mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
R: Add ReadOnly wrapper for backend data
This commit is contained in:
parent
d90cf08b6c
commit
b7358d3cc7
|
@ -8,6 +8,7 @@ import { type z, ZodError } from 'zod';
|
||||||
import { buildConstants } from '@/utils/build-constants';
|
import { buildConstants } from '@/utils/build-constants';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { errorMsg } from '@/utils/labels';
|
import { errorMsg } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { extractErrorMessage } from '@/utils/utils';
|
import { extractErrorMessage } from '@/utils/utils';
|
||||||
|
|
||||||
export { AxiosError } from 'axios';
|
export { AxiosError } from 'axios';
|
||||||
|
@ -58,7 +59,7 @@ export function axiosGet<ResponseData>({ endpoint, options, schema }: IAxiosGetR
|
||||||
.get<ResponseData>(endpoint, options)
|
.get<ResponseData>(endpoint, options)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
schema?.parse(response.data);
|
schema?.parse(response.data);
|
||||||
return response.data;
|
return response.data as RO<ResponseData>;
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
// Note: Ignore cancellation errors
|
// Note: Ignore cancellation errors
|
||||||
|
@ -81,7 +82,7 @@ export function axiosPost<RequestData, ResponseData = void>({
|
||||||
.then(response => {
|
.then(response => {
|
||||||
schema?.parse(response.data);
|
schema?.parse(response.data);
|
||||||
notifySuccess(response.data, request?.successMessage);
|
notifySuccess(response.data, request?.successMessage);
|
||||||
return response.data;
|
return response.data as RO<ResponseData>;
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError | ZodError) => {
|
.catch((error: Error | AxiosError | ZodError) => {
|
||||||
notifyError(error);
|
notifyError(error);
|
||||||
|
@ -100,7 +101,7 @@ export function axiosDelete<RequestData, ResponseData = void>({
|
||||||
.then(response => {
|
.then(response => {
|
||||||
schema?.parse(response.data);
|
schema?.parse(response.data);
|
||||||
notifySuccess(response.data, request?.successMessage);
|
notifySuccess(response.data, request?.successMessage);
|
||||||
return response.data;
|
return response.data as RO<ResponseData>;
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError | ZodError) => {
|
.catch((error: Error | AxiosError | ZodError) => {
|
||||||
notifyError(error);
|
notifyError(error);
|
||||||
|
@ -119,7 +120,7 @@ export function axiosPatch<RequestData, ResponseData = void>({
|
||||||
.then(response => {
|
.then(response => {
|
||||||
schema?.parse(response.data);
|
schema?.parse(response.data);
|
||||||
notifySuccess(response.data, request?.successMessage);
|
notifySuccess(response.data, request?.successMessage);
|
||||||
return response.data;
|
return response.data as RO<ResponseData>;
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError | ZodError) => {
|
.catch((error: Error | AxiosError | ZodError) => {
|
||||||
notifyError(error);
|
notifyError(error);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { cn } from '../utils';
|
||||||
|
|
||||||
interface ComboBoxProps<Option> extends Styling {
|
interface ComboBoxProps<Option> extends Styling {
|
||||||
id?: string;
|
id?: string;
|
||||||
items?: Option[];
|
items?: readonly Option[];
|
||||||
value: Option | null;
|
value: Option | null;
|
||||||
onChange: (newValue: Option | null) => void;
|
onChange: (newValue: Option | null) => void;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
||||||
import { type IRSFormDTO } from '@/features/rsform';
|
import { type IRSFormDTO } from '@/features/rsform';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { libraryApi } from './api';
|
import { libraryApi } from './api';
|
||||||
import { type AccessPolicy, type ILibraryItem } from './types';
|
import { type AccessPolicy, type ILibraryItem } from './types';
|
||||||
|
@ -38,7 +39,7 @@ export const useSetAccessPolicy = () => {
|
||||||
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
||||||
!prev ? undefined : { ...prev, access_policy: variables.policy }
|
!prev ? undefined : { ...prev, access_policy: variables.policy }
|
||||||
);
|
);
|
||||||
client.setQueryData(libraryKey, (prev: ILibraryItem[] | undefined) =>
|
client.setQueryData(libraryKey, (prev: RO<ILibraryItem[]> | undefined) =>
|
||||||
prev?.map(item => (item.id === variables.itemID ? { ...item, access_policy: variables.policy } : item))
|
prev?.map(item => (item.id === variables.itemID ? { ...item, access_policy: variables.policy } : item))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
||||||
import { type IRSFormDTO } from '@/features/rsform';
|
import { type IRSFormDTO } from '@/features/rsform';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { libraryApi } from './api';
|
import { libraryApi } from './api';
|
||||||
import { type ILibraryItem } from './types';
|
import { type ILibraryItem } from './types';
|
||||||
|
@ -38,7 +39,7 @@ export const useSetLocation = () => {
|
||||||
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
||||||
!prev ? undefined : { ...prev, location: variables.location }
|
!prev ? undefined : { ...prev, location: variables.location }
|
||||||
);
|
);
|
||||||
client.setQueryData(libraryKey, (prev: ILibraryItem[] | undefined) =>
|
client.setQueryData(libraryKey, (prev: RO<ILibraryItem[]> | undefined) =>
|
||||||
prev?.map(item => (item.id === variables.itemID ? { ...item, location: variables.location } : item))
|
prev?.map(item => (item.id === variables.itemID ? { ...item, location: variables.location } : item))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
||||||
import { type IRSFormDTO } from '@/features/rsform';
|
import { type IRSFormDTO } from '@/features/rsform';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { libraryApi } from './api';
|
import { libraryApi } from './api';
|
||||||
import { type ILibraryItem } from './types';
|
import { type ILibraryItem } from './types';
|
||||||
|
@ -38,7 +39,7 @@ export const useSetOwner = () => {
|
||||||
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
||||||
!prev ? undefined : { ...prev, owner: variables.owner }
|
!prev ? undefined : { ...prev, owner: variables.owner }
|
||||||
);
|
);
|
||||||
client.setQueryData(libraryKey, (prev: ILibraryItem[] | undefined) =>
|
client.setQueryData(libraryKey, (prev: RO<ILibraryItem[]> | undefined) =>
|
||||||
prev?.map(item => (item.id === variables.itemID ? { ...item, owner: variables.owner } : item))
|
prev?.map(item => (item.id === variables.itemID ? { ...item, owner: variables.owner } : item))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
||||||
import { type IRSFormDTO } from '@/features/rsform';
|
import { type IRSFormDTO } from '@/features/rsform';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { libraryApi } from './api';
|
import { libraryApi } from './api';
|
||||||
import { type ILibraryItem, type IUpdateLibraryItemDTO, LibraryItemType } from './types';
|
import { type ILibraryItem, type IUpdateLibraryItemDTO, LibraryItemType } from './types';
|
||||||
|
@ -20,7 +21,7 @@ export const useUpdateItem = () => {
|
||||||
data.item_type === LibraryItemType.RSFORM
|
data.item_type === LibraryItemType.RSFORM
|
||||||
? KEYS.composite.rsItem({ itemID: data.id })
|
? KEYS.composite.rsItem({ itemID: data.id })
|
||||||
: KEYS.composite.ossItem({ itemID: data.id });
|
: KEYS.composite.ossItem({ itemID: data.id });
|
||||||
client.setQueryData(libraryKey, (prev: ILibraryItem[] | undefined) =>
|
client.setQueryData(libraryKey, (prev: RO<ILibraryItem[]> | undefined) =>
|
||||||
prev?.map(item => (item.id === data.id ? data : item))
|
prev?.map(item => (item.id === data.id ? data : item))
|
||||||
);
|
);
|
||||||
client.setQueryData(itemKey, (prev: IRSFormDTO | IOperationSchemaDTO | undefined) =>
|
client.setQueryData(itemKey, (prev: IRSFormDTO | IOperationSchemaDTO | undefined) =>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type ILibraryItem } from './types';
|
import { type ILibraryItem } from './types';
|
||||||
import { useLibraryListKey } from './use-library';
|
import { useLibraryListKey } from './use-library';
|
||||||
|
|
||||||
|
@ -10,7 +12,7 @@ export function useUpdateTimestamp() {
|
||||||
updateTimestamp: (target: number) =>
|
updateTimestamp: (target: number) =>
|
||||||
client.setQueryData(
|
client.setQueryData(
|
||||||
libraryKey, //
|
libraryKey, //
|
||||||
(prev: ILibraryItem[] | undefined) =>
|
(prev: RO<ILibraryItem[]> | undefined) =>
|
||||||
prev?.map(item => (item.id === target ? { ...item, time_update: Date() } : item))
|
prev?.map(item => (item.id === target ? { ...item, time_update: Date() } : item))
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { DataTable, type IConditionalStyle, type VisibilityState } from '@/compo
|
||||||
import { useWindowSize } from '@/hooks/use-window-size';
|
import { useWindowSize } from '@/hooks/use-window-size';
|
||||||
import { useFitHeight } from '@/stores/app-layout';
|
import { useFitHeight } from '@/stores/app-layout';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type ILibraryItem, LibraryItemType } from '../../backend/types';
|
import { type ILibraryItem, LibraryItemType } from '../../backend/types';
|
||||||
import { useLibrarySearchStore } from '../../stores/library-search';
|
import { useLibrarySearchStore } from '../../stores/library-search';
|
||||||
|
@ -16,7 +17,7 @@ import { useLibrarySearchStore } from '../../stores/library-search';
|
||||||
import { useLibraryColumns } from './use-library-columns';
|
import { useLibraryColumns } from './use-library-columns';
|
||||||
|
|
||||||
interface TableLibraryItemsProps {
|
interface TableLibraryItemsProps {
|
||||||
items: ILibraryItem[];
|
items: RO<ILibraryItem[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
||||||
|
@ -55,7 +56,7 @@ export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
||||||
<DataTable
|
<DataTable
|
||||||
id='library_data'
|
id='library_data'
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={items}
|
data={items as ILibraryItem[]}
|
||||||
headPosition='0'
|
headPosition='0'
|
||||||
className={clsx('cc-scroll-y h-fit text-xs sm:text-sm border-b', folderMode && 'border-l')}
|
className={clsx('cc-scroll-y h-fit text-xs sm:text-sm border-b', folderMode && 'border-l')}
|
||||||
style={{ maxHeight: tableHeight }}
|
style={{ maxHeight: tableHeight }}
|
||||||
|
|
|
@ -6,12 +6,13 @@ import { MiniButton } from '@/components/control';
|
||||||
import { createColumnHelper } from '@/components/data-table';
|
import { createColumnHelper } from '@/components/data-table';
|
||||||
import { IconFolderTree } from '@/components/icons';
|
import { IconFolderTree } from '@/components/icons';
|
||||||
import { useWindowSize } from '@/hooks/use-window-size';
|
import { useWindowSize } from '@/hooks/use-window-size';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type ILibraryItem } from '../../backend/types';
|
import { type ILibraryItem } from '../../backend/types';
|
||||||
import { BadgeLocation } from '../../components/badge-location';
|
import { BadgeLocation } from '../../components/badge-location';
|
||||||
import { useLibrarySearchStore } from '../../stores/library-search';
|
import { useLibrarySearchStore } from '../../stores/library-search';
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
const columnHelper = createColumnHelper<RO<ILibraryItem>>();
|
||||||
|
|
||||||
export function useLibraryColumns() {
|
export function useLibraryColumns() {
|
||||||
const { isSmall } = useWindowSize();
|
const { isSmall } = useWindowSize();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { type ILibraryItem } from '@/features/library';
|
import { type ILibraryItem } from '@/features/library';
|
||||||
|
|
||||||
import { Graph } from '@/models/graph';
|
import { Graph } from '@/models/graph';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type IBlock, type IOperation, type IOperationSchema, type IOperationSchemaStats } from '../models/oss';
|
import { type IBlock, type IOperation, type IOperationSchema, type IOperationSchemaStats } from '../models/oss';
|
||||||
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from '../pages/oss-page/editor-oss-graph/graph/block-node';
|
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from '../pages/oss-page/editor-oss-graph/graph/block-node';
|
||||||
|
@ -19,9 +20,9 @@ export class OssLoader {
|
||||||
private operationByID = new Map<number, IOperation>();
|
private operationByID = new Map<number, IOperation>();
|
||||||
private blockByID = new Map<number, IBlock>();
|
private blockByID = new Map<number, IBlock>();
|
||||||
private schemaIDs: number[] = [];
|
private schemaIDs: number[] = [];
|
||||||
private items: ILibraryItem[];
|
private items: RO<ILibraryItem[]>;
|
||||||
|
|
||||||
constructor(input: IOperationSchemaDTO, items: ILibraryItem[]) {
|
constructor(input: RO<IOperationSchemaDTO>, items: RO<ILibraryItem[]>) {
|
||||||
this.oss = structuredClone(input) as IOperationSchema;
|
this.oss = structuredClone(input) as IOperationSchema;
|
||||||
this.items = items;
|
this.items = items;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { ossApi } from './api';
|
import { ossApi } from './api';
|
||||||
import { type IOperationSchemaDTO, type IOssLayout } from './types';
|
import { type IOperationSchemaDTO, type IOssLayout } from './types';
|
||||||
|
@ -17,7 +18,7 @@ export const useUpdateLayout = () => {
|
||||||
updateTimestamp(variables.itemID);
|
updateTimestamp(variables.itemID);
|
||||||
client.setQueryData(
|
client.setQueryData(
|
||||||
ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey,
|
ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey,
|
||||||
(prev: IOperationSchemaDTO | undefined) =>
|
(prev: RO<IOperationSchemaDTO> | undefined) =>
|
||||||
!prev
|
!prev
|
||||||
? prev
|
? prev
|
||||||
: {
|
: {
|
||||||
|
|
|
@ -9,9 +9,10 @@ import { ComboBox } from '@/components/input/combo-box';
|
||||||
import { type Styling } from '@/components/props';
|
import { type Styling } from '@/components/props';
|
||||||
import { cn } from '@/components/utils';
|
import { cn } from '@/components/utils';
|
||||||
import { NoData } from '@/components/view';
|
import { NoData } from '@/components/view';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { labelOssItem } from '../labels';
|
import { labelOssItem } from '../labels';
|
||||||
import { type IBlock, type IOperation, type IOperationSchema } from '../models/oss';
|
import { type IOperationSchema, type IOssItem } from '../models/oss';
|
||||||
import { getItemID, isOperation } from '../models/oss-api';
|
import { getItemID, isOperation } from '../models/oss-api';
|
||||||
|
|
||||||
const SELECTION_CLEAR_TIMEOUT = 1000;
|
const SELECTION_CLEAR_TIMEOUT = 1000;
|
||||||
|
@ -25,7 +26,7 @@ interface PickMultiOperationProps extends Styling {
|
||||||
disallowBlocks?: boolean;
|
disallowBlocks?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<IOperation | IBlock>();
|
const columnHelper = createColumnHelper<RO<IOssItem>>();
|
||||||
|
|
||||||
export function PickContents({
|
export function PickContents({
|
||||||
rows,
|
rows,
|
||||||
|
@ -40,7 +41,7 @@ export function PickContents({
|
||||||
const selectedItems = value
|
const selectedItems = value
|
||||||
.map(itemID => (itemID > 0 ? schema.operationByID.get(itemID) : schema.blockByID.get(-itemID)))
|
.map(itemID => (itemID > 0 ? schema.operationByID.get(itemID) : schema.blockByID.get(-itemID)))
|
||||||
.filter(item => item !== undefined);
|
.filter(item => item !== undefined);
|
||||||
const [lastSelected, setLastSelected] = useState<IOperation | IBlock | null>(null);
|
const [lastSelected, setLastSelected] = useState<RO<IOssItem> | null>(null);
|
||||||
const items = [
|
const items = [
|
||||||
...(disallowBlocks ? [] : schema.blocks.filter(item => !value.includes(-item.id) && !exclude?.includes(-item.id))),
|
...(disallowBlocks ? [] : schema.blocks.filter(item => !value.includes(-item.id) && !exclude?.includes(-item.id))),
|
||||||
...schema.operations.filter(item => !value.includes(item.id) && !exclude?.includes(item.id))
|
...schema.operations.filter(item => !value.includes(item.id) && !exclude?.includes(item.id))
|
||||||
|
@ -50,7 +51,7 @@ export function PickContents({
|
||||||
onChange(value.filter(item => item !== target));
|
onChange(value.filter(item => item !== target));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelect(target: IOperation | IBlock | null) {
|
function handleSelect(target: RO<IOssItem> | null) {
|
||||||
if (target) {
|
if (target) {
|
||||||
setLastSelected(target);
|
setLastSelected(target);
|
||||||
onChange([...value, getItemID(target)]);
|
onChange([...value, getItemID(target)]);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { OperationType } from './backend/types';
|
import { OperationType } from './backend/types';
|
||||||
import {
|
import {
|
||||||
type IOperation,
|
type IOperation,
|
||||||
|
@ -26,7 +28,7 @@ export function describeOperationType(itemType: OperationType): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generates error description for {@link ISubstitutionErrorDescription}. */
|
/** Generates error description for {@link ISubstitutionErrorDescription}. */
|
||||||
export function describeSubstitutionError(error: ISubstitutionErrorDescription): string {
|
export function describeSubstitutionError(error: RO<ISubstitutionErrorDescription>): string {
|
||||||
switch (error.errorType) {
|
switch (error.errorType) {
|
||||||
case SubstitutionErrorType.invalidIDs:
|
case SubstitutionErrorType.invalidIDs:
|
||||||
return 'Ошибка в идентификаторах схем';
|
return 'Ошибка в идентификаторах схем';
|
||||||
|
@ -55,7 +57,7 @@ export function describeSubstitutionError(error: ISubstitutionErrorDescription):
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves label for {@link IOssItem}. */
|
/** Retrieves label for {@link IOssItem}. */
|
||||||
export function labelOssItem(item: IOssItem): string {
|
export function labelOssItem(item: RO<IOssItem>): string {
|
||||||
if (isOperation(item)) {
|
if (isOperation(item)) {
|
||||||
return `${(item as IOperation).alias}: ${item.title}`;
|
return `${(item as IOperation).alias}: ${item.title}`;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
} from '@/features/rsform/models/rslang-api';
|
} from '@/features/rsform/models/rslang-api';
|
||||||
|
|
||||||
import { infoMsg } from '@/utils/labels';
|
import { infoMsg } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { Graph } from '../../../models/graph';
|
import { Graph } from '../../../models/graph';
|
||||||
import { describeSubstitutionError } from '../labels';
|
import { describeSubstitutionError } from '../labels';
|
||||||
|
@ -29,17 +30,17 @@ import { type IOperationSchema, type IOssItem, SubstitutionErrorType } from './o
|
||||||
const STARTING_SUB_INDEX = 900; // max semantic index for starting substitution
|
const STARTING_SUB_INDEX = 900; // max semantic index for starting substitution
|
||||||
|
|
||||||
/** Checks if element is {@link IOperation} or {@link IBlock}. */
|
/** Checks if element is {@link IOperation} or {@link IBlock}. */
|
||||||
export function isOperation(item: IOssItem | null): boolean {
|
export function isOperation(item: RO<IOssItem> | null): boolean {
|
||||||
return !!item && 'arguments' in item;
|
return !!item && 'arguments' in item;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract contiguous ID of {@link IOperation} or {@link IBlock}. */
|
/** Extract contiguous ID of {@link IOperation} or {@link IBlock}. */
|
||||||
export function getItemID(item: IOssItem): number {
|
export function getItemID(item: RO<IOssItem>): number {
|
||||||
return isOperation(item) ? item.id : -item.id;
|
return isOperation(item) ? item.id : -item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sorts library items relevant for the specified {@link IOperationSchema}. */
|
/** Sorts library items relevant for the specified {@link IOperationSchema}. */
|
||||||
export function sortItemsForOSS(oss: IOperationSchema, items: ILibraryItem[]): ILibraryItem[] {
|
export function sortItemsForOSS(oss: IOperationSchema, items: readonly ILibraryItem[]): ILibraryItem[] {
|
||||||
const result = items.filter(item => item.location === oss.location);
|
const result = items.filter(item => item.location === oss.location);
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.visible && item.owner === oss.owner && !result.includes(item)) {
|
if (item.visible && item.owner === oss.owner && !result.includes(item)) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Graph } from '@/models/graph';
|
import { Graph } from '@/models/graph';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform';
|
import { type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform';
|
||||||
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '../models/rsform-api';
|
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '../models/rsform-api';
|
||||||
|
@ -23,7 +24,7 @@ export class RSFormLoader {
|
||||||
private cstByAlias = new Map<string, IConstituenta>();
|
private cstByAlias = new Map<string, IConstituenta>();
|
||||||
private cstByID = new Map<number, IConstituenta>();
|
private cstByID = new Map<number, IConstituenta>();
|
||||||
|
|
||||||
constructor(input: IRSFormDTO) {
|
constructor(input: RO<IRSFormDTO>) {
|
||||||
this.schema = structuredClone(input) as IRSForm;
|
this.schema = structuredClone(input) as IRSForm;
|
||||||
this.schema.version = input.version ?? 'latest';
|
this.schema.version = input.version ?? 'latest';
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { ModalForm } from '@/components/modal';
|
import { ModalForm } from '@/components/modal';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { errorMsg } from '@/utils/labels';
|
import { errorMsg } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IConstituentaBasicsDTO,
|
type IConstituentaBasicsDTO,
|
||||||
|
@ -21,7 +22,7 @@ import { FormCreateCst } from './form-create-cst';
|
||||||
export interface DlgCreateCstProps {
|
export interface DlgCreateCstProps {
|
||||||
initial: ICreateConstituentaDTO;
|
initial: ICreateConstituentaDTO;
|
||||||
schema: IRSForm;
|
schema: IRSForm;
|
||||||
onCreate: (data: IConstituentaBasicsDTO) => void;
|
onCreate: (data: RO<IConstituentaBasicsDTO>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DlgCreateCst() {
|
export function DlgCreateCst() {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Loader } from '@/components/loader';
|
||||||
import { ModalForm } from '@/components/modal';
|
import { ModalForm } from '@/components/modal';
|
||||||
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
|
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CstType,
|
CstType,
|
||||||
|
@ -28,7 +29,7 @@ import { TemplateState } from './template-state';
|
||||||
|
|
||||||
export interface DlgCstTemplateProps {
|
export interface DlgCstTemplateProps {
|
||||||
schema: IRSForm;
|
schema: IRSForm;
|
||||||
onCreate: (data: IConstituentaBasicsDTO) => void;
|
onCreate: (data: RO<IConstituentaBasicsDTO>) => void;
|
||||||
insertAfter?: number;
|
insertAfter?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect } from 'react';
|
||||||
import { type Edge, MarkerType, type Node, useEdgesState, useNodesState } from 'reactflow';
|
import { type Edge, MarkerType, type Node, useEdgesState, useNodesState } from 'reactflow';
|
||||||
|
|
||||||
import { DiagramFlow } from '@/components/flow/diagram-flow';
|
import { DiagramFlow } from '@/components/flow/diagram-flow';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type SyntaxTree } from '../../models/rslang';
|
import { type SyntaxTree } from '../../models/rslang';
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ const flowOptions = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
interface ASTFlowProps {
|
interface ASTFlowProps {
|
||||||
data: SyntaxTree;
|
data: RO<SyntaxTree>;
|
||||||
onNodeEnter: (node: Node) => void;
|
onNodeEnter: (node: Node) => void;
|
||||||
onNodeLeave: (node: Node) => void;
|
onNodeLeave: (node: Node) => void;
|
||||||
onChangeDragging: (value: boolean) => void;
|
onChangeDragging: (value: boolean) => void;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { HelpTopic } from '@/features/help';
|
||||||
|
|
||||||
import { ModalView } from '@/components/modal';
|
import { ModalView } from '@/components/modal';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type SyntaxTree } from '../../models/rslang';
|
import { type SyntaxTree } from '../../models/rslang';
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ import { ASTFlow } from './ast-flow';
|
||||||
const NODE_POPUP_DELAY = 100;
|
const NODE_POPUP_DELAY = 100;
|
||||||
|
|
||||||
export interface DlgShowASTProps {
|
export interface DlgShowASTProps {
|
||||||
syntaxTree: SyntaxTree;
|
syntaxTree: RO<SyntaxTree>;
|
||||||
expression: string;
|
expression: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { HelpTopic } from '@/features/help';
|
||||||
import { ModalView } from '@/components/modal';
|
import { ModalView } from '@/components/modal';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { errorMsg } from '@/utils/labels';
|
import { errorMsg } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type ITypeInfo } from '../../models/rslang';
|
import { type ITypeInfo } from '../../models/rslang';
|
||||||
import { TypificationGraph } from '../../models/typification-graph';
|
import { TypificationGraph } from '../../models/typification-graph';
|
||||||
|
@ -15,7 +16,7 @@ import { TypificationGraph } from '../../models/typification-graph';
|
||||||
import { MGraphFlow } from './mgraph-flow';
|
import { MGraphFlow } from './mgraph-flow';
|
||||||
|
|
||||||
export interface DlgShowTypeGraphProps {
|
export interface DlgShowTypeGraphProps {
|
||||||
items: ITypeInfo[];
|
items: RO<ITypeInfo[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DlgShowTypeGraph() {
|
export function DlgShowTypeGraph() {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { prepareTooltip } from '@/utils/utils';
|
import { prepareTooltip } from '@/utils/utils';
|
||||||
|
|
||||||
import { type IVersionInfo } from '../library/backend/types';
|
import { type IVersionInfo } from '../library/backend/types';
|
||||||
|
@ -18,7 +19,7 @@ import { type GraphColoring } from './stores/term-graph';
|
||||||
/**
|
/**
|
||||||
* Generates description for {@link IConstituenta}.
|
* Generates description for {@link IConstituenta}.
|
||||||
*/
|
*/
|
||||||
export function describeConstituenta(cst: IConstituenta): string {
|
export function describeConstituenta(cst: RO<IConstituenta>): string {
|
||||||
if (cst.cst_type === CstType.STRUCTURED) {
|
if (cst.cst_type === CstType.STRUCTURED) {
|
||||||
return (
|
return (
|
||||||
cst.term_resolved ||
|
cst.term_resolved ||
|
||||||
|
@ -43,7 +44,7 @@ export function describeConstituenta(cst: IConstituenta): string {
|
||||||
/**
|
/**
|
||||||
* Generates description for term of a given {@link IConstituenta}.
|
* Generates description for term of a given {@link IConstituenta}.
|
||||||
*/
|
*/
|
||||||
export function describeConstituentaTerm(cst: IConstituenta | null): string {
|
export function describeConstituentaTerm(cst: RO<IConstituenta> | null): string {
|
||||||
if (!cst) {
|
if (!cst) {
|
||||||
return '!Конституента отсутствует!';
|
return '!Конституента отсутствует!';
|
||||||
}
|
}
|
||||||
|
@ -57,14 +58,14 @@ export function describeConstituentaTerm(cst: IConstituenta | null): string {
|
||||||
/**
|
/**
|
||||||
* Generates label for {@link IConstituenta}.
|
* Generates label for {@link IConstituenta}.
|
||||||
*/
|
*/
|
||||||
export function labelConstituenta(cst: IConstituenta) {
|
export function labelConstituenta(cst: RO<IConstituenta>) {
|
||||||
return `${cst.alias}: ${describeConstituenta(cst)}`;
|
return `${cst.alias}: ${describeConstituenta(cst)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates label for {@link IVersionInfo} of {@link IRSForm}.
|
* Generates label for {@link IVersionInfo} of {@link IRSForm}.
|
||||||
*/
|
*/
|
||||||
export function labelVersion(value: CurrentVersion, items: IVersionInfo[]) {
|
export function labelVersion(value: CurrentVersion, items: RO<IVersionInfo[]>) {
|
||||||
const version = items.find(ver => ver.id === value);
|
const version = items.find(ver => ver.id === value);
|
||||||
return version ? version.version : 'актуальная';
|
return version ? version.version : 'актуальная';
|
||||||
}
|
}
|
||||||
|
@ -345,7 +346,7 @@ export function labelTypification({
|
||||||
}: {
|
}: {
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
resultType: string;
|
resultType: string;
|
||||||
args: IArgumentInfo[];
|
args: RO<IArgumentInfo[]>;
|
||||||
}): string {
|
}): string {
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
|
@ -363,7 +364,7 @@ export function labelTypification({
|
||||||
/**
|
/**
|
||||||
* Generates label for {@link IConstituenta} typification.
|
* Generates label for {@link IConstituenta} typification.
|
||||||
*/
|
*/
|
||||||
export function labelCstTypification(cst: IConstituenta): string {
|
export function labelCstTypification(cst: RO<IConstituenta>): string {
|
||||||
return labelTypification({
|
return labelTypification({
|
||||||
isValid: cst.parse.status === ParsingStatus.VERIFIED,
|
isValid: cst.parse.status === ParsingStatus.VERIFIED,
|
||||||
resultType: cst.parse.typification,
|
resultType: cst.parse.typification,
|
||||||
|
@ -374,7 +375,7 @@ export function labelCstTypification(cst: IConstituenta): string {
|
||||||
/**
|
/**
|
||||||
* Generates label for {@link ISyntaxTreeNode}.
|
* Generates label for {@link ISyntaxTreeNode}.
|
||||||
*/
|
*/
|
||||||
export function labelSyntaxTree(node: ISyntaxTreeNode): string {
|
export function labelSyntaxTree(node: RO<ISyntaxTreeNode>): string {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
switch (node.typeID) {
|
switch (node.typeID) {
|
||||||
case TokenID.ID_LOCAL:
|
case TokenID.ID_LOCAL:
|
||||||
|
@ -531,7 +532,7 @@ export function labelGrammeme(gram: Grammeme): string {
|
||||||
/**
|
/**
|
||||||
* Generates error description for {@link IRSErrorDescription}.
|
* Generates error description for {@link IRSErrorDescription}.
|
||||||
*/
|
*/
|
||||||
export function describeRSError(error: IRSErrorDescription): string {
|
export function describeRSError(error: RO<IRSErrorDescription>): string {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
switch (error.errorType) {
|
switch (error.errorType) {
|
||||||
case RSErrorType.unknownSymbol:
|
case RSErrorType.unknownSymbol:
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import { BASIC_SCHEMAS, type ILibraryItem } from '@/features/library';
|
import { BASIC_SCHEMAS, type ILibraryItem } from '@/features/library';
|
||||||
|
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { TextMatcher } from '@/utils/utils';
|
import { TextMatcher } from '@/utils/utils';
|
||||||
|
|
||||||
import { CstType, ParsingStatus, ValueClass } from '../backend/types';
|
import { CstType, ParsingStatus, ValueClass } from '../backend/types';
|
||||||
|
@ -18,7 +19,7 @@ import { CATEGORY_CST_TYPE, CstClass, ExpressionStatus, type IConstituenta, type
|
||||||
* @param query - The query string used for matching.
|
* @param query - The query string used for matching.
|
||||||
* @param mode - The matching mode to determine which properties to include in the matching process.
|
* @param mode - The matching mode to determine which properties to include in the matching process.
|
||||||
*/
|
*/
|
||||||
export function matchConstituenta(target: IConstituenta, query: string, mode: CstMatchMode): boolean {
|
export function matchConstituenta(target: RO<IConstituenta>, query: string, mode: CstMatchMode): boolean {
|
||||||
const matcher = new TextMatcher(query);
|
const matcher = new TextMatcher(query);
|
||||||
if ((mode === CstMatchMode.ALL || mode === CstMatchMode.NAME) && matcher.test(target.alias)) {
|
if ((mode === CstMatchMode.ALL || mode === CstMatchMode.NAME) && matcher.test(target.alias)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -214,7 +215,7 @@ export function isFunctional(type: CstType): boolean {
|
||||||
/**
|
/**
|
||||||
* Evaluate if {@link IConstituenta} can be used produce structure.
|
* Evaluate if {@link IConstituenta} can be used produce structure.
|
||||||
*/
|
*/
|
||||||
export function canProduceStructure(cst: IConstituenta): boolean {
|
export function canProduceStructure(cst: RO<IConstituenta>): boolean {
|
||||||
return !!cst.parse.typification && cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT;
|
return !!cst.parse.typification && cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +272,7 @@ export function generateAlias(type: CstType, schema: IRSForm, takenAliases: stri
|
||||||
/**
|
/**
|
||||||
* Sorts library items relevant for InlineSynthesis with specified {@link IRSForm}.
|
* Sorts library items relevant for InlineSynthesis with specified {@link IRSForm}.
|
||||||
*/
|
*/
|
||||||
export function sortItemsForInlineSynthesis(receiver: IRSForm, items: ILibraryItem[]): ILibraryItem[] {
|
export function sortItemsForInlineSynthesis(receiver: IRSForm, items: readonly ILibraryItem[]): ILibraryItem[] {
|
||||||
const result = items.filter(item => item.location === receiver.location);
|
const result = items.filter(item => item.location === receiver.location);
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.visible && item.owner === item.owner && !result.includes(item)) {
|
if (item.visible && item.owner === item.owner && !result.includes(item)) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { type Tree } from '@lezer/common';
|
||||||
|
|
||||||
import { cursorNode } from '@/utils/codemirror';
|
import { cursorNode } from '@/utils/codemirror';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { CstType, type IRSErrorDescription, type RSErrorType } from '../backend/types';
|
import { CstType, type IRSErrorDescription, type RSErrorType } from '../backend/types';
|
||||||
import { labelCstTypification } from '../labels';
|
import { labelCstTypification } from '../labels';
|
||||||
|
@ -36,7 +37,7 @@ export function isSetTypification(text: string): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Infers type of constituent for a given template and arguments. */
|
/** Infers type of constituent for a given template and arguments. */
|
||||||
export function inferTemplatedType(templateType: CstType, args: IArgumentValue[]): CstType {
|
export function inferTemplatedType(templateType: CstType, args: RO<IArgumentValue[]>): CstType {
|
||||||
if (args.length === 0 || args.some(arg => !arg.value)) {
|
if (args.length === 0 || args.some(arg => !arg.value)) {
|
||||||
return templateType;
|
return templateType;
|
||||||
} else if (templateType === CstType.PREDICATE) {
|
} else if (templateType === CstType.PREDICATE) {
|
||||||
|
@ -92,7 +93,7 @@ export function splitTemplateDefinition(target: string) {
|
||||||
* It replaces template argument placeholders in the expression with their corresponding values
|
* It replaces template argument placeholders in the expression with their corresponding values
|
||||||
* from the provided arguments.
|
* from the provided arguments.
|
||||||
*/
|
*/
|
||||||
export function substituteTemplateArgs(expression: string, args: IArgumentValue[]): string {
|
export function substituteTemplateArgs(expression: string, args: RO<IArgumentValue[]>): string {
|
||||||
if (args.every(arg => !arg.value)) {
|
if (args.every(arg => !arg.value)) {
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +122,7 @@ export function substituteTemplateArgs(expression: string, args: IArgumentValue[
|
||||||
/**
|
/**
|
||||||
* Generate ErrorID label.
|
* Generate ErrorID label.
|
||||||
*/
|
*/
|
||||||
export function getRSErrorPrefix(error: IRSErrorDescription): string {
|
export function getRSErrorPrefix(error: RO<IRSErrorDescription>): string {
|
||||||
const id = error.errorType.toString(16);
|
const id = error.errorType.toString(16);
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
switch(inferErrorClass(error.errorType)) {
|
switch(inferErrorClass(error.errorType)) {
|
||||||
|
@ -133,12 +134,12 @@ export function getRSErrorPrefix(error: IRSErrorDescription): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply alias mapping. */
|
/** Apply alias mapping. */
|
||||||
export function applyAliasMapping(target: string, mapping: AliasMapping): string {
|
export function applyAliasMapping(target: string, mapping: RO<AliasMapping>): string {
|
||||||
return applyPattern(target, mapping, GLOBALS_REGEXP);
|
return applyPattern(target, mapping, GLOBALS_REGEXP);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply alias typification mapping. */
|
/** Apply alias typification mapping. */
|
||||||
export function applyTypificationMapping(target: string, mapping: AliasMapping): string {
|
export function applyTypificationMapping(target: string, mapping: RO<AliasMapping>): string {
|
||||||
const modified = applyAliasMapping(target, mapping);
|
const modified = applyAliasMapping(target, mapping);
|
||||||
if (modified === target) {
|
if (modified === target) {
|
||||||
return target;
|
return target;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type IArgumentInfo } from './rslang';
|
import { type IArgumentInfo } from './rslang';
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ export class TypificationGraph {
|
||||||
* @param result - typification of the formal definition.
|
* @param result - typification of the formal definition.
|
||||||
* @param args - arguments for term or predicate function.
|
* @param args - arguments for term or predicate function.
|
||||||
*/
|
*/
|
||||||
addConstituenta(alias: string, result: string, args: IArgumentInfo[]): void {
|
addConstituenta(alias: string, result: string, args: RO<IArgumentInfo[]>): void {
|
||||||
const argsNode = this.processArguments(args);
|
const argsNode = this.processArguments(args);
|
||||||
const resultNode = this.processResult(result);
|
const resultNode = this.processResult(result);
|
||||||
const combinedNode = this.combineResults(resultNode, argsNode);
|
const combinedNode = this.combineResults(resultNode, argsNode);
|
||||||
|
@ -122,7 +123,7 @@ export class TypificationGraph {
|
||||||
this.nodeByAlias.set(alias, nodeToAnnotate);
|
this.nodeByAlias.set(alias, nodeToAnnotate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processArguments(args: IArgumentInfo[]): TypificationGraphNode | null {
|
private processArguments(args: RO<IArgumentInfo[]>): TypificationGraphNode | null {
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { Indicator } from '@/components/view';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { useModificationStore } from '@/stores/modification';
|
import { useModificationStore } from '@/stores/modification';
|
||||||
import { errorMsg, tooltipText } from '@/utils/labels';
|
import { errorMsg, tooltipText } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { promptUnsaved } from '@/utils/utils';
|
import { promptUnsaved } from '@/utils/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -68,7 +69,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const [forceComment, setForceComment] = useState(false);
|
const [forceComment, setForceComment] = useState(false);
|
||||||
const [localParse, setLocalParse] = useState<IExpressionParseDTO | null>(null);
|
const [localParse, setLocalParse] = useState<RO<IExpressionParseDTO> | null>(null);
|
||||||
|
|
||||||
const typification = useMemo(
|
const typification = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useResetOnChange } from '@/hooks/use-reset-on-change';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { errorMsg } from '@/utils/labels';
|
import { errorMsg } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ICheckConstituentaDTO,
|
type ICheckConstituentaDTO,
|
||||||
|
@ -42,7 +43,7 @@ interface EditorRSExpressionProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
toggleReset?: boolean;
|
toggleReset?: boolean;
|
||||||
|
|
||||||
onChangeLocalParse: (typification: IExpressionParseDTO) => void;
|
onChangeLocalParse: (typification: RO<IExpressionParseDTO>) => void;
|
||||||
onOpenEdit: (cstID: number) => void;
|
onOpenEdit: (cstID: number) => void;
|
||||||
onShowTypeGraph: (event: React.MouseEvent<Element>) => void;
|
onShowTypeGraph: (event: React.MouseEvent<Element>) => void;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +63,7 @@ export function EditorRSExpression({
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(false);
|
const [isModified, setIsModified] = useState(false);
|
||||||
const rsInput = useRef<ReactCodeMirrorRef>(null);
|
const rsInput = useRef<ReactCodeMirrorRef>(null);
|
||||||
const [parseData, setParseData] = useState<IExpressionParseDTO | null>(null);
|
const [parseData, setParseData] = useState<RO<IExpressionParseDTO> | null>(null);
|
||||||
|
|
||||||
const isProcessing = useMutatingRSForm();
|
const isProcessing = useMutatingRSForm();
|
||||||
const showControls = usePreferencesStore(state => state.showExpressionControls);
|
const showControls = usePreferencesStore(state => state.showExpressionControls);
|
||||||
|
@ -78,7 +79,7 @@ export function EditorRSExpression({
|
||||||
function checkConstituenta(
|
function checkConstituenta(
|
||||||
expression: string,
|
expression: string,
|
||||||
activeCst: IConstituenta,
|
activeCst: IConstituenta,
|
||||||
onSuccess?: (data: IExpressionParseDTO) => void
|
onSuccess?: (data: RO<IExpressionParseDTO>) => void
|
||||||
) {
|
) {
|
||||||
const data: ICheckConstituentaDTO = {
|
const data: ICheckConstituentaDTO = {
|
||||||
definition_formal: expression,
|
definition_formal: expression,
|
||||||
|
@ -96,7 +97,7 @@ export function EditorRSExpression({
|
||||||
setIsModified(newValue !== activeCst.definition_formal);
|
setIsModified(newValue !== activeCst.definition_formal);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCheckExpression(callback?: (parse: IExpressionParseDTO) => void) {
|
function handleCheckExpression(callback?: (parse: RO<IExpressionParseDTO>) => void) {
|
||||||
checkConstituenta(value, activeCst, parse => {
|
checkConstituenta(value, activeCst, parse => {
|
||||||
onChangeLocalParse(parse);
|
onChangeLocalParse(parse);
|
||||||
if (parse.errors.length > 0) {
|
if (parse.errors.length > 0) {
|
||||||
|
@ -109,7 +110,7 @@ export function EditorRSExpression({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowError(error: IRSErrorDescription, prefixLen: number) {
|
function onShowError(error: RO<IRSErrorDescription>, prefixLen: number) {
|
||||||
if (!rsInput.current) {
|
if (!rsInput.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
|
|
||||||
import { type IExpressionParseDTO, type IRSErrorDescription } from '../../../backend/types';
|
import { type IExpressionParseDTO, type IRSErrorDescription } from '../../../backend/types';
|
||||||
import { describeRSError } from '../../../labels';
|
import { describeRSError } from '../../../labels';
|
||||||
import { getRSErrorPrefix } from '../../../models/rslang-api';
|
import { getRSErrorPrefix } from '../../../models/rslang-api';
|
||||||
|
|
||||||
interface ParsingResultProps {
|
interface ParsingResultProps {
|
||||||
data: IExpressionParseDTO | null;
|
data: RO<IExpressionParseDTO> | null;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onShowError: (error: IRSErrorDescription) => void;
|
onShowError: (error: RO<IRSErrorDescription>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultProps) {
|
export function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultProps) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { BadgeHelp } from '@/features/help/components';
|
||||||
import { Loader } from '@/components/loader';
|
import { Loader } from '@/components/loader';
|
||||||
import { APP_COLORS } from '@/styling/colors';
|
import { APP_COLORS } from '@/styling/colors';
|
||||||
import { globalIDs } from '@/utils/constants';
|
import { globalIDs } from '@/utils/constants';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { prepareTooltip } from '@/utils/utils';
|
import { prepareTooltip } from '@/utils/utils';
|
||||||
|
|
||||||
import { type IExpressionParseDTO, ParsingStatus } from '../../../backend/types';
|
import { type IExpressionParseDTO, ParsingStatus } from '../../../backend/types';
|
||||||
|
@ -21,7 +22,7 @@ interface StatusBarProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
processing: boolean;
|
processing: boolean;
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
parseData: IExpressionParseDTO | null;
|
parseData: RO<IExpressionParseDTO> | null;
|
||||||
activeCst: IConstituenta;
|
activeCst: IConstituenta;
|
||||||
onAnalyze: () => void;
|
onAnalyze: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ export function MenuEditSchema() {
|
||||||
cstID: targetCst.id
|
cstID: targetCst.id
|
||||||
}).then(cstList => {
|
}).then(cstList => {
|
||||||
if (cstList.length !== 0) {
|
if (cstList.length !== 0) {
|
||||||
setSelected(cstList);
|
setSelected([...cstList]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { useModificationStore } from '@/stores/modification';
|
import { useModificationStore } from '@/stores/modification';
|
||||||
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
||||||
import { infoMsg, tooltipText } from '@/utils/labels';
|
import { infoMsg, tooltipText } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
|
import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
|
||||||
|
|
||||||
import { useDownloadRSForm } from '../../backend/use-download-rsform';
|
import { useDownloadRSForm } from '../../backend/use-download-rsform';
|
||||||
|
@ -78,9 +79,9 @@ export function MenuMain() {
|
||||||
void download({
|
void download({
|
||||||
itemID: schema.id,
|
itemID: schema.id,
|
||||||
version: schema.version === 'latest' ? undefined : schema.version
|
version: schema.version === 'latest' ? undefined : schema.version
|
||||||
}).then((data: Blob) => {
|
}).then((data: RO<Blob>) => {
|
||||||
try {
|
try {
|
||||||
fileDownload(data, fileName);
|
fileDownload(data as Blob, fileName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { useModificationStore } from '@/stores/modification';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||||
import { promptText } from '@/utils/labels';
|
import { promptText } from '@/utils/labels';
|
||||||
|
import { type RO } from '@/utils/meta';
|
||||||
import { promptUnsaved } from '@/utils/utils';
|
import { promptUnsaved } from '@/utils/utils';
|
||||||
|
|
||||||
import { CstType, type IConstituentaBasicsDTO, type ICreateConstituentaDTO } from '../../backend/types';
|
import { CstType, type IConstituentaBasicsDTO, type ICreateConstituentaDTO } from '../../backend/types';
|
||||||
|
@ -136,7 +137,7 @@ export const RSEditState = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCreateCst(newCst: IConstituentaBasicsDTO) {
|
function onCreateCst(newCst: RO<IConstituentaBasicsDTO>) {
|
||||||
setSelected([newCst.id]);
|
setSelected([newCst.id]);
|
||||||
navigateRSForm({ tab: activeTab, activeID: newCst.id });
|
navigateRSForm({ tab: activeTab, activeID: newCst.id });
|
||||||
if (activeTab === RSTabID.CST_LIST) {
|
if (activeTab === RSTabID.CST_LIST) {
|
||||||
|
|
83
rsconcept/frontend/src/utils/meta.ts
Normal file
83
rsconcept/frontend/src/utils/meta.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* Module: Generic high order utility functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// List of disallowed object types (non-plain)
|
||||||
|
// ALLOW: Date | RegExp | ArrayBuffer | DataView
|
||||||
|
type DisallowedObjects =
|
||||||
|
| Map<unknown, unknown>
|
||||||
|
| Set<unknown>
|
||||||
|
| WeakMap<object, unknown>
|
||||||
|
| WeakSet<object>
|
||||||
|
| Promise<unknown>;
|
||||||
|
|
||||||
|
// Detects any disallowed object type directly
|
||||||
|
type IsDisallowedObject<T> = T extends DisallowedObjects ? true : false;
|
||||||
|
|
||||||
|
// Detects if any property in T is a function
|
||||||
|
type HasFunctionProps<T> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? true : false;
|
||||||
|
}[keyof T] extends true
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Detects if any property in T is a disallowed object
|
||||||
|
type HasDisallowedProps<T> = {
|
||||||
|
[K in keyof T]: IsDisallowedObject<T[K]>;
|
||||||
|
}[keyof T] extends true
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Detects if T is a class instance (has constructor)
|
||||||
|
type IsClassInstance<T> = T extends object
|
||||||
|
? T extends { constructor: new (...args: unknown[]) => unknown }
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// The final check — should the object be rejected?
|
||||||
|
type IsInvalid<T> = T extends object
|
||||||
|
? HasFunctionProps<T> extends true
|
||||||
|
? true
|
||||||
|
: HasDisallowedProps<T> extends true
|
||||||
|
? true
|
||||||
|
: IsClassInstance<T> extends true
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply readonly modifier to all properties of a SIMPLE object recursively.
|
||||||
|
* only works for arrays, dictionaries and primitives.
|
||||||
|
*/
|
||||||
|
export type RO<T> = IsInvalid<T> extends true
|
||||||
|
? never
|
||||||
|
: T extends readonly unknown[]
|
||||||
|
? readonly RO<T[number]>[]
|
||||||
|
: T extends object
|
||||||
|
? { readonly [K in keyof T]: RO<T[K]> }
|
||||||
|
: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freeze an object.
|
||||||
|
*/
|
||||||
|
export function deepFreeze<T>(obj: T): RO<T> {
|
||||||
|
// Ensure the input object is not null or undefined
|
||||||
|
if (obj === null || obj === undefined) {
|
||||||
|
throw new Error('Cannot freeze null or undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Freeze the current object
|
||||||
|
Object.freeze(obj);
|
||||||
|
|
||||||
|
// Freeze properties recursively
|
||||||
|
Object.getOwnPropertyNames(obj).forEach(prop => {
|
||||||
|
const value = (obj as Record<string, unknown>)[prop];
|
||||||
|
if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
|
||||||
|
deepFreeze(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the frozen object
|
||||||
|
return obj as RO<T>;
|
||||||
|
}
|
|
@ -128,7 +128,7 @@ export function extractErrorMessage(error: Error | AxiosError): string {
|
||||||
/**
|
/**
|
||||||
* Convert array of objects to CSV Blob.
|
* Convert array of objects to CSV Blob.
|
||||||
*/
|
*/
|
||||||
export function convertToCSV(targetObj: object[]): Blob {
|
export function convertToCSV(targetObj: readonly object[]): Blob {
|
||||||
if (!targetObj || targetObj.length === 0) {
|
if (!targetObj || targetObj.length === 0) {
|
||||||
return new Blob([], { type: 'text/csv;charset=utf-8;' });
|
return new Blob([], { type: 'text/csv;charset=utf-8;' });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user