M: Fix minor issues

This commit is contained in:
Ivan 2025-02-17 15:11:32 +03:00
parent 75c0a6f848
commit aae57d51f1
17 changed files with 76 additions and 90 deletions

View File

@ -5,13 +5,14 @@ import { useDebounce } from 'use-debounce';
import { Loader } from '@/components/Loader';
import { PARAMETER } from '@/utils/constants';
// TODO: add animation
export function GlobalLoader() {
const navigation = useNavigation();
const isLoading = navigation.state === 'loading';
const [loadingDebounced] = useDebounce(isLoading, PARAMETER.navigationPopupDelay);
if (!loadingDebounced || !isLoading) {
if (!loadingDebounced) {
return null;
}

View File

@ -31,9 +31,9 @@ export function SelectLibraryItem({
label: `${cst.alias}: ${cst.title}`
})) ?? [];
function filter(option: { value: number | undefined; label: string }, inputValue: string) {
const item = items?.find(item => item.id === option.value);
return !item ? false : matchLibraryItem(item, inputValue);
function filter(option: { value: string | undefined; label: string }, query: string) {
const item = items?.find(item => item.id === Number(option.value));
return !item ? false : matchLibraryItem(item, query);
}
return (
@ -42,7 +42,6 @@ export function SelectLibraryItem({
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}
{...restProps}

View File

@ -9,13 +9,13 @@ import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons';
import { CProps } from '@/components/props';
import { NoData } from '@/components/View';
import { IOperation, OperationID } from '../models/oss';
import { IOperation } from '../models/oss';
import SelectOperation from './SelectOperation';
interface PickMultiOperationProps extends CProps.Styling {
value: OperationID[];
onChange: (newValue: OperationID[]) => void;
value: number[];
onChange: (newValue: number[]) => void;
items: IOperation[];
rows?: number;
}
@ -27,7 +27,7 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
const nonSelectedItems = items.filter(item => !value.includes(item.id));
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
function handleDelete(operation: OperationID) {
function handleDelete(operation: number) {
onChange(value.filter(item => item !== operation));
}
@ -39,7 +39,7 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
}
}
function handleMoveUp(operation: OperationID) {
function handleMoveUp(operation: number) {
const index = value.indexOf(operation);
if (index > 0) {
const newSelected = [...value];
@ -49,7 +49,7 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
}
}
function handleMoveDown(operation: OperationID) {
function handleMoveDown(operation: number) {
const index = value.indexOf(operation);
if (index < value.length - 1) {
const newSelected = [...value];

View File

@ -5,7 +5,7 @@ import clsx from 'clsx';
import { SelectSingle } from '@/components/Input';
import { CProps } from '@/components/props';
import { IOperation, OperationID } from '../models/oss';
import { IOperation } from '../models/oss';
import { matchOperation } from '../models/ossAPI';
interface SelectOperationProps extends CProps.Styling {
@ -31,9 +31,9 @@ function SelectOperation({
label: `${cst.alias}: ${cst.title}`
})) ?? [];
function filter(option: { value: OperationID | undefined; label: string }, inputValue: string) {
const operation = items?.find(item => item.id === option.value);
return !operation ? false : matchOperation(operation, inputValue);
function filter(option: { value: string | undefined; label: string }, query: string) {
const operation = items?.find(item => item.id === Number(option.value));
return !operation ? false : matchOperation(operation, query);
}
return (
@ -42,7 +42,6 @@ function SelectOperation({
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}
{...restProps}

View File

@ -14,7 +14,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { IOperationCreateDTO, IOperationPosition, schemaOperationCreate } from '../../backend/types';
import { useOperationCreate } from '../../backend/useOperationCreate';
import { describeOperationType, labelOperationType } from '../../labels';
import { IOperationSchema, OperationID, OperationType } from '../../models/oss';
import { IOperationSchema, OperationType } from '../../models/oss';
import { calculateInsertPosition } from '../../models/ossAPI';
import TabInputOperation from './TabInputOperation';
@ -23,10 +23,10 @@ import TabSynthesisOperation from './TabSynthesisOperation';
export interface DlgCreateOperationProps {
oss: IOperationSchema;
positions: IOperationPosition[];
initialInputs: OperationID[];
initialInputs: number[];
defaultX: number;
defaultY: number;
onCreate?: (newID: OperationID) => void;
onCreate?: (newID: number) => void;
}
export enum TabID {

View File

@ -6,11 +6,6 @@ import { ICstSubstitute } from '@/features/rsform';
import { Graph } from '@/models/Graph';
/**
* Represents {@link IOperation} identifier type.
*/
export type OperationID = number;
/**
* Represents {@link IOperation} type.
*/
@ -23,7 +18,7 @@ export enum OperationType {
* Represents Operation.
*/
export interface IOperation {
id: OperationID;
id: number;
operation_type: OperationType;
oss: number;
@ -39,22 +34,22 @@ export interface IOperation {
is_owned: boolean;
is_consolidation: boolean; // aka 'diamond synthesis'
substitutions: ICstSubstituteEx[];
arguments: OperationID[];
arguments: number[];
}
/**
* Represents {@link IOperation} Argument.
*/
export interface IArgument {
operation: OperationID;
argument: OperationID;
operation: number;
argument: number;
}
/**
* Represents {@link ICstSubstitute} extended data.
*/
export interface ICstSubstituteEx extends ICstSubstitute {
operation: OperationID;
operation: number;
original_alias: string;
original_term: string;
substitution_alias: string;
@ -83,7 +78,7 @@ export interface IOperationSchema extends ILibraryItemData {
graph: Graph;
schemas: number[];
stats: IOperationSchemaStats;
operationByID: Map<OperationID, IOperation>;
operationByID: Map<number, IOperation>;
}
/**

View File

@ -21,7 +21,7 @@ import { Graph } from '../../../models/Graph';
import { IOperationPosition } from '../backend/types';
import { describeSubstitutionError } from '../labels';
import { IOperation, IOperationSchema, OperationID, OperationType, SubstitutionErrorType } from './oss';
import { IOperation, IOperationSchema, OperationType, SubstitutionErrorType } from './oss';
import { Position2D } from './ossLayout';
/**
@ -439,8 +439,8 @@ export class SubstitutionValidator {
* Filter relocate candidates from gives schema.
*/
export function getRelocateCandidates(
source: OperationID,
destination: OperationID,
source: number,
destination: number,
schema: IRSForm,
oss: IOperationSchema
): IConstituenta[] {
@ -482,7 +482,7 @@ export function getRelocateCandidates(
export function calculateInsertPosition(
oss: IOperationSchema,
operationType: OperationType,
argumentsOps: OperationID[],
argumentsOps: number[],
positions: IOperationPosition[],
defaultPosition: Position2D
): Position2D {

View File

@ -31,7 +31,6 @@ import { useInputCreate } from '../../../backend/useInputCreate';
import { useMutatingOss } from '../../../backend/useMutatingOss';
import { useOperationExecute } from '../../../backend/useOperationExecute';
import { useUpdatePositions } from '../../../backend/useUpdatePositions';
import { OperationID } from '../../../models/oss';
import { OssNode } from '../../../models/ossLayout';
import { useOSSGraphStore } from '../../../stores/ossGraph';
import { useOssEdit } from '../OssEditContext';
@ -137,7 +136,7 @@ function OssFlow() {
});
}
function handleCreateOperation(inputs: OperationID[]) {
function handleCreateOperation(inputs: number[]) {
const positions = getPositions();
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
controller.promptCreateOperation({
@ -149,7 +148,7 @@ function OssFlow() {
});
}
function handleDeleteOperation(target: OperationID) {
function handleDeleteOperation(target: number) {
if (!controller.canDelete(target)) {
return;
}
@ -163,7 +162,7 @@ function OssFlow() {
handleDeleteOperation(controller.selected[0]);
}
function handleInputCreate(target: OperationID) {
function handleInputCreate(target: number) {
const operation = controller.schema.operationByID.get(target);
if (!operation) {
return;
@ -178,15 +177,15 @@ function OssFlow() {
}).then(new_schema => router.push(urls.schema(new_schema.id)));
}
function handleEditSchema(target: OperationID) {
function handleEditSchema(target: number) {
controller.promptEditInput(target, getPositions());
}
function handleEditOperation(target: OperationID) {
function handleEditOperation(target: number) {
controller.promptEditOperation(target, getPositions());
}
function handleOperationExecute(target: OperationID) {
function handleOperationExecute(target: number) {
void operationExecute({
itemID: controller.schema.id, //
data: { target: target, positions: getPositions() }
@ -200,7 +199,7 @@ function OssFlow() {
handleOperationExecute(controller.selected[0]);
}
function handleRelocateConstituents(target: OperationID) {
function handleRelocateConstituents(target: number) {
controller.promptRelocateConstituents(target, getPositions());
}

View File

@ -14,7 +14,7 @@ import { promptText } from '@/utils/labels';
import { IOperationPosition } from '../../backend/types';
import { useOssSuspense } from '../../backend/useOSS';
import { IOperationSchema, OperationID, OperationType } from '../../models/oss';
import { IOperationSchema, OperationType } from '../../models/oss';
export enum OssTabID {
CARD = 0,
@ -24,14 +24,14 @@ export enum OssTabID {
export interface ICreateOperationPrompt {
defaultX: number;
defaultY: number;
inputs: OperationID[];
inputs: number[];
positions: IOperationPosition[];
callback: (newID: OperationID) => void;
callback: (newID: number) => void;
}
export interface IOssEditContext extends ILibraryItemEditor {
schema: IOperationSchema;
selected: OperationID[];
selected: number[];
isOwned: boolean;
isMutable: boolean;
@ -41,17 +41,17 @@ export interface IOssEditContext extends ILibraryItemEditor {
setShowTooltip: (newValue: boolean) => void;
navigateTab: (tab: OssTabID) => void;
navigateOperationSchema: (target: OperationID) => void;
navigateOperationSchema: (target: number) => void;
deleteSchema: () => void;
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
canDelete: (target: OperationID) => boolean;
canDelete: (target: number) => boolean;
promptCreateOperation: (props: ICreateOperationPrompt) => void;
promptDeleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void;
promptDeleteOperation: (target: number, positions: IOperationPosition[]) => void;
promptEditInput: (target: number, positions: IOperationPosition[]) => void;
promptEditOperation: (target: number, positions: IOperationPosition[]) => void;
promptRelocateConstituents: (target: number | undefined, positions: IOperationPosition[]) => void;
}
const OssEditContext = createContext<IOssEditContext | null>(null);
@ -83,7 +83,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
const isMutable = role > UserRole.READER && !schema.read_only;
const [showTooltip, setShowTooltip] = useState(true);
const [selected, setSelected] = useState<OperationID[]>([]);
const [selected, setSelected] = useState<number[]>([]);
const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
const showEditOperation = useDialogsStore(state => state.showEditOperation);
@ -112,7 +112,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
router.push(url);
}
function navigateOperationSchema(target: OperationID) {
function navigateOperationSchema(target: number) {
const node = schema.operationByID.get(target);
if (!node?.result) {
return;
@ -143,7 +143,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
});
}
function canDelete(target: OperationID) {
function canDelete(target: number) {
const operation = schema.operationByID.get(target);
if (!operation) {
return false;
@ -154,7 +154,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
return schema.graph.expandOutputs([target]).length === 0;
}
function promptEditOperation(target: OperationID, positions: IOperationPosition[]) {
function promptEditOperation(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target);
if (!operation) {
return;
@ -166,7 +166,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
});
}
function promptDeleteOperation(target: OperationID, positions: IOperationPosition[]) {
function promptDeleteOperation(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target);
if (!operation) {
return;
@ -178,7 +178,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
});
}
function promptEditInput(target: OperationID, positions: IOperationPosition[]) {
function promptEditInput(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target);
if (!operation) {
return;
@ -190,7 +190,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
});
}
function promptRelocateConstituents(target: OperationID | undefined, positions: IOperationPosition[]) {
function promptRelocateConstituents(target: number | undefined, positions: IOperationPosition[]) {
const operation = target ? schema.operationByID.get(target) : undefined;
showRelocateConstituents({
oss: schema,

View File

@ -33,9 +33,9 @@ function SelectConstituenta({
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
})) ?? [];
function filter(option: { value: number | undefined; label: string }, inputValue: string) {
const cst = items?.find(item => item.id === option.value);
return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL);
function filter(option: { value: string | undefined; label: string }, query: string) {
const cst = items?.find(item => item.id === Number(option.value));
return !cst ? false : matchConstituenta(cst, query, CstMatchMode.ALL);
}
return (
@ -44,7 +44,6 @@ function SelectConstituenta({
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}
{...restProps}

View File

@ -212,6 +212,13 @@ export function isFunctional(type: CstType): boolean {
}
}
/**
* Evaluate if {@link IConstituenta} can be used produce structure.
*/
export function canProduceStructure(cst: IConstituenta): boolean {
return !!cst.parse.typification && cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT;
}
/**
* Validate new alias against {@link CstType} and {@link IRSForm}.
*/

View File

@ -46,7 +46,7 @@ import { useMutatingRSForm } from '../../backend/useMutatingRSForm';
import { useProduceStructure } from '../../backend/useProduceStructure';
import { useResetAliases } from '../../backend/useResetAliases';
import { useRestoreOrder } from '../../backend/useRestoreOrder';
import { CstType } from '../../models/rsform';
import { canProduceStructure } from '../../models/rsformAPI';
import { useRSEdit } from './RSEditContext';
@ -75,12 +75,7 @@ function MenuRSTabs() {
const editMenu = useDropdown();
const accessMenu = useDropdown();
// TODO: move into separate function
const canProduceStructure =
!!controller.activeCst &&
!!controller.activeCst.parse.typification &&
controller.activeCst.cst_type !== CstType.BASE &&
controller.activeCst.cst_type !== CstType.CONSTANT;
const structureEnabled = !!controller.activeCst && canProduceStructure(controller.activeCst);
function calculateCloneLocation() {
const location = controller.schema.location;
@ -344,7 +339,7 @@ function MenuRSTabs() {
text='Порождение структуры'
titleHtml='Раскрыть структуру типизации <br/>выделенной конституенты'
icon={<IconGenerateStructure size='1rem' className='icon-primary' />}
disabled={!controller.isContentEditable || !canProduceStructure || isProcessing}
disabled={!controller.isContentEditable || !structureEnabled || isProcessing}
onClick={handleProduceStructure}
/>
<DropdownButton

View File

@ -3,10 +3,9 @@ import clsx from 'clsx';
import { CProps } from '@/components/props';
import { useLabelUser } from '../backend/useLabelUser';
import { UserID } from '../models/user';
interface InfoUsersProps extends CProps.Styling {
items: UserID[];
items: number[];
prefix: string;
header?: string;
}

View File

@ -7,13 +7,12 @@ import { CProps } from '@/components/props';
import { useLabelUser } from '../backend/useLabelUser';
import { useUsers } from '../backend/useUsers';
import { UserID } from '../models/user';
import { matchUser } from '../models/userAPI';
interface SelectUserProps extends CProps.Styling {
value?: UserID;
onChange: (newValue: UserID) => void;
filter?: (userID: UserID) => boolean;
value?: number;
onChange: (newValue: number) => void;
filter?: (userID: number) => boolean;
placeholder?: string;
noBorder?: boolean;
@ -36,9 +35,9 @@ export function SelectUser({
label: getUserLabel(user.id)
}));
function filterLabel(option: { value: UserID | undefined; label: string }, inputValue: string) {
const user = items.find(item => item.id === option.value);
return !user ? false : matchUser(user, inputValue);
function filterLabel(option: { value: string | undefined; label: string }, query: string) {
const user = items.find(item => item.id === Number(option.value));
return !user ? false : matchUser(user, query);
}
return (
@ -49,7 +48,6 @@ export function SelectUser({
onChange={data => {
if (data?.value !== undefined) onChange(data.value);
}}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filterLabel}
placeholder={placeholder}
{...restProps}

View File

@ -2,17 +2,12 @@
* Module: Models for Users.
*/
/**
* Represents {@link User} identifier type.
*/
export type UserID = number;
/**
* Represents user detailed information.
* Some information should only be accessible to authorized users
*/
export interface IUser {
id: UserID;
id: number;
username: string;
is_staff: boolean;
email: string;

View File

@ -12,7 +12,7 @@ export const PARAMETER = {
minimalTimeout: 10, // milliseconds delay for fast updates
zoomDuration: 500, // milliseconds animation duration
navigationDuration: 300, // milliseconds navigation duration
navigationPopupDelay: 200, // milliseconds delay for navigation popup
navigationPopupDelay: 300, // milliseconds delay for navigation popup
ossImageWidth: 1280, // pixels - size of OSS image
ossImageHeight: 960, // pixels - size of OSS image

View File

@ -6,8 +6,8 @@ test('should load the homepage and display login button', async ({ page }) => {
await authAnonymous(page);
await page.goto('/');
await page.waitForSelector('.h-full > .mr-1');
await expect(page).toHaveTitle('Концепт Портал');
await expect(page.getByRole('heading', { name: 'Портал' })).toBeVisible();
await page.click('.h-full > .mr-1');
await expect(page.getByText('Логин или email')).toBeVisible();