F: Implement schema checks for backend + small fixes
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run

This commit is contained in:
Ivan 2025-02-18 23:38:33 +03:00
parent a34af217eb
commit ab9f058b0a
32 changed files with 309 additions and 302 deletions

View File

@ -27,7 +27,6 @@ function LoginPage() {
register,
handleSubmit,
clearErrors,
resetField,
formState: { errors }
} = useForm({
resolver: zodResolver(schemaUserLogin),
@ -39,7 +38,6 @@ function LoginPage() {
function onSubmit(data: IUserLoginDTO) {
return login(data).then(() => {
resetField('password');
if (router.canBack()) {
router.back();
} else {

View File

@ -1,6 +1,11 @@
import { queryOptions } from '@tanstack/react-query';
import { IRSFormDTO } from '@/features/rsform/backend/types';
import {
IRSFormDTO,
IVersionCreatedResponse,
schemaRSForm,
schemaVersionCreatedResponse
} from '@/features/rsform/backend/types';
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS, KEYS } from '@/backend/configuration';
@ -13,10 +18,12 @@ import {
ILibraryItem,
IRenameLocationDTO,
IUpdateLibraryItemDTO,
IVersionCreatedResponse,
IVersionCreateDTO,
IVersionInfo,
IVersionUpdateDTO
IVersionUpdateDTO,
schemaLibraryItem,
schemaLibraryItemArray,
schemaVersionInfo
} from './types';
export const libraryApi = {
@ -29,6 +36,7 @@ export const libraryApi = {
staleTime: DELAYS.staleMedium,
queryFn: meta =>
axiosGet<ILibraryItem[]>({
schema: schemaLibraryItemArray,
endpoint: isAdmin ? '/api/library/all' : '/api/library/active',
options: { signal: meta.signal }
})
@ -39,6 +47,7 @@ export const libraryApi = {
staleTime: DELAYS.staleMedium,
queryFn: meta =>
axiosGet<ILibraryItem[]>({
schema: schemaLibraryItemArray,
endpoint: '/api/library/templates',
options: { signal: meta.signal }
})
@ -46,6 +55,7 @@ export const libraryApi = {
createItem: (data: ICreateLibraryItemDTO) =>
axiosPost<ICreateLibraryItemDTO, ILibraryItem>({
schema: schemaLibraryItem,
endpoint: !data.file ? '/api/library' : '/api/rsforms/create-detailed',
request: {
data: data,
@ -61,6 +71,7 @@ export const libraryApi = {
}),
updateItem: (data: IUpdateLibraryItemDTO) =>
axiosPatch<IUpdateLibraryItemDTO, ILibraryItem>({
schema: schemaLibraryItem,
endpoint: `/api/library/${data.id}`,
request: {
data: data,
@ -109,6 +120,7 @@ export const libraryApi = {
}),
cloneItem: (data: ICloneLibraryItemDTO) =>
axiosPost<ICloneLibraryItemDTO, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/library/${data.id}/clone`,
request: {
data: data,
@ -126,6 +138,7 @@ export const libraryApi = {
versionCreate: ({ itemID, data }: { itemID: number; data: IVersionCreateDTO }) =>
axiosPost<IVersionCreateDTO, IVersionCreatedResponse>({
schema: schemaVersionCreatedResponse,
endpoint: `/api/library/${itemID}/create-version`,
request: {
data: data,
@ -134,6 +147,7 @@ export const libraryApi = {
}),
versionRestore: ({ versionID }: { versionID: number }) =>
axiosPatch<undefined, IRSFormDTO>({
schema: schemaRSForm,
endpoint: `/api/versions/${versionID}/restore`,
request: {
successMessage: infoMsg.versionRestored
@ -141,6 +155,7 @@ export const libraryApi = {
}),
versionUpdate: (data: { itemID: number; version: IVersionUpdateDTO }) =>
axiosPatch<IVersionUpdateDTO, IVersionInfo>({
schema: schemaVersionInfo,
endpoint: `/api/versions/${data.version.id}`,
request: {
data: data.version,

View File

@ -1,7 +1,5 @@
import { z } from 'zod';
import { IRSFormDTO } from '@/features/rsform/backend/types';
import { errorMsg } from '@/utils/labels';
import { validateLocation } from '../models/libraryAPI';
@ -19,34 +17,15 @@ export enum AccessPolicy {
PRIVATE = 'private'
}
/**
* Represents library item common data typical for all item types.
*/
export interface ILibraryItem {
id: number;
item_type: LibraryItemType;
title: string;
alias: string;
comment: string;
visible: boolean;
read_only: boolean;
location: string;
access_policy: AccessPolicy;
time_create: string;
time_update: string;
owner: number | null;
}
/** Represents library item common data typical for all item types. */
export type ILibraryItem = z.infer<typeof schemaLibraryItem>;
/**
* Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
*/
/** Represents {@link ILibraryItem} data loaded for both OSS and RSForm. */
export interface ILibraryItemData extends ILibraryItem {
editors: number[];
}
/**
* Represents update data for renaming Location.
*/
/** Represents update data for renaming Location. */
export interface IRenameLocationDTO {
target: string;
new_location: string;
@ -61,65 +40,55 @@ export type ICloneLibraryItemDTO = z.infer<typeof schemaCloneLibraryItem>;
/** Represents data, used for creating {@link IRSForm}. */
export type ICreateLibraryItemDTO = z.infer<typeof schemaCreateLibraryItem>;
/**
* Represents update data for editing {@link ILibraryItem}.
*/
export const schemaUpdateLibraryItem = z.object({
id: z.number(),
item_type: z.nativeEnum(LibraryItemType),
title: z.string().nonempty(errorMsg.requiredField),
alias: z.string().nonempty(errorMsg.requiredField),
comment: z.string(),
visible: z.boolean(),
read_only: z.boolean()
});
/**
* Represents update data for editing {@link ILibraryItem}.
*/
/** Represents update data for editing {@link ILibraryItem}. */
export type IUpdateLibraryItemDTO = z.infer<typeof schemaUpdateLibraryItem>;
/**
* Create version metadata in persistent storage.
*/
export const schemaVersionCreate = z.object({
version: z.string(),
description: z.string(),
items: z.array(z.number()).optional()
});
/**
* Create version metadata in persistent storage.
*/
/** Create version metadata in persistent storage. */
export type IVersionCreateDTO = z.infer<typeof schemaVersionCreate>;
/**
* Represents data response when creating {@link IVersionInfo}.
*/
export interface IVersionCreatedResponse {
version: number;
schema: IRSFormDTO;
}
/** Represents version data, intended to update version metadata in persistent storage. */
export type IVersionUpdateDTO = z.infer<typeof schemaVersionUpdate>;
// ======= SCHEMAS =========
/** Represents data, used for cloning {@link IRSForm}. */
export const schemaCloneLibraryItem = z.object({
id: z.number(),
export const schemaLibraryItem = z.object({
id: z.coerce.number(),
item_type: z.nativeEnum(LibraryItemType),
title: z.string().nonempty(errorMsg.requiredField),
alias: z.string().nonempty(errorMsg.requiredField),
title: z.string(),
alias: z.string().nonempty(),
comment: z.string(),
visible: z.boolean(),
read_only: z.boolean(),
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation }),
location: z.string(),
access_policy: z.nativeEnum(AccessPolicy),
items: z.array(z.number()).optional()
time_create: z.string(),
time_update: z.string(),
owner: z.coerce.number().nullable()
});
export const schemaLibraryItemArray = z.array(schemaLibraryItem);
export const schemaCloneLibraryItem = schemaLibraryItem
.pick({
id: true,
item_type: true,
title: true,
alias: true,
comment: true,
visible: true,
read_only: true,
location: true,
access_policy: true
})
.extend({
title: z.string().nonempty(errorMsg.requiredField),
alias: z.string().nonempty(errorMsg.requiredField),
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation }),
items: z.array(z.number()).optional()
});
export const schemaCreateLibraryItem = z
.object({
item_type: z.nativeEnum(LibraryItemType),
@ -143,6 +112,16 @@ export const schemaCreateLibraryItem = z
message: errorMsg.requiredField
});
export const schemaUpdateLibraryItem = z.object({
id: z.number(),
item_type: z.nativeEnum(LibraryItemType),
title: z.string().nonempty(errorMsg.requiredField),
alias: z.string().nonempty(errorMsg.requiredField),
comment: z.string(),
visible: z.boolean(),
read_only: z.boolean()
});
export const schemaVersionInfo = z.object({
id: z.coerce.number(),
version: z.string(),
@ -155,3 +134,9 @@ export const schemaVersionUpdate = z.object({
version: z.string().nonempty(errorMsg.requiredField),
description: z.string()
});
export const schemaVersionCreate = z.object({
version: z.string(),
description: z.string(),
items: z.array(z.number()).optional()
});

View File

@ -6,9 +6,9 @@ import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
import { IconPrivate, IconProtected, IconPublic } from '@/components/Icons';
import { CProps } from '@/components/props';
import { prefixes } from '@/utils/constants';
import { describeAccessPolicy, labelAccessPolicy } from '@/utils/labels';
import { AccessPolicy } from '../backend/types';
import { describeAccessPolicy, labelAccessPolicy } from '../labels';
interface SelectAccessPolicyProps extends CProps.Styling {
value: AccessPolicy;

View File

@ -6,9 +6,9 @@ import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
import { IconOSS, IconRSForm } from '@/components/Icons';
import { CProps } from '@/components/props';
import { prefixes } from '@/utils/constants';
import { describeLibraryItemType, labelLibraryItemType } from '@/utils/labels';
import { LibraryItemType } from '../backend/types';
import { describeLibraryItemType, labelLibraryItemType } from '../labels';
interface SelectItemTypeProps extends CProps.Styling {
value: LibraryItemType;

View File

@ -94,7 +94,7 @@ function DlgEditVersions() {
selected={versionID}
/>
<form className='flex' onSubmit={event => void handleSubmit(onUpdate)(event)}>
<form className='flex items-center ' onSubmit={event => void handleSubmit(onUpdate)(event)}>
<TextInput
id='dlg_version'
{...register('version')}
@ -103,7 +103,7 @@ function DlgEditVersions() {
className='w-[16rem] mr-3'
error={formErrors.version}
/>
<div className='cc-icons'>
<div className='cc-icons h-fit'>
<MiniButton
type='submit'
title={isValid ? 'Сохранить изменения' : errorMsg.versionTaken}

View File

@ -1,3 +1,4 @@
import { AccessPolicy, LibraryItemType } from './backend/types';
import { FolderNode } from './models/FolderTree';
import { LocationHead } from './models/library';
import { validateLocation } from './models/libraryAPI';
@ -45,3 +46,52 @@ export function labelFolderNode(node: FolderNode): string {
export function describeFolderNode(node: FolderNode): string {
return `${node.filesInside} | ${node.filesTotal}`;
}
/**
* Retrieves label for {@link AccessPolicy}.
*/
export function labelAccessPolicy(policy: AccessPolicy): string {
// prettier-ignore
switch (policy) {
case AccessPolicy.PRIVATE: return 'Личный';
case AccessPolicy.PROTECTED: return 'Защищенный';
case AccessPolicy.PUBLIC: return 'Открытый';
}
}
/**
* Retrieves description for {@link AccessPolicy}.
*/
export function describeAccessPolicy(policy: AccessPolicy): string {
// prettier-ignore
switch (policy) {
case AccessPolicy.PRIVATE:
return 'Доступ только для владельца';
case AccessPolicy.PROTECTED:
return 'Доступ для владельца и редакторов';
case AccessPolicy.PUBLIC:
return 'Открытый доступ';
}
}
/**
* Retrieves label for {@link LibraryItemType}.
*/
export function labelLibraryItemType(itemType: LibraryItemType): string {
// prettier-ignore
switch (itemType) {
case LibraryItemType.RSFORM: return 'КС';
case LibraryItemType.OSS: return 'ОСС';
}
}
/**
* Retrieves description for {@link LibraryItemType}.
*/
export function describeLibraryItemType(itemType: LibraryItemType): string {
// prettier-ignore
switch (itemType) {
case LibraryItemType.RSFORM: return 'Концептуальная схема';
case LibraryItemType.OSS: return 'Операционная схема синтеза';
}
}

View File

@ -2,7 +2,7 @@
* Module: Models for LibraryItem.
*/
import { ILibraryItemData, IVersionInfo, LibraryItemType } from '../backend/types';
import { LibraryItemType } from '../backend/types';
/**
* Represents valid location headers.
@ -24,14 +24,6 @@ export interface ILibraryItemReference {
alias: string;
}
/**
* Represents {@link ILibraryItem} extended data with versions.
*/
export interface ILibraryItemVersioned extends ILibraryItemData {
version?: number;
versions: IVersionInfo[];
}
/**
* Represents Library filter parameters.
*/

View File

@ -6,28 +6,28 @@ import { ILibraryItem } from '@/features/library/backend/types';
import { Graph } from '@/models/Graph';
import { IOperationSchema, IOperationSchemaStats } from '../models/oss';
import { IOperation, IOperationSchema, IOperationSchemaStats } from '../models/oss';
import { IOperation, IOperationSchemaDTO, OperationType } from './types';
import { IOperationSchemaDTO, OperationType } from './types';
/**
* Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaDTO}.
*
*/
export class OssLoader {
private oss: IOperationSchemaDTO;
private oss: IOperationSchema;
private graph: Graph = new Graph();
private operationByID = new Map<number, IOperation>();
private schemaIDs: number[] = [];
private items: ILibraryItem[];
constructor(input: IOperationSchemaDTO, items: ILibraryItem[]) {
this.oss = input;
this.oss = input as unknown as IOperationSchema;
this.items = items;
}
produceOSS(): IOperationSchema {
const result = this.oss as IOperationSchema;
const result = this.oss;
this.prepareLookups();
this.createGraph();
this.extractSchemas();
@ -42,7 +42,7 @@ export class OssLoader {
private prepareLookups() {
this.oss.items.forEach(operation => {
this.operationByID.set(operation.id, operation as IOperation);
this.operationByID.set(operation.id, operation);
this.graph.addNode(operation.id);
});
}

View File

@ -17,7 +17,10 @@ import {
IOperationPosition,
IOperationSchemaDTO,
IOperationUpdateDTO,
ITargetOperation
ITargetOperation,
schemaConstituentaReference,
schemaOperationCreatedResponse,
schemaOperationSchema
} from './types';
export const ossApi = {
@ -31,6 +34,7 @@ export const ossApi = {
!itemID
? undefined
: axiosGet<IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/details`,
options: { signal: meta.signal }
})
@ -56,6 +60,7 @@ export const ossApi = {
operationCreate: ({ itemID, data }: { itemID: number; data: IOperationCreateDTO }) =>
axiosPost<IOperationCreateDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse,
endpoint: `/api/oss/${itemID}/create-operation`,
request: {
data: data,
@ -64,6 +69,7 @@ export const ossApi = {
}),
operationDelete: ({ itemID, data }: { itemID: number; data: IOperationDeleteDTO }) =>
axiosPatch<IOperationDeleteDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/delete-operation`,
request: {
data: data,
@ -80,6 +86,7 @@ export const ossApi = {
}),
inputUpdate: ({ itemID, data }: { itemID: number; data: IInputUpdateDTO }) =>
axiosPatch<IInputUpdateDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/set-input`,
request: {
data: data,
@ -88,6 +95,7 @@ export const ossApi = {
}),
operationUpdate: ({ itemID, data }: { itemID: number; data: IOperationUpdateDTO }) =>
axiosPatch<IOperationUpdateDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/update-operation`,
request: {
data: data,
@ -96,6 +104,7 @@ export const ossApi = {
}),
operationExecute: ({ itemID, data }: { itemID: number; data: ITargetOperation }) =>
axiosPost<ITargetOperation, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/execute-operation`,
request: {
data: data,
@ -105,6 +114,7 @@ export const ossApi = {
relocateConstituents: (data: ICstRelocateDTO) =>
axiosPost<ICstRelocateDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/relocate-constituents`,
request: {
data: data,
@ -113,6 +123,7 @@ export const ossApi = {
}),
getPredecessor: (data: ITargetCst) =>
axiosPost<ITargetCst, IConstituentaReference>({
schema: schemaConstituentaReference,
endpoint: '/api/oss/get-predecessor',
request: { data: data }
})

View File

@ -1,7 +1,9 @@
import { z } from 'zod';
import { ILibraryItem, ILibraryItemData } from '@/features/library/backend/types';
import { ICstSubstitute, schemaCstSubstitute } from '@/features/rsform/backend/types';
import { schemaLibraryItem } from '@/features/library/backend/types';
import { schemaCstSubstitute } from '@/features/rsform/backend/types';
import { errorMsg } from '@/utils/labels';
/**
* Represents {@link IOperation} type.
@ -11,61 +13,14 @@ export enum OperationType {
SYNTHESIS = 'synthesis'
}
/**
* Represents {@link ICstSubstitute} extended data.
*/
export interface ICstSubstituteEx extends ICstSubstitute {
operation: number;
original_alias: string;
original_term: string;
substitution_alias: string;
substitution_term: string;
}
/** Represents {@link ICstSubstitute} extended data. */
export type ICstSubstituteInfo = z.infer<typeof schemaCstSubstituteInfo>;
/**
* Represents Operation.
*/
export interface IOperation {
id: number;
operation_type: OperationType;
oss: number;
/** Represents {@link IOperation} data from server. */
export type IOperationDTO = z.infer<typeof schemaOperation>;
alias: string;
title: string;
comment: string;
position_x: number;
position_y: number;
result: number | null;
is_owned: boolean;
is_consolidation: boolean; // aka 'diamond synthesis'
substitutions: ICstSubstituteEx[];
arguments: number[];
}
/**
* Represents {@link IOperation} Argument.
*/
export interface IArgument {
operation: number;
argument: number;
}
/**
* Represents {@link IOperation} data from server.
*/
export interface IOperationDTO extends Omit<IOperation, 'substitutions' | 'arguments'> {}
/**
* Represents backend data for {@link IOperationSchema}.
*/
export interface IOperationSchemaDTO extends ILibraryItemData {
items: IOperationDTO[];
arguments: IArgument[];
substitutions: ICstSubstituteEx[];
}
/** Represents backend data for {@link IOperationSchema}. */
export type IOperationSchemaDTO = z.infer<typeof schemaOperationSchema>;
/** Represents {@link IOperation} position. */
export type IOperationPosition = z.infer<typeof schemaOperationPosition>;
@ -73,14 +28,8 @@ export type IOperationPosition = z.infer<typeof schemaOperationPosition>;
/** Represents {@link IOperation} data, used in creation process. */
export type IOperationCreateDTO = z.infer<typeof schemaOperationCreate>;
/**
* Represents data response when creating {@link IOperation}.
*/
export interface IOperationCreatedResponse {
new_operation: IOperationDTO;
oss: IOperationSchemaDTO;
}
/** Represents data response when creating {@link IOperation}. */
export type IOperationCreatedResponse = z.infer<typeof schemaOperationCreatedResponse>;
/**
* Represents target {@link IOperation}.
*/
@ -92,13 +41,8 @@ export interface ITargetOperation {
/** Represents {@link IOperation} data, used in destruction process. */
export type IOperationDeleteDTO = z.infer<typeof schemaOperationDelete>;
/**
* Represents data response when creating {@link IRSForm} for Input {@link IOperation}.
*/
export interface IInputCreatedResponse {
new_schema: ILibraryItem;
oss: IOperationSchemaDTO;
}
/** Represents data response when creating {@link IRSForm} for Input {@link IOperation}. */
export type IInputCreatedResponse = z.infer<typeof schemaInputCreatedResponse>;
/** Represents {@link IOperation} data, used in setInput process. */
export type IInputUpdateDTO = z.infer<typeof schemaInputUpdate>;
@ -106,29 +50,49 @@ export type IInputUpdateDTO = z.infer<typeof schemaInputUpdate>;
/** Represents {@link IOperation} data, used in update process. */
export type IOperationUpdateDTO = z.infer<typeof schemaOperationUpdate>;
/**
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
*/
export const schemaCstRelocate = z.object({
destination: z.number(),
items: z.array(z.number()).refine(data => data.length > 0)
});
/**
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
*/
/** Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s. */
export type ICstRelocateDTO = z.infer<typeof schemaCstRelocate>;
/**
* Represents {@link IConstituenta} reference.
*/
export interface IConstituentaReference {
id: number;
schema: number;
}
/** Represents {@link IConstituenta} reference. */
export type IConstituentaReference = z.infer<typeof schemaConstituentaReference>;
// ====== Schemas ======
export const schemaOperation = z.object({
id: z.number(),
operation_type: z.nativeEnum(OperationType),
oss: z.number(),
alias: z.string(),
title: z.string(),
comment: z.string(),
position_x: z.number(),
position_y: z.number(),
result: z.number().nullable()
});
export const schemaCstSubstituteInfo = schemaCstSubstitute.extend({
operation: z.number(),
original_alias: z.string(),
original_term: z.string(),
substitution_alias: z.string(),
substitution_term: z.string()
});
export const schemaOperationSchema = schemaLibraryItem.extend({
editors: z.number().array(),
items: z.array(schemaOperation),
arguments: z
.object({
operation: z.number(),
argument: z.number()
})
.array(),
substitutions: z.array(schemaCstSubstituteInfo)
});
export const schemaOperationPosition = z.object({
id: z.number(),
position_x: z.number(),
@ -150,6 +114,11 @@ export const schemaOperationCreate = z.object({
create_schema: z.boolean()
});
export const schemaOperationCreatedResponse = z.object({
new_operation: schemaOperation,
oss: schemaOperationSchema
});
export const schemaOperationDelete = z.object({
target: z.number(),
positions: z.array(schemaOperationPosition),
@ -163,14 +132,29 @@ export const schemaInputUpdate = z.object({
input: z.number().nullable()
});
export const schemaInputCreatedResponse = z.object({
new_schema: schemaLibraryItem,
oss: schemaOperationSchema
});
export const schemaOperationUpdate = z.object({
target: z.number(),
positions: z.array(schemaOperationPosition),
item_data: z.object({
alias: z.string().nonempty(),
alias: z.string().nonempty(errorMsg.requiredField),
title: z.string(),
comment: z.string()
}),
arguments: z.array(z.number()),
substitutions: z.array(schemaCstSubstitute)
});
export const schemaCstRelocate = z.object({
destination: z.number().nullable(),
items: z.array(z.number()).refine(data => data.length > 0)
});
export const schemaConstituentaReference = z.object({
id: z.number(),
schema: z.number()
});

View File

@ -9,7 +9,7 @@ import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons';
import { CProps } from '@/components/props';
import { NoData } from '@/components/View';
import { IOperation } from '../backend/types';
import { IOperation } from '../models/oss';
import SelectOperation from './SelectOperation';

View File

@ -5,7 +5,7 @@ import clsx from 'clsx';
import { SelectSingle } from '@/components/Input';
import { CProps } from '@/components/props';
import { IOperation } from '../backend/types';
import { IOperation } from '../models/oss';
import { matchOperation } from '../models/ossAPI';
interface SelectOperationProps extends CProps.Styling {

View File

@ -6,7 +6,7 @@ import { Tooltip } from '@/components/Container';
import DataTable from '@/components/DataTable';
import { IconPageRight } from '@/components/Icons';
import { ICstSubstituteEx, OperationType } from '../backend/types';
import { ICstSubstituteInfo, OperationType } from '../backend/types';
import { labelOperationType } from '../labels';
import { OssNodeInternal } from '../models/ossLayout';
@ -15,7 +15,7 @@ interface TooltipOperationProps {
anchor: string;
}
const columnHelper = createColumnHelper<ICstSubstituteEx>();
const columnHelper = createColumnHelper<ICstSubstituteInfo>();
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
const columns = [

View File

@ -12,9 +12,9 @@ import { Label } from '@/components/Input';
import { ModalForm } from '@/components/Modal';
import { useDialogsStore } from '@/stores/dialogs';
import { IInputUpdateDTO, IOperation, IOperationPosition, schemaInputUpdate } from '../backend/types';
import { IInputUpdateDTO, IOperationPosition, schemaInputUpdate } from '../backend/types';
import { useInputUpdate } from '../backend/useInputUpdate';
import { IOperationSchema } from '../models/oss';
import { IOperation, IOperationSchema } from '../models/oss';
import { sortItemsForOSS } from '../models/ossAPI';
export interface DlgChangeInputSchemaProps {

View File

@ -10,9 +10,9 @@ import { Checkbox, TextInput } from '@/components/Input';
import { ModalForm } from '@/components/Modal';
import { useDialogsStore } from '@/stores/dialogs';
import { IOperation, IOperationDeleteDTO, IOperationPosition, schemaOperationDelete } from '../backend/types';
import { IOperationDeleteDTO, IOperationPosition, schemaOperationDelete } from '../backend/types';
import { useOperationDelete } from '../backend/useOperationDelete';
import { IOperationSchema } from '../models/oss';
import { IOperation, IOperationSchema } from '../models/oss';
export interface DlgDeleteOperationProps {
oss: IOperationSchema;

View File

@ -12,15 +12,9 @@ import { ModalForm } from '@/components/Modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import { useDialogsStore } from '@/stores/dialogs';
import {
IOperation,
IOperationPosition,
IOperationUpdateDTO,
OperationType,
schemaOperationUpdate
} from '../../backend/types';
import { IOperationPosition, IOperationUpdateDTO, OperationType, schemaOperationUpdate } from '../../backend/types';
import { useOperationUpdate } from '../../backend/useOperationUpdate';
import { IOperationSchema } from '../../models/oss';
import { IOperation, IOperationSchema } from '../../models/oss';
import TabArguments from './TabArguments';
import TabOperation from './TabOperation';
@ -45,6 +39,7 @@ function DlgEditOperation() {
const methods = useForm<IOperationUpdateDTO>({
resolver: zodResolver(schemaOperationUpdate),
defaultValues: {
target: target.id,
item_data: {
alias: target.alias,
title: target.alias,
@ -59,7 +54,7 @@ function DlgEditOperation() {
}
});
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
const canSubmit = alias !== '';
const isValid = alias !== '';
const [activeTab, setActiveTab] = useState(TabID.CARD);
@ -71,7 +66,7 @@ function DlgEditOperation() {
<ModalForm
header='Редактирование операции'
submitText='Сохранить'
canSubmit={canSubmit}
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-[40rem] px-6 h-[32rem]'
helpTopic={HelpTopic.UI_SUBSTITUTIONS}

View File

@ -15,10 +15,10 @@ import { Loader } from '@/components/Loader';
import { ModalForm } from '@/components/Modal';
import { useDialogsStore } from '@/stores/dialogs';
import { ICstRelocateDTO, IOperation, IOperationPosition, schemaCstRelocate } from '../backend/types';
import { ICstRelocateDTO, IOperationPosition, schemaCstRelocate } from '../backend/types';
import { useRelocateConstituents } from '../backend/useRelocateConstituents';
import { useUpdatePositions } from '../backend/useUpdatePositions';
import { IOperationSchema } from '../models/oss';
import { IOperation, IOperationSchema } from '../models/oss';
import { getRelocateCandidates } from '../models/ossAPI';
export interface DlgRelocateConstituentsProps {
@ -37,7 +37,6 @@ function DlgRelocateConstituents() {
handleSubmit,
control,
setValue,
resetField,
formState: { isValid }
} = useForm<ICstRelocateDTO>({
resolver: zodResolver(schemaCstRelocate),
@ -78,22 +77,22 @@ function DlgRelocateConstituents() {
function toggleDirection() {
setDirectionUp(prev => !prev);
resetField('destination');
setValue('destination', null);
}
function handleSelectSource(newValue: ILibraryItem | undefined) {
setSource(newValue);
resetField('destination');
resetField('items');
setValue('destination', null);
setValue('items', []);
}
function handleSelectDestination(newValue: ILibraryItem | undefined) {
if (newValue) {
setValue('destination', newValue.id);
} else {
resetField('destination');
setValue('destination', null);
}
resetField('items');
setValue('items', []);
}
function onSubmit(data: ICstRelocateDTO) {
@ -116,7 +115,7 @@ function DlgRelocateConstituents() {
<ModalForm
header='Перенос конституент'
submitText='Переместить'
canSubmit={isValid}
canSubmit={isValid && destinationItem !== undefined}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className={clsx('w-[40rem] h-[33rem]', 'py-3 px-6')}
helpTopic={HelpTopic.UI_RELOCATE_CST}

View File

@ -2,11 +2,19 @@
* Module: Schema of Synthesis Operations.
*/
import { ILibraryItemData } from '@/features/library/backend/types';
import { Graph } from '@/models/Graph';
import { IArgument, ICstSubstituteEx, IOperation } from '../backend/types';
import { ICstSubstituteInfo, IOperationDTO, IOperationSchemaDTO } from '../backend/types';
/**
* Represents Operation.
*/
export interface IOperation extends IOperationDTO {
is_owned: boolean;
is_consolidation: boolean; // aka 'diamond synthesis'
substitutions: ICstSubstituteInfo[];
arguments: number[];
}
/**
* Represents {@link IOperationSchema} statistics.
@ -22,10 +30,8 @@ export interface IOperationSchemaStats {
/**
* Represents OperationSchema.
*/
export interface IOperationSchema extends ILibraryItemData {
export interface IOperationSchema extends IOperationSchemaDTO {
items: IOperation[];
arguments: IArgument[];
substitutions: ICstSubstituteEx[];
graph: Graph;
schemas: number[];

View File

@ -18,10 +18,10 @@ import { infoMsg } from '@/utils/labels';
import { TextMatcher } from '@/utils/utils';
import { Graph } from '../../../models/Graph';
import { IOperation, IOperationPosition, OperationType } from '../backend/types';
import { IOperationPosition, OperationType } from '../backend/types';
import { describeSubstitutionError } from '../labels';
import { IOperationSchema, SubstitutionErrorType } from './oss';
import { IOperation, IOperationSchema, SubstitutionErrorType } from './oss';
import { Position2D } from './ossLayout';
/**

View File

@ -3,7 +3,8 @@
*/
import { Node } from 'reactflow';
import { IOperation } from '../backend/types';
import { IOperation } from './oss';
/**
* Represents XY Position.
*/

View File

@ -16,8 +16,9 @@ import useClickedOutside from '@/hooks/useClickedOutside';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { IOperation, OperationType } from '../../../backend/types';
import { OperationType } from '../../../backend/types';
import { useMutatingOss } from '../../../backend/useMutatingOss';
import { IOperation } from '../../../models/oss';
import { useOssEdit } from '../OssEditContext';
export interface ContextMenuData {

View File

@ -1,43 +1,26 @@
import { axiosPost } from '@/backend/apiTransport';
import { KEYS } from '@/backend/configuration';
/**
* Represents API result for text output.
*/
export interface ITextResult {
result: string;
}
/**
* Represents wordform data used for backend communication.
*/
export interface IWordFormDTO {
text: string;
grams: string;
}
/**
* Represents lexeme response containing multiple {@link Wordform}s.
*/
export interface ILexemeResponse {
items: IWordFormDTO[];
}
import { ILexemeResponse, ITextResult, IWordFormDTO, schemaLexemeResponse, schemaTextResult } from './types';
export const cctextApi = {
baseKey: KEYS.cctext,
inflectText: (data: IWordFormDTO) =>
axiosPost<IWordFormDTO, ITextResult>({
schema: schemaTextResult,
endpoint: '/api/cctext/inflect',
request: { data: data }
}),
parseText: (data: { text: string }) =>
axiosPost<{ text: string }, ITextResult>({
schema: schemaTextResult,
endpoint: '/api/cctext/parse',
request: { data: data }
}),
generateLexeme: (data: { text: string }) =>
axiosPost<{ text: string }, ILexemeResponse>({
schema: schemaLexemeResponse,
endpoint: '/api/cctext/generate-lexeme',
request: { data: data }
})

View File

@ -0,0 +1,25 @@
import { z } from 'zod';
/** Represents API result for text output. */
export type ITextResult = z.infer<typeof schemaTextResult>;
/** Represents wordform data used for backend communication. */
export type IWordFormDTO = z.infer<typeof schemaWordForm>;
/** Represents lexeme response containing multiple {@link Wordform}s. */
export type ILexemeResponse = z.infer<typeof schemaLexemeResponse>;
// ====== Schemas =========
export const schemaTextResult = z.object({
result: z.string()
});
export const schemaWordForm = z.object({
text: z.string(),
grams: z.string()
});
export const schemaLexemeResponse = z.object({
items: z.array(schemaWordForm)
});

View File

@ -1,6 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { cctextApi, IWordFormDTO } from './api';
import { cctextApi } from './api';
import { IWordFormDTO } from './types';
export const useInflectText = () => {
const mutation = useMutation({

View File

@ -109,6 +109,9 @@ export type IRSErrorDescription = z.infer<typeof schemaRSErrorDescription>;
/** Represents results of expression parse in RSLang. */
export type IExpressionParseDTO = z.infer<typeof schemaExpressionParse>;
/** Represents data response when creating {@link IVersionInfo}. */
export type IVersionCreatedResponse = z.infer<typeof schemaVersionCreatedResponse>;
/** Represents RSLang token types. */
export enum TokenID {
// Global, local IDs and literals
@ -322,6 +325,11 @@ export const schemaRSForm = z.object({
oss: z.array(z.object({ id: z.coerce.number(), alias: z.string() }))
});
export const schemaVersionCreatedResponse = z.object({
version: z.number(),
schema: schemaRSForm
});
export const schemaCstCreate = schemaConstituentaBasics
.pick({
cst_type: true,

View File

@ -62,7 +62,7 @@ export function ToolbarRSFormCard({ controller, onSubmit }: ToolbarRSFormCardPro
/>
) : null}
<MiniButton
titleHtml={tooltipText.shareItem(controller.schema.access_policy)}
titleHtml={tooltipText.shareItem(controller.schema.access_policy === AccessPolicy.PUBLIC)}
icon={<IconShare size='1.25rem' className='icon-primary' />}
onClick={sharePage}
disabled={controller.schema.access_policy !== AccessPolicy.PUBLIC}

View File

@ -2,7 +2,8 @@
* Module: Models for formal representation for systems of concepts.
*/
import { ILibraryItemReference, ILibraryItemVersioned } from '@/features/library/models/library';
import { ILibraryItemData, IVersionInfo } from '@/features/library/backend/types';
import { ILibraryItemReference } from '@/features/library/models/library';
import { Graph } from '@/models/Graph';
@ -137,7 +138,10 @@ export interface IInheritanceInfo {
/**
* Represents formal explication for set of concepts.
*/
export interface IRSForm extends ILibraryItemVersioned {
export interface IRSForm extends ILibraryItemData {
version?: number;
versions: IVersionInfo[];
items: IConstituenta[];
inheritance: IInheritanceInfo[];
oss: ILibraryItemReference[];

View File

@ -227,7 +227,7 @@ function MenuRSTabs() {
<Dropdown isOpen={schemaMenu.isOpen}>
<DropdownButton
text='Поделиться'
titleHtml={tooltipText.shareItem(controller.schema.access_policy)}
titleHtml={tooltipText.shareItem(controller.schema.access_policy === AccessPolicy.PUBLIC)}
icon={<IconShare size='1rem' className='icon-primary' />}
onClick={handleShare}
disabled={controller.schema.access_policy !== AccessPolicy.PUBLIC}

View File

@ -14,6 +14,11 @@ export const PARAMETER = {
navigationDuration: 300, // milliseconds navigation duration
navigationPopupDelay: 300, // milliseconds delay for navigation popup
fastAnimation: 200, // milliseconds - duration of fast animation
fadeDuration: 300, // milliseconds - duration of fade animation
dropdownDuration: 200, // milliseconds - duration of dropdown animation
moveDuration: 500, // milliseconds - duration of move animation
ossImageWidth: 1280, // pixels - size of OSS image
ossImageHeight: 960, // pixels - size of OSS image
ossContextMenuWidth: 200, // pixels - width of OSS context menu
@ -23,11 +28,6 @@ export const PARAMETER = {
ossDistanceX: 180, // pixels - insert x-distance between node centers
ossDistanceY: 100, // pixels - insert y-distance between node centers
fastAnimation: 200, // milliseconds - duration of fast animation
fadeDuration: 300, // milliseconds - duration of fade animation
dropdownDuration: 300, // milliseconds - duration of dropdown animation
moveDuration: 500, // milliseconds - duration of move animation
graphHandleSize: 3, // pixels - size of graph connection handle
graphNodeRadius: 20, // pixels - radius of graph node
graphNodePadding: 5, // pixels - padding of graph node

View File

@ -4,7 +4,6 @@
* Label is a short text used to represent an entity.
* Description is a long description used in tooltips.
*/
import { AccessPolicy, LibraryItemType } from '@/features/library/backend/types';
import { UserRole } from '@/features/users/stores/role';
/**
@ -37,55 +36,6 @@ export function describeAccessMode(mode: UserRole): string {
}
}
/**
* Retrieves label for {@link AccessPolicy}.
*/
export function labelAccessPolicy(policy: AccessPolicy): string {
// prettier-ignore
switch (policy) {
case AccessPolicy.PRIVATE: return 'Личный';
case AccessPolicy.PROTECTED: return 'Защищенный';
case AccessPolicy.PUBLIC: return 'Открытый';
}
}
/**
* Retrieves description for {@link AccessPolicy}.
*/
export function describeAccessPolicy(policy: AccessPolicy): string {
// prettier-ignore
switch (policy) {
case AccessPolicy.PRIVATE:
return 'Доступ только для владельца';
case AccessPolicy.PROTECTED:
return 'Доступ для владельца и редакторов';
case AccessPolicy.PUBLIC:
return 'Открытый доступ';
}
}
/**
* Retrieves label for {@link LibraryItemType}.
*/
export function labelLibraryItemType(itemType: LibraryItemType): string {
// prettier-ignore
switch (itemType) {
case LibraryItemType.RSFORM: return 'КС';
case LibraryItemType.OSS: return 'ОСС';
}
}
/**
* Retrieves description for {@link LibraryItemType}.
*/
export function describeLibraryItemType(itemType: LibraryItemType): string {
// prettier-ignore
switch (itemType) {
case LibraryItemType.RSFORM: return 'Концептуальная схема';
case LibraryItemType.OSS: return 'Операционная схема синтеза';
}
}
/**
* UI info descriptors.
*/
@ -150,8 +100,7 @@ export const errorMsg = {
*/
export const tooltipText = {
unsaved: 'Сохраните или отмените изменения',
shareItem: (policy?: AccessPolicy) =>
policy === AccessPolicy.PUBLIC ? 'Поделиться схемой' : 'Поделиться можно только <br/>открытой схемой'
shareItem: (isPublic: boolean) => (isPublic ? 'Поделиться схемой' : 'Поделиться можно только <br/>открытой схемой')
};
/**