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 { PARAMETER } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { extractErrorMessage } from '@/utils/utils';
|
||||
|
||||
export { AxiosError } from 'axios';
|
||||
|
@ -58,7 +59,7 @@ export function axiosGet<ResponseData>({ endpoint, options, schema }: IAxiosGetR
|
|||
.get<ResponseData>(endpoint, options)
|
||||
.then(response => {
|
||||
schema?.parse(response.data);
|
||||
return response.data;
|
||||
return response.data as RO<ResponseData>;
|
||||
})
|
||||
.catch((error: Error | AxiosError) => {
|
||||
// Note: Ignore cancellation errors
|
||||
|
@ -81,7 +82,7 @@ export function axiosPost<RequestData, ResponseData = void>({
|
|||
.then(response => {
|
||||
schema?.parse(response.data);
|
||||
notifySuccess(response.data, request?.successMessage);
|
||||
return response.data;
|
||||
return response.data as RO<ResponseData>;
|
||||
})
|
||||
.catch((error: Error | AxiosError | ZodError) => {
|
||||
notifyError(error);
|
||||
|
@ -100,7 +101,7 @@ export function axiosDelete<RequestData, ResponseData = void>({
|
|||
.then(response => {
|
||||
schema?.parse(response.data);
|
||||
notifySuccess(response.data, request?.successMessage);
|
||||
return response.data;
|
||||
return response.data as RO<ResponseData>;
|
||||
})
|
||||
.catch((error: Error | AxiosError | ZodError) => {
|
||||
notifyError(error);
|
||||
|
@ -119,7 +120,7 @@ export function axiosPatch<RequestData, ResponseData = void>({
|
|||
.then(response => {
|
||||
schema?.parse(response.data);
|
||||
notifySuccess(response.data, request?.successMessage);
|
||||
return response.data;
|
||||
return response.data as RO<ResponseData>;
|
||||
})
|
||||
.catch((error: Error | AxiosError | ZodError) => {
|
||||
notifyError(error);
|
||||
|
|
|
@ -11,7 +11,7 @@ import { cn } from '../utils';
|
|||
|
||||
interface ComboBoxProps<Option> extends Styling {
|
||||
id?: string;
|
||||
items?: Option[];
|
||||
items?: readonly Option[];
|
||||
value: Option | null;
|
||||
onChange: (newValue: Option | null) => void;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
|||
import { type IRSFormDTO } from '@/features/rsform';
|
||||
|
||||
import { KEYS } from '@/backend/configuration';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
import { type AccessPolicy, type ILibraryItem } from './types';
|
||||
|
@ -38,7 +39,7 @@ export const useSetAccessPolicy = () => {
|
|||
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
||||
!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))
|
||||
);
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
|||
import { type IRSFormDTO } from '@/features/rsform';
|
||||
|
||||
import { KEYS } from '@/backend/configuration';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
import { type ILibraryItem } from './types';
|
||||
|
@ -38,7 +39,7 @@ export const useSetLocation = () => {
|
|||
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
||||
!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))
|
||||
);
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
|||
import { type IRSFormDTO } from '@/features/rsform';
|
||||
|
||||
import { KEYS } from '@/backend/configuration';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
import { type ILibraryItem } from './types';
|
||||
|
@ -38,7 +39,7 @@ export const useSetOwner = () => {
|
|||
client.setQueryData(rsKey, (prev: IRSFormDTO | undefined) =>
|
||||
!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))
|
||||
);
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import { type IOperationSchemaDTO } from '@/features/oss';
|
|||
import { type IRSFormDTO } from '@/features/rsform';
|
||||
|
||||
import { KEYS } from '@/backend/configuration';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { libraryApi } from './api';
|
||||
import { type ILibraryItem, type IUpdateLibraryItemDTO, LibraryItemType } from './types';
|
||||
|
@ -20,7 +21,7 @@ export const useUpdateItem = () => {
|
|||
data.item_type === LibraryItemType.RSFORM
|
||||
? KEYS.composite.rsItem({ 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))
|
||||
);
|
||||
client.setQueryData(itemKey, (prev: IRSFormDTO | IOperationSchemaDTO | undefined) =>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type ILibraryItem } from './types';
|
||||
import { useLibraryListKey } from './use-library';
|
||||
|
||||
|
@ -10,7 +12,7 @@ export function useUpdateTimestamp() {
|
|||
updateTimestamp: (target: number) =>
|
||||
client.setQueryData(
|
||||
libraryKey, //
|
||||
(prev: ILibraryItem[] | undefined) =>
|
||||
(prev: RO<ILibraryItem[]> | undefined) =>
|
||||
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 { useFitHeight } from '@/stores/app-layout';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type ILibraryItem, LibraryItemType } from '../../backend/types';
|
||||
import { useLibrarySearchStore } from '../../stores/library-search';
|
||||
|
@ -16,7 +17,7 @@ import { useLibrarySearchStore } from '../../stores/library-search';
|
|||
import { useLibraryColumns } from './use-library-columns';
|
||||
|
||||
interface TableLibraryItemsProps {
|
||||
items: ILibraryItem[];
|
||||
items: RO<ILibraryItem[]>;
|
||||
}
|
||||
|
||||
export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
||||
|
@ -55,7 +56,7 @@ export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
|||
<DataTable
|
||||
id='library_data'
|
||||
columns={columns}
|
||||
data={items}
|
||||
data={items as ILibraryItem[]}
|
||||
headPosition='0'
|
||||
className={clsx('cc-scroll-y h-fit text-xs sm:text-sm border-b', folderMode && 'border-l')}
|
||||
style={{ maxHeight: tableHeight }}
|
||||
|
|
|
@ -6,12 +6,13 @@ import { MiniButton } from '@/components/control';
|
|||
import { createColumnHelper } from '@/components/data-table';
|
||||
import { IconFolderTree } from '@/components/icons';
|
||||
import { useWindowSize } from '@/hooks/use-window-size';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type ILibraryItem } from '../../backend/types';
|
||||
import { BadgeLocation } from '../../components/badge-location';
|
||||
import { useLibrarySearchStore } from '../../stores/library-search';
|
||||
|
||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||
const columnHelper = createColumnHelper<RO<ILibraryItem>>();
|
||||
|
||||
export function useLibraryColumns() {
|
||||
const { isSmall } = useWindowSize();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import { type ILibraryItem } from '@/features/library';
|
||||
|
||||
import { Graph } from '@/models/graph';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
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';
|
||||
|
@ -19,9 +20,9 @@ export class OssLoader {
|
|||
private operationByID = new Map<number, IOperation>();
|
||||
private blockByID = new Map<number, IBlock>();
|
||||
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.items = items;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||
|
||||
import { KEYS } from '@/backend/configuration';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { ossApi } from './api';
|
||||
import { type IOperationSchemaDTO, type IOssLayout } from './types';
|
||||
|
@ -17,7 +18,7 @@ export const useUpdateLayout = () => {
|
|||
updateTimestamp(variables.itemID);
|
||||
client.setQueryData(
|
||||
ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey,
|
||||
(prev: IOperationSchemaDTO | undefined) =>
|
||||
(prev: RO<IOperationSchemaDTO> | undefined) =>
|
||||
!prev
|
||||
? prev
|
||||
: {
|
||||
|
|
|
@ -9,9 +9,10 @@ import { ComboBox } from '@/components/input/combo-box';
|
|||
import { type Styling } from '@/components/props';
|
||||
import { cn } from '@/components/utils';
|
||||
import { NoData } from '@/components/view';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
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';
|
||||
|
||||
const SELECTION_CLEAR_TIMEOUT = 1000;
|
||||
|
@ -25,7 +26,7 @@ interface PickMultiOperationProps extends Styling {
|
|||
disallowBlocks?: boolean;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IOperation | IBlock>();
|
||||
const columnHelper = createColumnHelper<RO<IOssItem>>();
|
||||
|
||||
export function PickContents({
|
||||
rows,
|
||||
|
@ -40,7 +41,7 @@ export function PickContents({
|
|||
const selectedItems = value
|
||||
.map(itemID => (itemID > 0 ? schema.operationByID.get(itemID) : schema.blockByID.get(-itemID)))
|
||||
.filter(item => item !== undefined);
|
||||
const [lastSelected, setLastSelected] = useState<IOperation | IBlock | null>(null);
|
||||
const [lastSelected, setLastSelected] = useState<RO<IOssItem> | null>(null);
|
||||
const items = [
|
||||
...(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))
|
||||
|
@ -50,7 +51,7 @@ export function PickContents({
|
|||
onChange(value.filter(item => item !== target));
|
||||
}
|
||||
|
||||
function handleSelect(target: IOperation | IBlock | null) {
|
||||
function handleSelect(target: RO<IOssItem> | null) {
|
||||
if (target) {
|
||||
setLastSelected(target);
|
||||
onChange([...value, getItemID(target)]);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { OperationType } from './backend/types';
|
||||
import {
|
||||
type IOperation,
|
||||
|
@ -26,7 +28,7 @@ export function describeOperationType(itemType: OperationType): string {
|
|||
}
|
||||
|
||||
/** Generates error description for {@link ISubstitutionErrorDescription}. */
|
||||
export function describeSubstitutionError(error: ISubstitutionErrorDescription): string {
|
||||
export function describeSubstitutionError(error: RO<ISubstitutionErrorDescription>): string {
|
||||
switch (error.errorType) {
|
||||
case SubstitutionErrorType.invalidIDs:
|
||||
return 'Ошибка в идентификаторах схем';
|
||||
|
@ -55,7 +57,7 @@ export function describeSubstitutionError(error: ISubstitutionErrorDescription):
|
|||
}
|
||||
|
||||
/** Retrieves label for {@link IOssItem}. */
|
||||
export function labelOssItem(item: IOssItem): string {
|
||||
export function labelOssItem(item: RO<IOssItem>): string {
|
||||
if (isOperation(item)) {
|
||||
return `${(item as IOperation).alias}: ${item.title}`;
|
||||
} else {
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '@/features/rsform/models/rslang-api';
|
||||
|
||||
import { infoMsg } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { Graph } from '../../../models/graph';
|
||||
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
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
for (const item of items) {
|
||||
if (item.visible && item.owner === oss.owner && !result.includes(item)) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import { Graph } from '@/models/graph';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform';
|
||||
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '../models/rsform-api';
|
||||
|
@ -23,7 +24,7 @@ export class RSFormLoader {
|
|||
private cstByAlias = new Map<string, IConstituenta>();
|
||||
private cstByID = new Map<number, IConstituenta>();
|
||||
|
||||
constructor(input: IRSFormDTO) {
|
||||
constructor(input: RO<IRSFormDTO>) {
|
||||
this.schema = structuredClone(input) as IRSForm;
|
||||
this.schema.version = input.version ?? 'latest';
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||
import { ModalForm } from '@/components/modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import {
|
||||
type IConstituentaBasicsDTO,
|
||||
|
@ -21,7 +22,7 @@ import { FormCreateCst } from './form-create-cst';
|
|||
export interface DlgCreateCstProps {
|
||||
initial: ICreateConstituentaDTO;
|
||||
schema: IRSForm;
|
||||
onCreate: (data: IConstituentaBasicsDTO) => void;
|
||||
onCreate: (data: RO<IConstituentaBasicsDTO>) => void;
|
||||
}
|
||||
|
||||
export function DlgCreateCst() {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Loader } from '@/components/loader';
|
|||
import { ModalForm } from '@/components/modal';
|
||||
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import {
|
||||
CstType,
|
||||
|
@ -28,7 +29,7 @@ import { TemplateState } from './template-state';
|
|||
|
||||
export interface DlgCstTemplateProps {
|
||||
schema: IRSForm;
|
||||
onCreate: (data: IConstituentaBasicsDTO) => void;
|
||||
onCreate: (data: RO<IConstituentaBasicsDTO>) => void;
|
||||
insertAfter?: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect } from 'react';
|
|||
import { type Edge, MarkerType, type Node, useEdgesState, useNodesState } from 'reactflow';
|
||||
|
||||
import { DiagramFlow } from '@/components/flow/diagram-flow';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type SyntaxTree } from '../../models/rslang';
|
||||
|
||||
|
@ -22,7 +23,7 @@ const flowOptions = {
|
|||
} as const;
|
||||
|
||||
interface ASTFlowProps {
|
||||
data: SyntaxTree;
|
||||
data: RO<SyntaxTree>;
|
||||
onNodeEnter: (node: Node) => void;
|
||||
onNodeLeave: (node: Node) => void;
|
||||
onChangeDragging: (value: boolean) => void;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { HelpTopic } from '@/features/help';
|
|||
|
||||
import { ModalView } from '@/components/modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type SyntaxTree } from '../../models/rslang';
|
||||
|
||||
|
@ -17,7 +18,7 @@ import { ASTFlow } from './ast-flow';
|
|||
const NODE_POPUP_DELAY = 100;
|
||||
|
||||
export interface DlgShowASTProps {
|
||||
syntaxTree: SyntaxTree;
|
||||
syntaxTree: RO<SyntaxTree>;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { HelpTopic } from '@/features/help';
|
|||
import { ModalView } from '@/components/modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type ITypeInfo } from '../../models/rslang';
|
||||
import { TypificationGraph } from '../../models/typification-graph';
|
||||
|
@ -15,7 +16,7 @@ import { TypificationGraph } from '../../models/typification-graph';
|
|||
import { MGraphFlow } from './mgraph-flow';
|
||||
|
||||
export interface DlgShowTypeGraphProps {
|
||||
items: ITypeInfo[];
|
||||
items: RO<ITypeInfo[]>;
|
||||
}
|
||||
|
||||
export function DlgShowTypeGraph() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { prepareTooltip } from '@/utils/utils';
|
||||
|
||||
import { type IVersionInfo } from '../library/backend/types';
|
||||
|
@ -18,7 +19,7 @@ import { type GraphColoring } from './stores/term-graph';
|
|||
/**
|
||||
* Generates description for {@link IConstituenta}.
|
||||
*/
|
||||
export function describeConstituenta(cst: IConstituenta): string {
|
||||
export function describeConstituenta(cst: RO<IConstituenta>): string {
|
||||
if (cst.cst_type === CstType.STRUCTURED) {
|
||||
return (
|
||||
cst.term_resolved ||
|
||||
|
@ -43,7 +44,7 @@ export function describeConstituenta(cst: IConstituenta): string {
|
|||
/**
|
||||
* 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) {
|
||||
return '!Конституента отсутствует!';
|
||||
}
|
||||
|
@ -57,14 +58,14 @@ export function describeConstituentaTerm(cst: IConstituenta | null): string {
|
|||
/**
|
||||
* Generates label for {@link IConstituenta}.
|
||||
*/
|
||||
export function labelConstituenta(cst: IConstituenta) {
|
||||
export function labelConstituenta(cst: RO<IConstituenta>) {
|
||||
return `${cst.alias}: ${describeConstituenta(cst)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return version ? version.version : 'актуальная';
|
||||
}
|
||||
|
@ -345,7 +346,7 @@ export function labelTypification({
|
|||
}: {
|
||||
isValid: boolean;
|
||||
resultType: string;
|
||||
args: IArgumentInfo[];
|
||||
args: RO<IArgumentInfo[]>;
|
||||
}): string {
|
||||
if (!isValid) {
|
||||
return 'N/A';
|
||||
|
@ -363,7 +364,7 @@ export function labelTypification({
|
|||
/**
|
||||
* Generates label for {@link IConstituenta} typification.
|
||||
*/
|
||||
export function labelCstTypification(cst: IConstituenta): string {
|
||||
export function labelCstTypification(cst: RO<IConstituenta>): string {
|
||||
return labelTypification({
|
||||
isValid: cst.parse.status === ParsingStatus.VERIFIED,
|
||||
resultType: cst.parse.typification,
|
||||
|
@ -374,7 +375,7 @@ export function labelCstTypification(cst: IConstituenta): string {
|
|||
/**
|
||||
* Generates label for {@link ISyntaxTreeNode}.
|
||||
*/
|
||||
export function labelSyntaxTree(node: ISyntaxTreeNode): string {
|
||||
export function labelSyntaxTree(node: RO<ISyntaxTreeNode>): string {
|
||||
// prettier-ignore
|
||||
switch (node.typeID) {
|
||||
case TokenID.ID_LOCAL:
|
||||
|
@ -531,7 +532,7 @@ export function labelGrammeme(gram: Grammeme): string {
|
|||
/**
|
||||
* Generates error description for {@link IRSErrorDescription}.
|
||||
*/
|
||||
export function describeRSError(error: IRSErrorDescription): string {
|
||||
export function describeRSError(error: RO<IRSErrorDescription>): string {
|
||||
// prettier-ignore
|
||||
switch (error.errorType) {
|
||||
case RSErrorType.unknownSymbol:
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import { BASIC_SCHEMAS, type ILibraryItem } from '@/features/library';
|
||||
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { TextMatcher } from '@/utils/utils';
|
||||
|
||||
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 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);
|
||||
if ((mode === CstMatchMode.ALL || mode === CstMatchMode.NAME) && matcher.test(target.alias)) {
|
||||
return true;
|
||||
|
@ -214,7 +215,7 @@ export function isFunctional(type: CstType): boolean {
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
@ -271,7 +272,7 @@ export function generateAlias(type: CstType, schema: IRSForm, takenAliases: stri
|
|||
/**
|
||||
* 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);
|
||||
for (const item of items) {
|
||||
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 { PARAMETER } from '@/utils/constants';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { CstType, type IRSErrorDescription, type RSErrorType } from '../backend/types';
|
||||
import { labelCstTypification } from '../labels';
|
||||
|
@ -36,7 +37,7 @@ export function isSetTypification(text: string): boolean {
|
|||
}
|
||||
|
||||
/** 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)) {
|
||||
return templateType;
|
||||
} 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
|
||||
* 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)) {
|
||||
return expression;
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ export function substituteTemplateArgs(expression: string, args: IArgumentValue[
|
|||
/**
|
||||
* Generate ErrorID label.
|
||||
*/
|
||||
export function getRSErrorPrefix(error: IRSErrorDescription): string {
|
||||
export function getRSErrorPrefix(error: RO<IRSErrorDescription>): string {
|
||||
const id = error.errorType.toString(16);
|
||||
// prettier-ignore
|
||||
switch(inferErrorClass(error.errorType)) {
|
||||
|
@ -133,12 +134,12 @@ export function getRSErrorPrefix(error: IRSErrorDescription): string {
|
|||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
|
||||
/** 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);
|
||||
if (modified === target) {
|
||||
return target;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type IArgumentInfo } from './rslang';
|
||||
|
||||
|
@ -35,7 +36,7 @@ export class TypificationGraph {
|
|||
* @param result - typification of the formal definition.
|
||||
* @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 resultNode = this.processResult(result);
|
||||
const combinedNode = this.combineResults(resultNode, argsNode);
|
||||
|
@ -122,7 +123,7 @@ export class TypificationGraph {
|
|||
this.nodeByAlias.set(alias, nodeToAnnotate);
|
||||
}
|
||||
|
||||
private processArguments(args: IArgumentInfo[]): TypificationGraphNode | null {
|
||||
private processArguments(args: RO<IArgumentInfo[]>): TypificationGraphNode | null {
|
||||
if (args.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Indicator } from '@/components/view';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
import { errorMsg, tooltipText } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { promptUnsaved } from '@/utils/utils';
|
||||
|
||||
import {
|
||||
|
@ -68,7 +69,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
}
|
||||
});
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
const [localParse, setLocalParse] = useState<IExpressionParseDTO | null>(null);
|
||||
const [localParse, setLocalParse] = useState<RO<IExpressionParseDTO> | null>(null);
|
||||
|
||||
const typification = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useResetOnChange } from '@/hooks/use-reset-on-change';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import {
|
||||
type ICheckConstituentaDTO,
|
||||
|
@ -42,7 +43,7 @@ interface EditorRSExpressionProps {
|
|||
disabled?: boolean;
|
||||
toggleReset?: boolean;
|
||||
|
||||
onChangeLocalParse: (typification: IExpressionParseDTO) => void;
|
||||
onChangeLocalParse: (typification: RO<IExpressionParseDTO>) => void;
|
||||
onOpenEdit: (cstID: number) => void;
|
||||
onShowTypeGraph: (event: React.MouseEvent<Element>) => void;
|
||||
}
|
||||
|
@ -62,7 +63,7 @@ export function EditorRSExpression({
|
|||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const rsInput = useRef<ReactCodeMirrorRef>(null);
|
||||
const [parseData, setParseData] = useState<IExpressionParseDTO | null>(null);
|
||||
const [parseData, setParseData] = useState<RO<IExpressionParseDTO> | null>(null);
|
||||
|
||||
const isProcessing = useMutatingRSForm();
|
||||
const showControls = usePreferencesStore(state => state.showExpressionControls);
|
||||
|
@ -78,7 +79,7 @@ export function EditorRSExpression({
|
|||
function checkConstituenta(
|
||||
expression: string,
|
||||
activeCst: IConstituenta,
|
||||
onSuccess?: (data: IExpressionParseDTO) => void
|
||||
onSuccess?: (data: RO<IExpressionParseDTO>) => void
|
||||
) {
|
||||
const data: ICheckConstituentaDTO = {
|
||||
definition_formal: expression,
|
||||
|
@ -96,7 +97,7 @@ export function EditorRSExpression({
|
|||
setIsModified(newValue !== activeCst.definition_formal);
|
||||
}
|
||||
|
||||
function handleCheckExpression(callback?: (parse: IExpressionParseDTO) => void) {
|
||||
function handleCheckExpression(callback?: (parse: RO<IExpressionParseDTO>) => void) {
|
||||
checkConstituenta(value, activeCst, parse => {
|
||||
onChangeLocalParse(parse);
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type IExpressionParseDTO, type IRSErrorDescription } from '../../../backend/types';
|
||||
import { describeRSError } from '../../../labels';
|
||||
import { getRSErrorPrefix } from '../../../models/rslang-api';
|
||||
|
||||
interface ParsingResultProps {
|
||||
data: IExpressionParseDTO | null;
|
||||
data: RO<IExpressionParseDTO> | null;
|
||||
disabled?: boolean;
|
||||
isOpen: boolean;
|
||||
onShowError: (error: IRSErrorDescription) => void;
|
||||
onShowError: (error: RO<IRSErrorDescription>) => void;
|
||||
}
|
||||
|
||||
export function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultProps) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { BadgeHelp } from '@/features/help/components';
|
|||
import { Loader } from '@/components/loader';
|
||||
import { APP_COLORS } from '@/styling/colors';
|
||||
import { globalIDs } from '@/utils/constants';
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { prepareTooltip } from '@/utils/utils';
|
||||
|
||||
import { type IExpressionParseDTO, ParsingStatus } from '../../../backend/types';
|
||||
|
@ -21,7 +22,7 @@ interface StatusBarProps {
|
|||
className?: string;
|
||||
processing: boolean;
|
||||
isModified: boolean;
|
||||
parseData: IExpressionParseDTO | null;
|
||||
parseData: RO<IExpressionParseDTO> | null;
|
||||
activeCst: IConstituenta;
|
||||
onAnalyze: () => void;
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ export function MenuEditSchema() {
|
|||
cstID: targetCst.id
|
||||
}).then(cstList => {
|
||||
if (cstList.length !== 0) {
|
||||
setSelected(cstList);
|
||||
setSelected([...cstList]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { useModificationStore } from '@/stores/modification';
|
||||
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
||||
import { infoMsg, tooltipText } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
|
||||
|
||||
import { useDownloadRSForm } from '../../backend/use-download-rsform';
|
||||
|
@ -78,9 +79,9 @@ export function MenuMain() {
|
|||
void download({
|
||||
itemID: schema.id,
|
||||
version: schema.version === 'latest' ? undefined : schema.version
|
||||
}).then((data: Blob) => {
|
||||
}).then((data: RO<Blob>) => {
|
||||
try {
|
||||
fileDownload(data, fileName);
|
||||
fileDownload(data as Blob, fileName);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useModificationStore } from '@/stores/modification';
|
|||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { promptText } from '@/utils/labels';
|
||||
import { type RO } from '@/utils/meta';
|
||||
import { promptUnsaved } from '@/utils/utils';
|
||||
|
||||
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]);
|
||||
navigateRSForm({ tab: activeTab, activeID: newCst.id });
|
||||
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.
|
||||
*/
|
||||
export function convertToCSV(targetObj: object[]): Blob {
|
||||
export function convertToCSV(targetObj: readonly object[]): Blob {
|
||||
if (!targetObj || targetObj.length === 0) {
|
||||
return new Blob([], { type: 'text/csv;charset=utf-8;' });
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user