R: Improve dialogs persistence when data is invalidated
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled
Frontend CI / notify-failure (push) Has been cancelled

This commit is contained in:
Ivan 2025-08-13 22:08:03 +03:00
parent 2ecdbd1719
commit 9420729a96
20 changed files with 220 additions and 142 deletions

View File

@ -10,18 +10,20 @@ 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 ICreateBlockDTO, schemaCreateBlock } from '../../backend/types'; import { type ICreateBlockDTO, type IOssLayout, schemaCreateBlock } from '../../backend/types';
import { useCreateBlock } from '../../backend/use-create-block'; import { useCreateBlock } from '../../backend/use-create-block';
import { type IOssItem, NodeType } from '../../models/oss'; import { useOssSuspense } from '../../backend/use-oss';
import { type LayoutManager } from '../../models/oss-layout-api'; import { LayoutManager } from '../../models/oss-layout-api';
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';
import { TabBlockCard } from './tab-block-card'; import { TabBlockCard } from './tab-block-card';
import { TabBlockChildren } from './tab-block-children'; import { TabBlockChildren } from './tab-block-children';
export interface DlgCreateBlockProps { export interface DlgCreateBlockProps {
manager: LayoutManager; ossID: number;
initialChildren: IOssItem[]; layout: IOssLayout;
childrenBlocks: number[];
childrenOperations: number[];
initialParent: number | null; initialParent: number | null;
defaultX: number; defaultX: number;
defaultY: number; defaultY: number;
@ -37,9 +39,19 @@ export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgCreateBlock() { export function DlgCreateBlock() {
const { createBlock } = useCreateBlock(); const { createBlock } = useCreateBlock();
const { manager, initialChildren, initialParent, onCreate, defaultX, defaultY } = useDialogsStore( const {
state => state.props as DlgCreateBlockProps ossID, //
); layout,
childrenBlocks,
childrenOperations,
initialParent,
onCreate,
defaultX,
defaultY
} = useDialogsStore(state => state.props as DlgCreateBlockProps);
const { schema } = useOssSuspense({ itemID: ossID });
const manager = new LayoutManager(schema, layout);
const methods = useForm<ICreateBlockDTO>({ const methods = useForm<ICreateBlockDTO>({
resolver: zodResolver(schemaCreateBlock), resolver: zodResolver(schemaCreateBlock),
@ -55,8 +67,8 @@ export function DlgCreateBlock() {
width: BLOCK_NODE_MIN_WIDTH, width: BLOCK_NODE_MIN_WIDTH,
height: BLOCK_NODE_MIN_HEIGHT height: BLOCK_NODE_MIN_HEIGHT
}, },
children_blocks: initialChildren.filter(item => item.nodeType === NodeType.BLOCK).map(item => item.id), children_blocks: childrenBlocks,
children_operations: initialChildren.filter(item => item.nodeType === NodeType.OPERATION).map(item => item.id), children_operations: childrenOperations,
layout: manager.layout layout: manager.layout
}, },
mode: 'onChange' mode: 'onChange'
@ -93,11 +105,11 @@ export function DlgCreateBlock() {
<FormProvider {...methods}> <FormProvider {...methods}>
<TabPanel> <TabPanel>
<TabBlockCard /> <TabBlockCard oss={schema} />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<TabBlockChildren /> <TabBlockChildren oss={schema} />
</TabPanel> </TabPanel>
</FormProvider> </FormProvider>
</Tabs> </Tabs>

View File

@ -3,17 +3,17 @@
import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { TextArea, TextInput } from '@/components/input'; import { TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO } from '../../backend/types'; import { type ICreateBlockDTO } from '../../backend/types';
import { SelectParent } from '../../components/select-parent'; import { SelectParent } from '../../components/select-parent';
import { NodeType } from '../../models/oss'; import { type IOperationSchema, NodeType } from '../../models/oss';
import { constructNodeID } from '../../models/oss-api'; import { constructNodeID } from '../../models/oss-api';
import { type DlgCreateBlockProps } from './dlg-create-block'; interface TabBlockCardProps {
oss: IOperationSchema;
}
export function TabBlockCard() { export function TabBlockCard({ oss }: TabBlockCardProps) {
const { manager } = useDialogsStore(state => state.props as DlgCreateBlockProps);
const { const {
register, register,
control, control,
@ -21,7 +21,7 @@ export function TabBlockCard() {
} = useFormContext<ICreateBlockDTO>(); } = useFormContext<ICreateBlockDTO>();
const children_blocks = useWatch({ control, name: 'children_blocks' }); const children_blocks = useWatch({ control, name: 'children_blocks' });
const block_ids = children_blocks.map(id => constructNodeID(NodeType.BLOCK, id)); const block_ids = children_blocks.map(id => constructNodeID(NodeType.BLOCK, id));
const all_children = [...block_ids, ...manager.oss.hierarchy.expandAllOutputs(block_ids)]; const all_children = [...block_ids, ...oss.hierarchy.expandAllOutputs(block_ids)];
return ( return (
<div className='cc-fade-in cc-column'> <div className='cc-fade-in cc-column'>
@ -36,8 +36,8 @@ export function TabBlockCard() {
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<SelectParent <SelectParent
items={manager.oss.blocks.filter(block => !all_children.includes(block.nodeID))} items={oss.blocks.filter(block => !all_children.includes(block.nodeID))}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null} value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок' placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)} onChange={value => field.onChange(value ? value.id : null)}
/> />

View File

@ -3,34 +3,34 @@
import { useFormContext, useWatch } from 'react-hook-form'; import { useFormContext, useWatch } from 'react-hook-form';
import { Label } from '@/components/input'; import { Label } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO } from '../../backend/types'; import { type ICreateBlockDTO } from '../../backend/types';
import { PickContents } from '../../components/pick-contents'; import { PickContents } from '../../components/pick-contents';
import { type IOssItem, NodeType } from '../../models/oss'; import { type IOperationSchema, type IOssItem, NodeType } from '../../models/oss';
import { type DlgCreateBlockProps } from './dlg-create-block'; interface TabBlockChildrenProps {
oss: IOperationSchema;
}
export function TabBlockChildren() { export function TabBlockChildren({ oss }: TabBlockChildrenProps) {
const { setValue, control } = useFormContext<ICreateBlockDTO>(); const { setValue, control } = useFormContext<ICreateBlockDTO>();
const { manager } = useDialogsStore(state => state.props as DlgCreateBlockProps);
const parent = useWatch({ control, name: 'item_data.parent' }); const parent = useWatch({ control, name: 'item_data.parent' });
const children_blocks = useWatch({ control, name: 'children_blocks' }); const children_blocks = useWatch({ control, name: 'children_blocks' });
const children_operations = useWatch({ control, name: 'children_operations' }); const children_operations = useWatch({ control, name: 'children_operations' });
const parentItem = parent ? manager.oss.blockByID.get(parent) : null; const parentItem = parent ? oss.blockByID.get(parent) : null;
const internalBlocks = parentItem const internalBlocks = parentItem
? manager.oss.hierarchy ? oss.hierarchy
.expandAllInputs([parentItem.nodeID]) .expandAllInputs([parentItem.nodeID])
.map(id => manager.oss.itemByNodeID.get(id)) .map(id => oss.itemByNodeID.get(id))
.filter(item => item !== null && item?.nodeType === NodeType.BLOCK) .filter(item => item !== null && item?.nodeType === NodeType.BLOCK)
: []; : [];
const exclude = parentItem ? [parentItem, ...internalBlocks] : []; const exclude = parentItem ? [parentItem, ...internalBlocks] : [];
const value = [ const value = [
...children_blocks.map(id => manager.oss.blockByID.get(id)!), ...children_blocks.map(id => oss.blockByID.get(id)!),
...children_operations.map(id => manager.oss.operationByID.get(id)!) ...children_operations.map(id => oss.operationByID.get(id)!)
]; ];
function handleChangeSelected(newValue: IOssItem[]) { function handleChangeSelected(newValue: IOssItem[]) {
@ -50,7 +50,7 @@ export function TabBlockChildren() {
<div className='cc-fade-in cc-column'> <div className='cc-fade-in cc-column'>
<Label text={`Выбор содержания: [ ${value.length} ]`} /> <Label text={`Выбор содержания: [ ${value.length} ]`} />
<PickContents <PickContents
schema={manager.oss} schema={oss}
exclude={exclude} exclude={exclude}
value={value} value={value}
onChange={newValue => handleChangeSelected(newValue)} onChange={newValue => handleChangeSelected(newValue)}

View File

@ -9,13 +9,15 @@ import { TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal'; import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateSchemaDTO, schemaCreateSchema } from '../backend/types'; import { type ICreateSchemaDTO, type IOssLayout, schemaCreateSchema } from '../backend/types';
import { useCreateSchema } from '../backend/use-create-schema'; import { useCreateSchema } from '../backend/use-create-schema';
import { useOssSuspense } from '../backend/use-oss';
import { SelectParent } from '../components/select-parent'; import { SelectParent } from '../components/select-parent';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../models/oss-layout-api'; import { LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../models/oss-layout-api';
export interface DlgCreateSchemaProps { export interface DlgCreateSchemaProps {
manager: LayoutManager; ossID: number;
layout: IOssLayout;
defaultX: number; defaultX: number;
defaultY: number; defaultY: number;
initialParent: number | null; initialParent: number | null;
@ -25,9 +27,17 @@ export interface DlgCreateSchemaProps {
export function DlgCreateSchema() { export function DlgCreateSchema() {
const { createSchema } = useCreateSchema(); const { createSchema } = useCreateSchema();
const { manager, initialParent, onCreate, defaultX, defaultY } = useDialogsStore( const {
state => state.props as DlgCreateSchemaProps ossID, //
); layout,
initialParent,
onCreate,
defaultX,
defaultY
} = useDialogsStore(state => state.props as DlgCreateSchemaProps);
const { schema } = useOssSuspense({ itemID: ossID });
const manager = new LayoutManager(schema, layout);
const { const {
control, control,

View File

@ -11,15 +11,17 @@ 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 ICreateSynthesisDTO, schemaCreateSynthesis } from '../../backend/types'; import { type ICreateSynthesisDTO, type IOssLayout, schemaCreateSynthesis } from '../../backend/types';
import { useCreateSynthesis } from '../../backend/use-create-synthesis'; import { useCreateSynthesis } from '../../backend/use-create-synthesis';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../../models/oss-layout-api'; import { useOssSuspense } from '../../backend/use-oss';
import { LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../../models/oss-layout-api';
import { TabArguments } from './tab-arguments'; import { TabArguments } from './tab-arguments';
import { TabSubstitutions } from './tab-substitutions'; import { TabSubstitutions } from './tab-substitutions';
export interface DlgCreateSynthesisProps { export interface DlgCreateSynthesisProps {
manager: LayoutManager; ossID: number;
layout: IOssLayout;
initialParent: number | null; initialParent: number | null;
initialInputs: number[]; initialInputs: number[];
defaultX: number; defaultX: number;
@ -36,9 +38,17 @@ export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgCreateSynthesis() { export function DlgCreateSynthesis() {
const { createSynthesis } = useCreateSynthesis(); const { createSynthesis } = useCreateSynthesis();
const { manager, initialInputs, initialParent, onCreate, defaultX, defaultY } = useDialogsStore( const {
state => state.props as DlgCreateSynthesisProps ossID, //
); layout,
initialInputs,
initialParent,
onCreate,
defaultX,
defaultY
} = useDialogsStore(state => state.props as DlgCreateSynthesisProps);
const { schema } = useOssSuspense({ itemID: ossID });
const manager = new LayoutManager(schema, layout);
const methods = useForm<ICreateSynthesisDTO>({ const methods = useForm<ICreateSynthesisDTO>({
resolver: zodResolver(schemaCreateSynthesis), resolver: zodResolver(schemaCreateSynthesis),
@ -87,7 +97,7 @@ export function DlgCreateSynthesis() {
</TabList> </TabList>
<FormProvider {...methods}> <FormProvider {...methods}>
<TabPanel> <TabPanel>
<TabArguments /> <TabArguments oss={schema} />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Suspense <Suspense
@ -97,7 +107,7 @@ export function DlgCreateSynthesis() {
</div> </div>
} }
> >
<TabSubstitutions /> <TabSubstitutions oss={schema} />
</Suspense> </Suspense>
</TabPanel> </TabPanel>
</FormProvider> </FormProvider>

View File

@ -3,16 +3,17 @@
import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { Label, TextArea, TextInput } from '@/components/input'; import { Label, TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateSynthesisDTO } from '../../backend/types'; import { type ICreateSynthesisDTO } from '../../backend/types';
import { PickMultiOperation } from '../../components/pick-multi-operation'; import { PickMultiOperation } from '../../components/pick-multi-operation';
import { SelectParent } from '../../components/select-parent'; import { SelectParent } from '../../components/select-parent';
import { type IOperationSchema } from '../../models/oss';
import { type DlgCreateSynthesisProps } from './dlg-create-synthesis'; interface TabArgumentsProps {
oss: IOperationSchema;
}
export function TabArguments() { export function TabArguments({ oss }: TabArgumentsProps) {
const { manager } = useDialogsStore(state => state.props as DlgCreateSynthesisProps);
const { const {
register, register,
control, control,
@ -20,11 +21,11 @@ export function TabArguments() {
} = useFormContext<ICreateSynthesisDTO>(); } = useFormContext<ICreateSynthesisDTO>();
const inputs = useWatch({ control, name: 'arguments' }); const inputs = useWatch({ control, name: 'arguments' });
const replicas = manager.oss.replicas const replicas = oss.replicas
.filter(item => inputs.includes(item.original)) .filter(item => inputs.includes(item.original))
.map(item => item.replica) .map(item => item.replica)
.concat(manager.oss.replicas.filter(item => inputs.includes(item.replica)).map(item => item.original)); .concat(oss.replicas.filter(item => inputs.includes(item.replica)).map(item => item.original));
const filtered = manager.oss.operations.filter(item => !replicas.includes(item.id)); const filtered = oss.operations.filter(item => !replicas.includes(item.id));
return ( return (
<div className='cc-fade-in cc-column'> <div className='cc-fade-in cc-column'>
@ -48,8 +49,8 @@ export function TabArguments() {
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<SelectParent <SelectParent
items={manager.oss.blocks} items={oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null} value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок' placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)} onChange={value => field.onChange(value ? value.id : null)}
/> />

View File

@ -6,21 +6,22 @@ import { useRSForms } from '@/features/rsform/backend/use-rsforms';
import { PickSubstitutions } from '@/features/rsform/components/pick-substitutions'; import { PickSubstitutions } from '@/features/rsform/components/pick-substitutions';
import { TextArea } from '@/components/input'; import { TextArea } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateSynthesisDTO } from '../../backend/types'; import { type ICreateSynthesisDTO } from '../../backend/types';
import { type IOperationSchema } from '../../models/oss';
import { SubstitutionValidator } from '../../models/oss-api'; import { SubstitutionValidator } from '../../models/oss-api';
import { type DlgCreateSynthesisProps } from './dlg-create-synthesis'; interface TabSubstitutionsProps {
oss: IOperationSchema;
}
export function TabSubstitutions() { export function TabSubstitutions({ oss }: TabSubstitutionsProps) {
const { manager } = useDialogsStore(state => state.props as DlgCreateSynthesisProps);
const { control } = useFormContext<ICreateSynthesisDTO>(); const { control } = useFormContext<ICreateSynthesisDTO>();
const inputs = useWatch({ control, name: 'arguments' }); const inputs = useWatch({ control, name: 'arguments' });
const substitutions = useWatch({ control, name: 'substitutions' }); const substitutions = useWatch({ control, name: 'substitutions' });
const schemasIDs = inputs const schemasIDs = inputs
.map(id => manager.oss.operationByID.get(id)!) .map(id => oss.operationByID.get(id)!)
.map(operation => operation.result) .map(operation => operation.result)
.filter(id => id !== null); .filter(id => id !== null);
const schemas = useRSForms(schemasIDs); const schemas = useRSForms(schemasIDs);

View File

@ -11,23 +11,26 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type IDeleteOperationDTO, type IOssLayout, OperationType, schemaDeleteOperation } from '../backend/types'; import { type IDeleteOperationDTO, type IOssLayout, OperationType, schemaDeleteOperation } from '../backend/types';
import { useDeleteOperation } from '../backend/use-delete-operation'; import { useDeleteOperation } from '../backend/use-delete-operation';
import { type IOperationInput, type IOperationSchema, type IOperationSynthesis } from '../models/oss'; import { useOssSuspense } from '../backend/use-oss';
export interface DlgDeleteOperationProps { export interface DlgDeleteOperationProps {
oss: IOperationSchema; ossID: number;
target: IOperationInput | IOperationSynthesis; targetID: number;
layout: IOssLayout; layout: IOssLayout;
beforeDelete?: () => void; beforeDelete?: () => void;
} }
export function DlgDeleteOperation() { export function DlgDeleteOperation() {
const { oss, target, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteOperationProps); const { ossID, targetID, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteOperationProps);
const { deleteOperation } = useDeleteOperation(); const { deleteOperation } = useDeleteOperation();
const { schema } = useOssSuspense({ itemID: ossID });
const target = schema.operationByID.get(targetID)!;
const { handleSubmit, control } = useForm<IDeleteOperationDTO>({ const { handleSubmit, control } = useForm<IDeleteOperationDTO>({
resolver: zodResolver(schemaDeleteOperation), resolver: zodResolver(schemaDeleteOperation),
defaultValues: { defaultValues: {
target: target.id, target: targetID,
layout: layout, layout: layout,
keep_constituents: false, keep_constituents: false,
delete_schema: true delete_schema: true
@ -35,7 +38,7 @@ export function DlgDeleteOperation() {
}); });
function onSubmit(data: IDeleteOperationDTO) { function onSubmit(data: IDeleteOperationDTO) {
return deleteOperation({ itemID: oss.id, data: data, beforeUpdate: beforeDelete }); return deleteOperation({ itemID: ossID, data: data, beforeUpdate: beforeDelete });
} }
return ( return (

View File

@ -11,23 +11,26 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type IDeleteReplicaDTO, type IOssLayout, schemaDeleteReplica } from '../backend/types'; import { type IDeleteReplicaDTO, type IOssLayout, schemaDeleteReplica } from '../backend/types';
import { useDeleteReplica } from '../backend/use-delete-replica'; import { useDeleteReplica } from '../backend/use-delete-replica';
import { type IOperationReplica, type IOperationSchema } from '../models/oss'; import { useOssSuspense } from '../backend/use-oss';
export interface DlgDeleteReplicaProps { export interface DlgDeleteReplicaProps {
oss: IOperationSchema; ossID: number;
target: IOperationReplica; targetID: number;
layout: IOssLayout; layout: IOssLayout;
beforeDelete?: () => void; beforeDelete?: () => void;
} }
export function DlgDeleteReplica() { export function DlgDeleteReplica() {
const { oss, target, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteReplicaProps); const { ossID, targetID, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteReplicaProps);
const { deleteReplica: deleteReference } = useDeleteReplica(); const { deleteReplica: deleteReference } = useDeleteReplica();
const { schema } = useOssSuspense({ itemID: ossID });
const target = schema.operationByID.get(targetID)!;
const { handleSubmit, control } = useForm<IDeleteReplicaDTO>({ const { handleSubmit, control } = useForm<IDeleteReplicaDTO>({
resolver: zodResolver(schemaDeleteReplica), resolver: zodResolver(schemaDeleteReplica),
defaultValues: { defaultValues: {
target: target.id, target: targetID,
layout: layout, layout: layout,
keep_constituents: false, keep_constituents: false,
keep_connections: false keep_connections: false
@ -36,7 +39,7 @@ export function DlgDeleteReplica() {
const keep_connections = useWatch({ control, name: 'keep_connections' }); const keep_connections = useWatch({ control, name: 'keep_connections' });
function onSubmit(data: IDeleteReplicaDTO) { function onSubmit(data: IDeleteReplicaDTO) {
return deleteReference({ itemID: oss.id, data: data, beforeUpdate: beforeDelete }); return deleteReference({ itemID: ossID, data: data, beforeUpdate: beforeDelete });
} }
return ( return (

View File

@ -9,20 +9,24 @@ import { TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal'; import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { type IUpdateBlockDTO, schemaUpdateBlock } from '../backend/types'; import { type IOssLayout, type IUpdateBlockDTO, schemaUpdateBlock } from '../backend/types';
import { useOssSuspense } from '../backend/use-oss';
import { useUpdateBlock } from '../backend/use-update-block'; import { useUpdateBlock } from '../backend/use-update-block';
import { SelectParent } from '../components/select-parent'; import { SelectParent } from '../components/select-parent';
import { type IBlock } from '../models/oss'; import { LayoutManager } from '../models/oss-layout-api';
import { type LayoutManager } from '../models/oss-layout-api';
export interface DlgEditBlockProps { export interface DlgEditBlockProps {
manager: LayoutManager; ossID: number;
target: IBlock; layout: IOssLayout;
targetID: number;
} }
export function DlgEditBlock() { export function DlgEditBlock() {
const { manager, target } = useDialogsStore(state => state.props as DlgEditBlockProps); const { ossID, targetID, layout } = useDialogsStore(state => state.props as DlgEditBlockProps);
const { updateBlock } = useUpdateBlock(); const { updateBlock } = useUpdateBlock();
const { schema } = useOssSuspense({ itemID: ossID });
const manager = new LayoutManager(schema, layout);
const target = manager.oss.blockByID.get(targetID)!;
const { const {
handleSubmit, handleSubmit,

View File

@ -11,18 +11,19 @@ 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 IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types'; import { type IOssLayout, type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types';
import { useOssSuspense } from '../../backend/use-oss';
import { useUpdateOperation } from '../../backend/use-update-operation'; import { useUpdateOperation } from '../../backend/use-update-operation';
import { type IOperationInput, type IOperationSynthesis } from '../../models/oss'; import { LayoutManager } from '../../models/oss-layout-api';
import { type LayoutManager } from '../../models/oss-layout-api';
import { TabArguments } from './tab-arguments'; import { TabArguments } from './tab-arguments';
import { TabOperation } from './tab-operation'; import { TabOperation } from './tab-operation';
import { TabSubstitutions } from './tab-substitutions'; import { TabSubstitutions } from './tab-substitutions';
export interface DlgEditOperationProps { export interface DlgEditOperationProps {
manager: LayoutManager; ossID: number;
target: IOperationInput | IOperationSynthesis; layout: IOssLayout;
targetID: number;
} }
export const TabID = { export const TabID = {
@ -33,13 +34,17 @@ export const TabID = {
export type TabID = (typeof TabID)[keyof typeof TabID]; export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgEditOperation() { export function DlgEditOperation() {
const { manager, target } = useDialogsStore(state => state.props as DlgEditOperationProps); const { ossID, layout, targetID } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { updateOperation } = useUpdateOperation(); const { updateOperation } = useUpdateOperation();
const { schema } = useOssSuspense({ itemID: ossID });
const manager = new LayoutManager(schema, layout);
const target = manager.oss.operationByID.get(targetID)!;
const methods = useForm<IUpdateOperationDTO>({ const methods = useForm<IUpdateOperationDTO>({
resolver: zodResolver(schemaUpdateOperation), resolver: zodResolver(schemaUpdateOperation),
defaultValues: { defaultValues: {
target: target.id, target: targetID,
item_data: { item_data: {
alias: target.alias, alias: target.alias,
title: target.title, title: target.title,
@ -101,15 +106,17 @@ export function DlgEditOperation() {
<FormProvider {...methods}> <FormProvider {...methods}>
<TabPanel> <TabPanel>
<TabOperation /> <TabOperation oss={schema} />
</TabPanel> </TabPanel>
<TabPanel>{target.operation_type === OperationType.SYNTHESIS ? <TabArguments /> : null}</TabPanel> <TabPanel>
{target.operation_type === OperationType.SYNTHESIS ? <TabArguments oss={schema} target={target} /> : null}
</TabPanel>
<TabPanel> <TabPanel>
{target.operation_type === OperationType.SYNTHESIS ? ( {target.operation_type === OperationType.SYNTHESIS ? (
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<TabSubstitutions /> <TabSubstitutions oss={schema} />
</Suspense> </Suspense>
) : null} ) : null}
</TabPanel> </TabPanel>

View File

@ -3,24 +3,26 @@
import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { Label } from '@/components/input'; import { Label } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type IUpdateOperationDTO } from '../../backend/types'; import { type IUpdateOperationDTO } from '../../backend/types';
import { PickMultiOperation } from '../../components/pick-multi-operation'; import { PickMultiOperation } from '../../components/pick-multi-operation';
import { type IOperationInput, type IOperationSchema, type IOperationSynthesis } from '../../models/oss';
import { type DlgEditOperationProps } from './dlg-edit-operation'; interface TabArgumentsProps {
oss: IOperationSchema;
target: IOperationInput | IOperationSynthesis;
}
export function TabArguments() { export function TabArguments({ oss, target }: TabArgumentsProps) {
const { control, setValue } = useFormContext<IUpdateOperationDTO>(); const { control, setValue } = useFormContext<IUpdateOperationDTO>();
const { manager, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
const args = useWatch({ control, name: 'arguments' }); const args = useWatch({ control, name: 'arguments' });
const replicas = manager.oss.replicas const replicas = oss.replicas
.filter(item => args.includes(item.original) || item.original === target.id) .filter(item => args.includes(item.original) || item.original === target.id)
.map(item => item.replica) .map(item => item.replica)
.concat(manager.oss.replicas.filter(item => args.includes(item.replica)).map(item => item.original)); .concat(oss.replicas.filter(item => args.includes(item.replica)).map(item => item.original));
const potentialCycle = [target.id, ...replicas, ...manager.oss.graph.expandAllOutputs([target.id])]; const potentialCycle = [target.id, ...replicas, ...oss.graph.expandAllOutputs([target.id])];
const filtered = manager.oss.operations.filter(item => !potentialCycle.includes(item.id)); const filtered = oss.operations.filter(item => !potentialCycle.includes(item.id));
function handleChangeArguments(prev: number[], newValue: number[]) { function handleChangeArguments(prev: number[], newValue: number[]) {
setValue('arguments', newValue, { shouldValidate: true }); setValue('arguments', newValue, { shouldValidate: true });

View File

@ -1,15 +1,16 @@
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import { TextArea, TextInput } from '@/components/input'; import { TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type IUpdateOperationDTO } from '../../backend/types'; import { type IUpdateOperationDTO } from '../../backend/types';
import { SelectParent } from '../../components/select-parent'; import { SelectParent } from '../../components/select-parent';
import { type IOperationSchema } from '../../models/oss';
import { type DlgEditOperationProps } from './dlg-edit-operation'; interface TabOperationProps {
oss: IOperationSchema;
}
export function TabOperation() { export function TabOperation({ oss }: TabOperationProps) {
const { manager } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { const {
register, register,
control, control,
@ -37,8 +38,8 @@ export function TabOperation() {
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<SelectParent <SelectParent
items={manager.oss.blocks} items={oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null} value={field.value ? oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок' placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)} onChange={value => field.onChange(value ? value.id : null)}
/> />

View File

@ -6,21 +6,22 @@ import { useRSForms } from '@/features/rsform/backend/use-rsforms';
import { PickSubstitutions } from '@/features/rsform/components/pick-substitutions'; import { PickSubstitutions } from '@/features/rsform/components/pick-substitutions';
import { TextArea } from '@/components/input'; import { TextArea } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type IUpdateOperationDTO } from '../../backend/types'; import { type IUpdateOperationDTO } from '../../backend/types';
import { type IOperationSchema } from '../../models/oss';
import { SubstitutionValidator } from '../../models/oss-api'; import { SubstitutionValidator } from '../../models/oss-api';
import { type DlgEditOperationProps } from './dlg-edit-operation'; interface TabSubstitutionsProps {
oss: IOperationSchema;
}
export function TabSubstitutions() { export function TabSubstitutions({ oss }: TabSubstitutionsProps) {
const { manager } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { control } = useFormContext<IUpdateOperationDTO>(); const { control } = useFormContext<IUpdateOperationDTO>();
const inputs = useWatch({ control, name: 'arguments' }); const inputs = useWatch({ control, name: 'arguments' });
const substitutions = useWatch({ control, name: 'substitutions' }); const substitutions = useWatch({ control, name: 'substitutions' });
const schemasIDs = inputs const schemasIDs = inputs
.map(id => manager.oss.operationByID.get(id)!) .map(id => oss.operationByID.get(id)!)
.map(operation => operation.result) .map(operation => operation.result)
.filter(id => id !== null); .filter(id => id !== null);
const schemas = useRSForms(schemasIDs); const schemas = useRSForms(schemasIDs);

View File

@ -12,14 +12,16 @@ import { Checkbox, TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal'; import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { type IImportSchemaDTO, schemaImportSchema } from '../backend/types'; import { type IImportSchemaDTO, type IOssLayout, schemaImportSchema } from '../backend/types';
import { useImportSchema } from '../backend/use-import-schema'; import { useImportSchema } from '../backend/use-import-schema';
import { useOssSuspense } from '../backend/use-oss';
import { SelectParent } from '../components/select-parent'; import { SelectParent } from '../components/select-parent';
import { sortItemsForOSS } from '../models/oss-api'; import { sortItemsForOSS } from '../models/oss-api';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../models/oss-layout-api'; import { LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../models/oss-layout-api';
export interface DlgImportSchemaProps { export interface DlgImportSchemaProps {
manager: LayoutManager; ossID: number;
layout: IOssLayout;
defaultX: number; defaultX: number;
defaultY: number; defaultY: number;
initialParent: number | null; initialParent: number | null;
@ -29,9 +31,16 @@ export interface DlgImportSchemaProps {
export function DlgImportSchema() { export function DlgImportSchema() {
const { importSchema } = useImportSchema(); const { importSchema } = useImportSchema();
const { manager, initialParent, onCreate, defaultX, defaultY } = useDialogsStore( const {
state => state.props as DlgImportSchemaProps ossID, //
); layout,
initialParent,
onCreate,
defaultX,
defaultY
} = useDialogsStore(state => state.props as DlgImportSchemaProps);
const { schema } = useOssSuspense({ itemID: ossID });
const manager = new LayoutManager(schema, layout);
const { items: libraryItems } = useLibrary(); const { items: libraryItems } = useLibrary();
const sortedItems = sortItemsForOSS(manager.oss, libraryItems); const sortedItems = sortItemsForOSS(manager.oss, libraryItems);

View File

@ -17,24 +17,27 @@ import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { type IOssLayout, type IRelocateConstituentsDTO, schemaRelocateConstituents } from '../backend/types'; import { type IOssLayout, type IRelocateConstituentsDTO, schemaRelocateConstituents } from '../backend/types';
import { useOssSuspense } from '../backend/use-oss';
import { useRelocateConstituents } from '../backend/use-relocate-constituents'; import { useRelocateConstituents } from '../backend/use-relocate-constituents';
import { useUpdateLayout } from '../backend/use-update-layout'; import { useUpdateLayout } from '../backend/use-update-layout';
import { IconRelocationUp } from '../components/icon-relocation-up'; import { IconRelocationUp } from '../components/icon-relocation-up';
import { type IOperation, type IOperationSchema } from '../models/oss';
import { getRelocateCandidates } from '../models/oss-api'; import { getRelocateCandidates } from '../models/oss-api';
export interface DlgRelocateConstituentsProps { export interface DlgRelocateConstituentsProps {
oss: IOperationSchema; ossID: number;
initialTarget?: IOperation; targetID?: number;
layout?: IOssLayout; layout?: IOssLayout;
} }
export function DlgRelocateConstituents() { export function DlgRelocateConstituents() {
const { oss, initialTarget, layout } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps); const { ossID, targetID, layout } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
const { items: libraryItems } = useLibrary(); const { items: libraryItems } = useLibrary();
const { updateLayout: updatePositions } = useUpdateLayout(); const { updateLayout: updatePositions } = useUpdateLayout();
const { relocateConstituents } = useRelocateConstituents(); const { relocateConstituents } = useRelocateConstituents();
const { schema: oss } = useOssSuspense({ itemID: ossID });
const initialTarget = targetID ? oss.operationByID.get(targetID)! : undefined;
const { handleSubmit, control, setValue } = useForm<IRelocateConstituentsDTO>({ const { handleSubmit, control, setValue } = useForm<IRelocateConstituentsDTO>({
resolver: zodResolver(schemaRelocateConstituents), resolver: zodResolver(schemaRelocateConstituents),
defaultValues: { defaultValues: {

View File

@ -7,7 +7,6 @@ import { useDialogsStore } from '@/stores/dialogs';
import { useDeleteBlock } from '../../../../backend/use-delete-block'; import { useDeleteBlock } from '../../../../backend/use-delete-block';
import { useMutatingOss } from '../../../../backend/use-mutating-oss'; import { useMutatingOss } from '../../../../backend/use-mutating-oss';
import { type IBlock } from '../../../../models/oss'; import { type IBlock } from '../../../../models/oss';
import { LayoutManager } from '../../../../models/oss-layout-api';
import { useOssEdit } from '../../oss-edit-context'; import { useOssEdit } from '../../oss-edit-context';
import { useGetLayout } from '../use-get-layout'; import { useGetLayout } from '../use-get-layout';
@ -30,8 +29,9 @@ export function MenuBlock({ block, onHide }: MenuBlockProps) {
} }
onHide(); onHide();
showEditBlock({ showEditBlock({
manager: new LayoutManager(schema, getLayout()), layout: getLayout(),
target: block ossID: schema.id,
targetID: block.id
}); });
} }

View File

@ -101,8 +101,9 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
} }
onHide(); onHide();
showEditOperation({ showEditOperation({
manager: new LayoutManager(schema, getLayout()), layout: getLayout(),
target: operation ossID: schema.id,
targetID: operation.id
}); });
} }
@ -114,8 +115,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
switch (operation.operation_type) { switch (operation.operation_type) {
case OperationType.REPLICA: case OperationType.REPLICA:
showDeleteReference({ showDeleteReference({
oss: schema, ossID: schema.id,
target: operation, targetID: operation.id,
layout: getLayout(), layout: getLayout(),
beforeDelete: deselectAll beforeDelete: deselectAll
}); });
@ -123,8 +124,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
case OperationType.INPUT: case OperationType.INPUT:
case OperationType.SYNTHESIS: case OperationType.SYNTHESIS:
showDeleteOperation({ showDeleteOperation({
oss: schema, ossID: schema.id,
target: operation, targetID: operation.id,
layout: getLayout(), layout: getLayout(),
beforeDelete: deselectAll beforeDelete: deselectAll
}); });
@ -163,8 +164,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
} }
onHide(); onHide();
showRelocateConstituents({ showRelocateConstituents({
oss: schema, ossID: schema.id,
initialTarget: operation, targetID: operation.id,
layout: getLayout() layout: getLayout()
}); });
} }

View File

@ -73,7 +73,7 @@ export function OssFlow() {
const [mouseCoords, setMouseCoords] = useState<Position2D>({ x: 0, y: 0 }); const [mouseCoords, setMouseCoords] = useState<Position2D>({ x: 0, y: 0 });
const showCreateOperation = useDialogsStore(state => state.showCreateOperation); const showCreateOperation = useDialogsStore(state => state.showCreateSynthesis);
const showCreateBlock = useDialogsStore(state => state.showCreateBlock); const showCreateBlock = useDialogsStore(state => state.showCreateBlock);
const showCreateSchema = useDialogsStore(state => state.showCreateSchema); const showCreateSchema = useDialogsStore(state => state.showCreateSchema);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation); const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
@ -91,7 +91,8 @@ export function OssFlow() {
function handleCreateSynthesis() { function handleCreateSynthesis() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateOperation({ showCreateOperation({
manager: new LayoutManager(schema, getLayout()), ossID: schema.id,
layout: getLayout(),
defaultX: targetPosition.x, defaultX: targetPosition.x,
defaultY: targetPosition.y, defaultY: targetPosition.y,
initialInputs: selectedItems.filter(item => item?.nodeType === NodeType.OPERATION).map(item => item.id), initialInputs: selectedItems.filter(item => item?.nodeType === NodeType.OPERATION).map(item => item.id),
@ -106,12 +107,18 @@ export function OssFlow() {
function handleCreateBlock() { function handleCreateBlock() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
const parent = extractBlockParent(selectedItems); const parent = extractBlockParent(selectedItems);
const needChildren = parent === null || selectedItems.length !== 1 || parent !== selectedItems[0].id;
showCreateBlock({ showCreateBlock({
manager: new LayoutManager(schema, getLayout()), ossID: schema.id,
layout: getLayout(),
defaultX: targetPosition.x, defaultX: targetPosition.x,
defaultY: targetPosition.y, defaultY: targetPosition.y,
initialChildren: childrenBlocks: !needChildren
parent !== null && selectedItems.length === 1 && parent === selectedItems[0].id ? [] : selectedItems, ? []
: selectedItems.filter(item => item.nodeType === NodeType.BLOCK).map(item => item.id),
childrenOperations: !needChildren
? []
: selectedItems.filter(item => item.nodeType === NodeType.OPERATION).map(item => item.id),
initialParent: parent, initialParent: parent,
onCreate: newID => { onCreate: newID => {
resetView(); resetView();
@ -123,7 +130,8 @@ export function OssFlow() {
function handleCreateSchema() { function handleCreateSchema() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateSchema({ showCreateSchema({
manager: new LayoutManager(schema, getLayout()), ossID: schema.id,
layout: getLayout(),
defaultX: targetPosition.x, defaultX: targetPosition.x,
defaultY: targetPosition.y, defaultY: targetPosition.y,
initialParent: extractBlockParent(selectedItems), initialParent: extractBlockParent(selectedItems),
@ -137,7 +145,8 @@ export function OssFlow() {
function handleImportSchema() { function handleImportSchema() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showImportSchema({ showImportSchema({
manager: new LayoutManager(schema, getLayout()), ossID: schema.id,
layout: getLayout(),
defaultX: targetPosition.x, defaultX: targetPosition.x,
defaultY: targetPosition.y, defaultY: targetPosition.y,
initialParent: extractBlockParent(selectedItems), initialParent: extractBlockParent(selectedItems),
@ -163,8 +172,8 @@ export function OssFlow() {
switch (item.operation_type) { switch (item.operation_type) {
case OperationType.REPLICA: case OperationType.REPLICA:
showDeleteReference({ showDeleteReference({
oss: schema, ossID: schema.id,
target: item, targetID: item.id,
layout: getLayout(), layout: getLayout(),
beforeDelete: deselectAll beforeDelete: deselectAll
}); });
@ -172,8 +181,8 @@ export function OssFlow() {
case OperationType.INPUT: case OperationType.INPUT:
case OperationType.SYNTHESIS: case OperationType.SYNTHESIS:
showDeleteOperation({ showDeleteOperation({
oss: schema, ossID: schema.id,
target: item, targetID: item.id,
layout: getLayout(), layout: getLayout(),
beforeDelete: deselectAll beforeDelete: deselectAll
}); });
@ -198,8 +207,9 @@ export function OssFlow() {
const block = schema.blockByID.get(-Number(node.id)); const block = schema.blockByID.get(-Number(node.id));
if (block) { if (block) {
showEditBlock({ showEditBlock({
manager: new LayoutManager(schema, getLayout()), ossID: schema.id,
target: block layout: getLayout(),
targetID: block.id
}); });
} }
} else { } else {

View File

@ -93,7 +93,7 @@ interface DialogsStore {
showCstTemplate: (props: DlgCstTemplateProps) => void; showCstTemplate: (props: DlgCstTemplateProps) => void;
showCreateCst: (props: DlgCreateCstProps) => void; showCreateCst: (props: DlgCreateCstProps) => void;
showCreateBlock: (props: DlgCreateBlockProps) => void; showCreateBlock: (props: DlgCreateBlockProps) => void;
showCreateOperation: (props: DlgCreateSynthesisProps) => void; showCreateSynthesis: (props: DlgCreateSynthesisProps) => void;
showDeleteCst: (props: DlgDeleteCstProps) => void; showDeleteCst: (props: DlgDeleteCstProps) => void;
showEditEditors: (props: DlgEditEditorsProps) => void; showEditEditors: (props: DlgEditEditorsProps) => void;
showEditOperation: (props: DlgEditOperationProps) => void; showEditOperation: (props: DlgEditOperationProps) => void;
@ -138,7 +138,7 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
showVideo: props => set({ active: DialogType.SHOW_VIDEO, props: props }), showVideo: props => set({ active: DialogType.SHOW_VIDEO, props: props }),
showCstTemplate: props => set({ active: DialogType.CONSTITUENTA_TEMPLATE, props: props }), showCstTemplate: props => set({ active: DialogType.CONSTITUENTA_TEMPLATE, props: props }),
showCreateCst: props => set({ active: DialogType.CREATE_CONSTITUENTA, props: props }), showCreateCst: props => set({ active: DialogType.CREATE_CONSTITUENTA, props: props }),
showCreateOperation: props => set({ active: DialogType.CREATE_SYNTHESIS, props: props }), showCreateSynthesis: props => set({ active: DialogType.CREATE_SYNTHESIS, props: props }),
showCreateBlock: props => set({ active: DialogType.CREATE_BLOCK, props: props }), showCreateBlock: props => set({ active: DialogType.CREATE_BLOCK, props: props }),
showDeleteCst: props => set({ active: DialogType.DELETE_CONSTITUENTA, props: props }), showDeleteCst: props => set({ active: DialogType.DELETE_CONSTITUENTA, props: props }),
showEditEditors: props => set({ active: DialogType.EDIT_EDITORS, props: props }), showEditEditors: props => set({ active: DialogType.EDIT_EDITORS, props: props }),