From 5efce874b22723e5845aa3a1f25fbcee68928022 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:47:40 +0300 Subject: [PATCH] R: Restructuring layout data pt2 --- .../apps/oss/serializers/data_access.py | 6 +-- .../apps/oss/tests/s_views/t_operations.py | 8 ++-- .../backend/apps/oss/tests/s_views/t_oss.py | 18 ++++---- .../library/backend/use-set-access-policy.tsx | 2 +- .../library/backend/use-set-editors.tsx | 2 +- .../library/backend/use-set-location.tsx | 2 +- .../library/backend/use-set-owner.tsx | 2 +- .../frontend/src/features/oss/backend/api.ts | 14 ++---- .../src/features/oss/backend/oss-loader.ts | 9 ++-- .../src/features/oss/backend/types.ts | 43 +++++++++++-------- .../oss/backend/use-operation-update.tsx | 2 +- .../oss/backend/use-update-layout.tsx | 18 ++++++-- .../oss/dialogs/dlg-change-input-schema.tsx | 8 ++-- .../dlg-create-operation.tsx | 30 ++++++------- .../tab-synthesis-operation.tsx | 2 +- .../oss/dialogs/dlg-delete-operation.tsx | 8 ++-- .../dlg-edit-operation/dlg-edit-operation.tsx | 13 ++---- .../dlg-edit-operation/tab-arguments.tsx | 2 +- .../oss/dialogs/dlg-relocate-constituents.tsx | 18 +++----- .../src/features/oss/models/oss-api.ts | 31 +++++++------ .../frontend/src/features/oss/models/oss.ts | 4 +- .../editor-oss-graph/node-context-menu.tsx | 16 +++---- .../oss-page/editor-oss-graph/oss-flow.tsx | 26 ++++------- .../editor-oss-graph/toolbar-oss-graph.tsx | 19 +++----- .../editor-oss-graph/use-get-layout.tsx | 17 ++++++++ .../editor-oss-graph/use-get-positions.tsx | 12 ------ .../oss/pages/oss-page/menu-edit-oss.tsx | 3 +- 27 files changed, 161 insertions(+), 174 deletions(-) create mode 100644 rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-layout.tsx delete mode 100644 rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-positions.tsx diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 2e7d8f72..d5f7fe85 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -172,7 +172,7 @@ class SetOperationInputSerializer(serializers.Serializer): class OperationSchemaSerializer(serializers.ModelSerializer): ''' Serializer: Detailed data for OSS. ''' - items = serializers.ListField( + operations = serializers.ListField( child=OperationSerializer() ) arguments = serializers.ListField( @@ -193,9 +193,9 @@ class OperationSchemaSerializer(serializers.ModelSerializer): del result['versions'] oss = OperationSchema(instance) result['layout'] = oss.layout().data - result['items'] = [] + result['operations'] = [] for operation in oss.operations().order_by('pk'): - result['items'].append(OperationSerializer(operation).data) + result['operations'].append(OperationSerializer(operation).data) result['arguments'] = [] for argument in oss.arguments().order_by('order'): result['arguments'].append(ArgumentSerializer(argument).data) diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py index 0840d4d5..4509a4c4 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py @@ -98,7 +98,7 @@ class TestOssOperations(EndpointTester): self.executeNotFound(data=data, item=self.invalid_id) response = self.executeCreated(data=data, item=self.owned_id) - self.assertEqual(len(response.data['oss']['items']), 4) + self.assertEqual(len(response.data['oss']['operations']), 4) new_operation = response.data['new_operation'] layout = response.data['oss']['layout'] item = [item for item in layout['operations'] if item['id'] == new_operation['id']][0] @@ -210,9 +210,9 @@ class TestOssOperations(EndpointTester): self.login() response = self.executeOK(data=data) layout = response.data['layout'] - items = [item for item in layout['operations'] if item['id'] == data['target']] - self.assertEqual(len(response.data['items']), 2) - self.assertEqual(len(items), 0) + deleted_items = [item for item in layout['operations'] if item['id'] == data['target']] + self.assertEqual(len(response.data['operations']), 2) + self.assertEqual(len(deleted_items), 0) @decl_endpoint('/api/oss/{item}/create-input', method='patch') def test_create_input(self): diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py index 868bdd18..a6c526dc 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -82,9 +82,9 @@ class TestOssViewset(EndpointTester): self.assertEqual(response.data['item_type'], LibraryItemType.OPERATION_SCHEMA) - self.assertEqual(len(response.data['items']), 3) - self.assertEqual(response.data['items'][0]['id'], self.operation1.pk) - self.assertEqual(response.data['items'][0]['operation_type'], self.operation1.operation_type) + self.assertEqual(len(response.data['operations']), 3) + self.assertEqual(response.data['operations'][0]['id'], self.operation1.pk) + self.assertEqual(response.data['operations'][0]['operation_type'], self.operation1.operation_type) self.assertEqual(len(response.data['substitutions']), 1) sub = response.data['substitutions'][0] @@ -125,11 +125,13 @@ class TestOssViewset(EndpointTester): data = {'operations': [], 'blocks': []} self.executeOK(data=data) - data = {'operations': [ - {'id': self.operation1.pk, 'x': 42.1, 'y': 1337}, - {'id': self.operation2.pk, 'x': 36.1, 'y': 1437}, - {'id': self.operation3.pk, 'x': 36.1, 'y': 1435} - ], 'blocks': []} + data = { + 'operations': [ + {'id': self.operation1.pk, 'x': 42.1, 'y': 1337}, + {'id': self.operation2.pk, 'x': 36.1, 'y': 1437}, + {'id': self.operation3.pk, 'x': 36.1, 'y': 1435} + ], 'blocks': [] + } self.toggle_admin(True) self.executeOK(data=data, item=self.unowned_id) diff --git a/rsconcept/frontend/src/features/library/backend/use-set-access-policy.tsx b/rsconcept/frontend/src/features/library/backend/use-set-access-policy.tsx index 8ca10b9c..16d4f83f 100644 --- a/rsconcept/frontend/src/features/library/backend/use-set-access-policy.tsx +++ b/rsconcept/frontend/src/features/library/backend/use-set-access-policy.tsx @@ -20,7 +20,7 @@ export const useSetAccessPolicy = () => { client.setQueryData(ossKey, { ...ossData, access_policy: variables.policy }); return Promise.allSettled([ client.invalidateQueries({ queryKey: KEYS.composite.libraryList }), - ...ossData.items + ...ossData.operations .map(item => { if (!item.result) { return; diff --git a/rsconcept/frontend/src/features/library/backend/use-set-editors.tsx b/rsconcept/frontend/src/features/library/backend/use-set-editors.tsx index 9a97e2c2..199bdc21 100644 --- a/rsconcept/frontend/src/features/library/backend/use-set-editors.tsx +++ b/rsconcept/frontend/src/features/library/backend/use-set-editors.tsx @@ -18,7 +18,7 @@ export const useSetEditors = () => { if (ossData) { client.setQueryData(ossKey, { ...ossData, editors: variables.editors }); return Promise.allSettled( - ossData.items + ossData.operations .map(item => { if (!item.result) { return; diff --git a/rsconcept/frontend/src/features/library/backend/use-set-location.tsx b/rsconcept/frontend/src/features/library/backend/use-set-location.tsx index c979170f..3f0a324b 100644 --- a/rsconcept/frontend/src/features/library/backend/use-set-location.tsx +++ b/rsconcept/frontend/src/features/library/backend/use-set-location.tsx @@ -20,7 +20,7 @@ export const useSetLocation = () => { client.setQueryData(ossKey, { ...ossData, location: variables.location }); return Promise.allSettled([ client.invalidateQueries({ queryKey: libraryApi.libraryListKey }), - ...ossData.items + ...ossData.operations .map(item => { if (!item.result) { return; diff --git a/rsconcept/frontend/src/features/library/backend/use-set-owner.tsx b/rsconcept/frontend/src/features/library/backend/use-set-owner.tsx index 826a7788..7060c1e4 100644 --- a/rsconcept/frontend/src/features/library/backend/use-set-owner.tsx +++ b/rsconcept/frontend/src/features/library/backend/use-set-owner.tsx @@ -20,7 +20,7 @@ export const useSetOwner = () => { client.setQueryData(ossKey, { ...ossData, owner: variables.owner }); return Promise.allSettled([ client.invalidateQueries({ queryKey: libraryApi.libraryListKey }), - ...ossData.items + ...ossData.operations .map(item => { if (!item.result) { return; diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 7ed20f3e..8dbfac5e 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -12,9 +12,9 @@ import { type IOperationCreatedResponse, type IOperationCreateDTO, type IOperationDeleteDTO, - type IOperationPosition, type IOperationSchemaDTO, type IOperationUpdateDTO, + type IOssLayout, type ITargetOperation, schemaConstituentaReference, schemaOperationCreatedResponse, @@ -39,19 +39,11 @@ export const ossApi = { }); }, - updateLayout: ({ - itemID, - positions, - isSilent - }: { - itemID: number; - positions: IOperationPosition[]; - isSilent?: boolean; - }) => + updateLayout: ({ itemID, data, isSilent }: { itemID: number; data: IOssLayout; isSilent?: boolean }) => axiosPatch({ endpoint: `/api/oss/${itemID}/update-layout`, request: { - data: { positions: positions }, + data: data, successMessage: isSilent ? undefined : infoMsg.changesSaved } }), diff --git a/rsconcept/frontend/src/features/oss/backend/oss-loader.ts b/rsconcept/frontend/src/features/oss/backend/oss-loader.ts index fba0565a..95d83e42 100644 --- a/rsconcept/frontend/src/features/oss/backend/oss-loader.ts +++ b/rsconcept/frontend/src/features/oss/backend/oss-loader.ts @@ -41,7 +41,7 @@ export class OssLoader { } private prepareLookups() { - this.oss.items.forEach(operation => { + this.oss.operations.forEach(operation => { this.operationByID.set(operation.id, operation); this.graph.addNode(operation.id); }); @@ -52,13 +52,16 @@ export class OssLoader { } private extractSchemas() { - this.schemaIDs = this.oss.items.map(operation => operation.result).filter(item => item !== null); + this.schemaIDs = this.oss.operations.map(operation => operation.result).filter(item => item !== null); } private inferOperationAttributes() { this.graph.topologicalOrder().forEach(operationID => { const operation = this.operationByID.get(operationID)!; const schema = this.items.find(item => item.id === operation.result); + const position = this.oss.layout.operations.find(item => item.id === operationID); + operation.x = position?.x ?? 0; + operation.y = position?.y ?? 0; operation.is_consolidation = this.inferConsolidation(operationID); operation.is_owned = !schema || (schema.owner === this.oss.owner && schema.location === this.oss.location); operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID); @@ -82,7 +85,7 @@ export class OssLoader { } private calculateStats(): IOperationSchemaStats { - const items = this.oss.items; + const items = this.oss.operations; return { count_operations: items.length, count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length, diff --git a/rsconcept/frontend/src/features/oss/backend/types.ts b/rsconcept/frontend/src/features/oss/backend/types.ts index b390c4b8..c38ee20a 100644 --- a/rsconcept/frontend/src/features/oss/backend/types.ts +++ b/rsconcept/frontend/src/features/oss/backend/types.ts @@ -23,8 +23,8 @@ export type IOperationDTO = z.infer; /** Represents backend data for {@link IOperationSchema}. */ export type IOperationSchemaDTO = z.infer; -/** Represents {@link IOperation} position. */ -export type IOperationPosition = z.infer; +/** Represents {@link schemaOperation} layout. */ +export type IOssLayout = z.infer; /** Represents {@link IOperation} data, used in creation process. */ export type IOperationCreateDTO = z.infer; @@ -35,7 +35,7 @@ export type IOperationCreatedResponse = z.infer { mutationFn: ossApi.operationUpdate, onSuccess: (data, variables) => { client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data); - const schemaID = data.items.find(item => item.id === variables.data.target)?.result; + const schemaID = data.operations.find(item => item.id === variables.data.target)?.result; if (!schemaID) { return; } diff --git a/rsconcept/frontend/src/features/oss/backend/use-update-layout.tsx b/rsconcept/frontend/src/features/oss/backend/use-update-layout.tsx index 5f6aade0..724865b9 100644 --- a/rsconcept/frontend/src/features/oss/backend/use-update-layout.tsx +++ b/rsconcept/frontend/src/features/oss/backend/use-update-layout.tsx @@ -5,7 +5,7 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest import { KEYS } from '@/backend/configuration'; import { ossApi } from './api'; -import { type IOperationPosition } from './types'; +import { type IOperationSchemaDTO, type IOssLayout } from './types'; export const useUpdateLayout = () => { const client = useQueryClient(); @@ -13,13 +13,25 @@ export const useUpdateLayout = () => { const mutation = useMutation({ mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-layout'], mutationFn: ossApi.updateLayout, - onSuccess: (_, variables) => updateTimestamp(variables.itemID), + onSuccess: (_, variables) => { + updateTimestamp(variables.itemID); + client.setQueryData( + ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey, + (prev: IOperationSchemaDTO | undefined) => + !prev + ? prev + : { + ...prev, + layout: variables.data + } + ); + }, onError: () => client.invalidateQueries() }); return { updateLayout: (data: { itemID: number; // - positions: IOperationPosition[]; + data: IOssLayout; isSilent?: boolean; }) => mutation.mutateAsync(data) }; diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-change-input-schema.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-change-input-schema.tsx index e50d30d6..dc80328b 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-change-input-schema.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-change-input-schema.tsx @@ -13,7 +13,7 @@ import { Label } from '@/components/input'; import { ModalForm } from '@/components/modal'; import { useDialogsStore } from '@/stores/dialogs'; -import { type IInputUpdateDTO, type IOperationPosition, schemaInputUpdate } from '../backend/types'; +import { type IInputUpdateDTO, type IOssLayout, schemaInputUpdate } from '../backend/types'; import { useInputUpdate } from '../backend/use-input-update'; import { type IOperation, type IOperationSchema } from '../models/oss'; import { sortItemsForOSS } from '../models/oss-api'; @@ -21,18 +21,18 @@ import { sortItemsForOSS } from '../models/oss-api'; export interface DlgChangeInputSchemaProps { oss: IOperationSchema; target: IOperation; - positions: IOperationPosition[]; + layout: IOssLayout; } export function DlgChangeInputSchema() { - const { oss, target, positions } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps); + const { oss, target, layout } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps); const { inputUpdate } = useInputUpdate(); const { setValue, handleSubmit, control } = useForm({ resolver: zodResolver(schemaInputUpdate), defaultValues: { target: target.id, - positions: positions, + layout: layout, input: target.result } }); diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/dlg-create-operation.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/dlg-create-operation.tsx index c22a9a1b..392a9088 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/dlg-create-operation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/dlg-create-operation.tsx @@ -10,12 +10,7 @@ import { ModalForm } from '@/components/modal'; import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs'; import { useDialogsStore } from '@/stores/dialogs'; -import { - type IOperationCreateDTO, - type IOperationPosition, - OperationType, - schemaOperationCreate -} from '../../backend/types'; +import { type IOperationCreateDTO, type IOssLayout, OperationType, schemaOperationCreate } from '../../backend/types'; import { useOperationCreate } from '../../backend/use-operation-create'; import { describeOperationType, labelOperationType } from '../../labels'; import { type IOperationSchema } from '../../models/oss'; @@ -26,7 +21,7 @@ import { TabSynthesisOperation } from './tab-synthesis-operation'; export interface DlgCreateOperationProps { oss: IOperationSchema; - positions: IOperationPosition[]; + layout: IOssLayout; initialInputs: number[]; defaultX: number; defaultY: number; @@ -42,7 +37,7 @@ export type TabID = (typeof TabID)[keyof typeof TabID]; export function DlgCreateOperation() { const { operationCreate } = useOperationCreate(); - const { oss, positions, initialInputs, onCreate, defaultX, defaultY } = useDialogsStore( + const { oss, layout, initialInputs, onCreate, defaultX, defaultY } = useDialogsStore( state => state.props as DlgCreateOperationProps ); @@ -51,30 +46,31 @@ export function DlgCreateOperation() { defaultValues: { item_data: { operation_type: initialInputs.length === 0 ? OperationType.INPUT : OperationType.SYNTHESIS, - result: null, - position_x: defaultX, - position_y: defaultY, alias: '', title: '', - description: '' + description: '', + result: null, + parent: null }, + position_x: defaultX, + position_y: defaultY, arguments: initialInputs, create_schema: false, - positions: positions + layout: layout }, mode: 'onChange' }); const alias = useWatch({ control: methods.control, name: 'item_data.alias' }); const [activeTab, setActiveTab] = useState(initialInputs.length === 0 ? TabID.INPUT : TabID.SYNTHESIS); - const isValid = !!alias && !oss.items.some(operation => operation.alias === alias); + const isValid = !!alias && !oss.operations.some(operation => operation.alias === alias); function onSubmit(data: IOperationCreateDTO) { - const target = calculateInsertPosition(oss, data.arguments, positions, { + const target = calculateInsertPosition(oss, data.arguments, layout, { x: defaultX, y: defaultY }); - data.item_data.position_x = target.x; - data.item_data.position_y = target.y; + data.position_x = target.x; + data.position_y = target.y; void operationCreate({ itemID: oss.id, data: data }).then(response => onCreate?.(response.new_operation.id)); } diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/tab-synthesis-operation.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/tab-synthesis-operation.tsx index 809677c0..6cac05f6 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/tab-synthesis-operation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-create-operation/tab-synthesis-operation.tsx @@ -50,7 +50,7 @@ export function TabSynthesisOperation() { name='arguments' control={control} render={({ field }) => ( - + )} /> diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx index f7ef43a2..5a16e9b6 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-delete-operation.tsx @@ -9,25 +9,25 @@ import { Checkbox, TextInput } from '@/components/input'; import { ModalForm } from '@/components/modal'; import { useDialogsStore } from '@/stores/dialogs'; -import { type IOperationDeleteDTO, type IOperationPosition, schemaOperationDelete } from '../backend/types'; +import { type IOperationDeleteDTO, type IOssLayout, schemaOperationDelete } from '../backend/types'; import { useOperationDelete } from '../backend/use-operation-delete'; import { type IOperation, type IOperationSchema } from '../models/oss'; export interface DlgDeleteOperationProps { oss: IOperationSchema; target: IOperation; - positions: IOperationPosition[]; + layout: IOssLayout; } export function DlgDeleteOperation() { - const { oss, target, positions } = useDialogsStore(state => state.props as DlgDeleteOperationProps); + const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteOperationProps); const { operationDelete } = useOperationDelete(); const { handleSubmit, control } = useForm({ resolver: zodResolver(schemaOperationDelete), defaultValues: { target: target.id, - positions: positions, + layout: layout, keep_constituents: false, delete_schema: false } diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx index 9cc52a6c..6bb35414 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation.tsx @@ -11,12 +11,7 @@ import { ModalForm } from '@/components/modal'; import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs'; import { useDialogsStore } from '@/stores/dialogs'; -import { - type IOperationPosition, - type IOperationUpdateDTO, - OperationType, - schemaOperationUpdate -} from '../../backend/types'; +import { type IOperationUpdateDTO, type IOssLayout, OperationType, schemaOperationUpdate } from '../../backend/types'; import { useOperationUpdate } from '../../backend/use-operation-update'; import { type IOperation, type IOperationSchema } from '../../models/oss'; @@ -27,7 +22,7 @@ import { TabSynthesis } from './tab-synthesis'; export interface DlgEditOperationProps { oss: IOperationSchema; target: IOperation; - positions: IOperationPosition[]; + layout: IOssLayout; } export const TabID = { @@ -38,7 +33,7 @@ export const TabID = { export type TabID = (typeof TabID)[keyof typeof TabID]; export function DlgEditOperation() { - const { oss, target, positions } = useDialogsStore(state => state.props as DlgEditOperationProps); + const { oss, target, layout } = useDialogsStore(state => state.props as DlgEditOperationProps); const { operationUpdate } = useOperationUpdate(); const methods = useForm({ @@ -55,7 +50,7 @@ export function DlgEditOperation() { original: sub.original, substitution: sub.substitution })), - positions: positions + layout: layout }, mode: 'onChange' }); diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx index 5d94f45a..b626a8ea 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-edit-operation/tab-arguments.tsx @@ -13,7 +13,7 @@ export function TabArguments() { const { control, setValue } = useFormContext(); const { oss, target } = useDialogsStore(state => state.props as DlgEditOperationProps); const potentialCycle = [target.id, ...oss.graph.expandAllOutputs([target.id])]; - const filtered = oss.items.filter(item => !potentialCycle.includes(item.id)); + const filtered = oss.operations.filter(item => !potentialCycle.includes(item.id)); function handleChangeArguments(prev: number[], newValue: number[]) { setValue('arguments', newValue, { shouldValidate: true }); diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-relocate-constituents.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-relocate-constituents.tsx index eddd6516..5cf66ec3 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/dlg-relocate-constituents.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-relocate-constituents.tsx @@ -16,7 +16,7 @@ import { Loader } from '@/components/loader'; import { ModalForm } from '@/components/modal'; import { useDialogsStore } from '@/stores/dialogs'; -import { type ICstRelocateDTO, type IOperationPosition, schemaCstRelocate } from '../backend/types'; +import { type ICstRelocateDTO, type IOssLayout, schemaCstRelocate } from '../backend/types'; import { useRelocateConstituents } from '../backend/use-relocate-constituents'; import { useUpdateLayout } from '../backend/use-update-layout'; import { IconRelocationUp } from '../components/icon-relocation-up'; @@ -26,11 +26,11 @@ import { getRelocateCandidates } from '../models/oss-api'; export interface DlgRelocateConstituentsProps { oss: IOperationSchema; initialTarget?: IOperation; - positions: IOperationPosition[]; + layout?: IOssLayout; } export function DlgRelocateConstituents() { - const { oss, initialTarget, positions } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps); + const { oss, initialTarget, layout } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps); const { items: libraryItems } = useLibrary(); const { updateLayout: updatePositions } = useUpdateLayout(); const { relocateConstituents } = useRelocateConstituents(); @@ -55,7 +55,7 @@ export function DlgRelocateConstituents() { libraryItems.find(item => item.id === initialTarget?.result) ?? null ); - const operation = oss.items.find(item => item.result === source?.id); + const operation = oss.operations.find(item => item.result === source?.id); const sourceSchemas = libraryItems.filter(item => oss.schemas.includes(item.id)); const destinationSchemas = (() => { if (!operation) { @@ -73,7 +73,7 @@ export function DlgRelocateConstituents() { if (!sourceData.schema || !destinationItem || !operation) { return []; } - const destinationOperation = oss.items.find(item => item.result === destination); + const destinationOperation = oss.operations.find(item => item.result === destination); return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss); })(); @@ -98,17 +98,13 @@ export function DlgRelocateConstituents() { } function onSubmit(data: ICstRelocateDTO) { - const positionsUnchanged = positions.every(item => { - const operation = oss.operationByID.get(item.id)!; - return operation.position_x === item.position_x && operation.position_y === item.position_y; - }); - if (positionsUnchanged) { + if (!layout || JSON.stringify(layout) === JSON.stringify(oss.layout)) { return relocateConstituents(data); } else { return updatePositions({ isSilent: true, itemID: oss.id, - positions: positions + data: layout }).then(() => relocateConstituents(data)); } } diff --git a/rsconcept/frontend/src/features/oss/models/oss-api.ts b/rsconcept/frontend/src/features/oss/models/oss-api.ts index 0700b9fd..4ee4cf40 100644 --- a/rsconcept/frontend/src/features/oss/models/oss-api.ts +++ b/rsconcept/frontend/src/features/oss/models/oss-api.ts @@ -23,7 +23,7 @@ import { infoMsg } from '@/utils/labels'; import { TextMatcher } from '@/utils/utils'; import { Graph } from '../../../models/graph'; -import { type IOperationPosition } from '../backend/types'; +import { type IOssLayout } from '../backend/types'; import { describeSubstitutionError } from '../labels'; import { type IOperation, type IOperationSchema, SubstitutionErrorType } from './oss'; @@ -494,40 +494,39 @@ export function getRelocateCandidates( export function calculateInsertPosition( oss: IOperationSchema, argumentsOps: number[], - positions: IOperationPosition[], + layout: IOssLayout, defaultPosition: Position2D ): Position2D { const result = defaultPosition; - if (positions.length === 0) { + const operations = layout.operations; + if (operations.length === 0) { return result; } if (argumentsOps.length === 0) { - let inputsPositions = positions.filter(pos => - oss.items.find(operation => operation.arguments.length === 0 && operation.id === pos.id) + let inputsPositions = operations.filter(pos => + oss.operations.find(operation => operation.arguments.length === 0 && operation.id === pos.id) ); if (inputsPositions.length === 0) { - inputsPositions = positions; + inputsPositions = operations; } - const maxX = Math.max(...inputsPositions.map(node => node.position_x)); - const minY = Math.min(...inputsPositions.map(node => node.position_y)); + const maxX = Math.max(...inputsPositions.map(node => node.x)); + const minY = Math.min(...inputsPositions.map(node => node.y)); result.x = maxX + DISTANCE_X; result.y = minY; } else { - const argNodes = positions.filter(pos => argumentsOps.includes(pos.id)); - const maxY = Math.max(...argNodes.map(node => node.position_y)); - const minX = Math.min(...argNodes.map(node => node.position_x)); - const maxX = Math.max(...argNodes.map(node => node.position_x)); + const argNodes = operations.filter(pos => argumentsOps.includes(pos.id)); + const maxY = Math.max(...argNodes.map(node => node.y)); + const minX = Math.min(...argNodes.map(node => node.x)); + const maxX = Math.max(...argNodes.map(node => node.x)); result.x = Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE; result.y = maxY + DISTANCE_Y; } let flagIntersect = false; do { - flagIntersect = positions.some( - position => - Math.abs(position.position_x - result.x) < MIN_DISTANCE && - Math.abs(position.position_y - result.y) < MIN_DISTANCE + flagIntersect = operations.some( + position => Math.abs(position.x - result.x) < MIN_DISTANCE && Math.abs(position.y - result.y) < MIN_DISTANCE ); if (flagIntersect) { result.x += MIN_DISTANCE; diff --git a/rsconcept/frontend/src/features/oss/models/oss.ts b/rsconcept/frontend/src/features/oss/models/oss.ts index b0651a70..cea0da7e 100644 --- a/rsconcept/frontend/src/features/oss/models/oss.ts +++ b/rsconcept/frontend/src/features/oss/models/oss.ts @@ -8,6 +8,8 @@ import { type ICstSubstituteInfo, type IOperationDTO, type IOperationSchemaDTO } /** Represents Operation. */ export interface IOperation extends IOperationDTO { + x: number; + y: number; is_owned: boolean; is_consolidation: boolean; // aka 'diamond synthesis' substitutions: ICstSubstituteInfo[]; @@ -25,7 +27,7 @@ export interface IOperationSchemaStats { /** Represents OperationSchema. */ export interface IOperationSchema extends IOperationSchemaDTO { - items: IOperation[]; + operations: IOperation[]; graph: Graph; schemas: number[]; diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx index a7e3baf9..f29a9474 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx @@ -27,7 +27,7 @@ import { useMutatingOss } from '../../../backend/use-mutating-oss'; import { type IOperation } from '../../../models/oss'; import { useOssEdit } from '../oss-edit-context'; -import { useGetPositions } from './use-get-positions'; +import { useGetLayout } from './use-get-layout'; // pixels - size of OSS context menu const MENU_WIDTH = 200; @@ -49,7 +49,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: const { items: libraryItems } = useLibrary(); const { schema, navigateOperationSchema, isMutable, canDeleteOperation: canDelete } = useOssEdit(); const isProcessing = useMutatingOss(); - const getPositions = useGetPositions(); + const getLayout = useGetLayout(); const { inputCreate } = useInputCreate(); const { operationExecute } = useOperationExecute(); @@ -104,7 +104,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: showEditInput({ oss: schema, target: operation, - positions: getPositions() + layout: getLayout() }); } @@ -116,7 +116,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: showEditOperation({ oss: schema, target: operation, - positions: getPositions() + layout: getLayout() }); } @@ -128,7 +128,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: showDeleteOperation({ oss: schema, target: operation, - positions: getPositions() + layout: getLayout() }); } @@ -139,7 +139,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: onHide(); void operationExecute({ itemID: schema.id, // - data: { target: operation.id, positions: getPositions() } + data: { target: operation.id, layout: getLayout() } }); } @@ -154,7 +154,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: onHide(); void inputCreate({ itemID: schema.id, - data: { target: operation.id, positions: getPositions() } + data: { target: operation.id, layout: getLayout() } }).then(new_schema => router.push({ path: urls.schema(new_schema.id), force: true })); } @@ -166,7 +166,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: showRelocateConstituents({ oss: schema, initialTarget: operation, - positions: getPositions() + layout: getLayout() }); } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx index 23c6a18e..f8a31132 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx @@ -26,7 +26,7 @@ import { useOssEdit } from '../oss-edit-context'; import { OssNodeTypes } from './graph/oss-node-types'; import { type ContextMenuData, NodeContextMenu } from './node-context-menu'; import { ToolbarOssGraph } from './toolbar-oss-graph'; -import { useGetPositions } from './use-get-positions'; +import { useGetLayout } from './use-get-layout'; const ZOOM_MAX = 2; const ZOOM_MIN = 0.5; @@ -52,7 +52,7 @@ export function OssFlow() { const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate); const edgeStraight = useOSSGraphStore(state => state.edgeStraight); - const getPositions = useGetPositions(); + const getLayout = useGetLayout(); const { updateLayout: updatePositions } = useUpdateLayout(); const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -78,10 +78,10 @@ export function OssFlow() { useEffect(() => { setNodes( - schema.items.map(operation => ({ + schema.operations.map(operation => ({ id: String(operation.id), data: { label: operation.alias, operation: operation }, - position: { x: operation.position_x, y: operation.position_y }, + position: { x: operation.x, y: operation.y }, type: operation.operation_type.toString() })) ); @@ -93,8 +93,7 @@ export function OssFlow() { type: edgeStraight ? 'straight' : 'simplebezier', animated: edgeAnimate, targetHandle: - schema.operationByID.get(argument.argument)!.position_x > - schema.operationByID.get(argument.operation)!.position_x + schema.operationByID.get(argument.argument)!.x > schema.operationByID.get(argument.operation)!.x ? 'right' : 'left' })) @@ -103,16 +102,7 @@ export function OssFlow() { }, [schema, setNodes, setEdges, toggleReset, edgeStraight, edgeAnimate, fitView]); function handleSavePositions() { - const positions = getPositions(); - void updatePositions({ itemID: schema.id, positions: positions }).then(() => { - positions.forEach(item => { - const operation = schema.operationByID.get(item.id); - if (operation) { - operation.position_x = item.position_x; - operation.position_y = item.position_y; - } - }); - }); + void updatePositions({ itemID: schema.id, data: getLayout() }); } function handleCreateOperation() { @@ -121,7 +111,7 @@ export function OssFlow() { oss: schema, defaultX: targetPosition.x, defaultY: targetPosition.y, - positions: getPositions(), + layout: getLayout(), initialInputs: selected, onCreate: () => setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout) @@ -139,7 +129,7 @@ export function OssFlow() { showDeleteOperation({ oss: schema, target: operation, - positions: getPositions() + layout: getLayout() }); } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx index a2ce1b64..0124d4dd 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx @@ -34,7 +34,7 @@ import { useOSSGraphStore } from '../../../stores/oss-graph'; import { useOssEdit } from '../oss-edit-context'; import { VIEW_PADDING } from './oss-flow'; -import { useGetPositions } from './use-get-positions'; +import { useGetLayout } from './use-get-layout'; interface ToolbarOssGraphProps extends Styling { onCreate: () => void; @@ -53,7 +53,7 @@ export function ToolbarOssGraph({ const isProcessing = useMutatingOss(); const { fitView } = useReactFlow(); const selectedOperation = schema.operationByID.get(selected[0]); - const getPositions = useGetPositions(); + const getLayout = useGetLayout(); const showGrid = useOSSGraphStore(state => state.showGrid); const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate); @@ -93,16 +93,7 @@ export function ToolbarOssGraph({ } function handleSavePositions() { - const positions = getPositions(); - void updatePositions({ itemID: schema.id, positions: positions }).then(() => { - positions.forEach(item => { - const operation = schema.operationByID.get(item.id); - if (operation) { - operation.position_x = item.position_x; - operation.position_y = item.position_y; - } - }); - }); + void updatePositions({ itemID: schema.id, data: getLayout() }); } function handleOperationExecute() { @@ -111,7 +102,7 @@ export function ToolbarOssGraph({ } void operationExecute({ itemID: schema.id, // - data: { target: selectedOperation.id, positions: getPositions() } + data: { target: selectedOperation.id, layout: getLayout() } }); } @@ -122,7 +113,7 @@ export function ToolbarOssGraph({ showEditOperation({ oss: schema, target: selectedOperation, - positions: getPositions() + layout: getLayout() }); } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-layout.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-layout.tsx new file mode 100644 index 00000000..05114890 --- /dev/null +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-layout.tsx @@ -0,0 +1,17 @@ +import { useReactFlow } from 'reactflow'; + +import { type IOssLayout } from '@/features/oss/backend/types'; + +export function useGetLayout() { + const { getNodes } = useReactFlow(); + return function getLayout(): IOssLayout { + return { + operations: getNodes().map(node => ({ + id: Number(node.id), + x: node.position.x, + y: node.position.y + })), + blocks: [] + }; + }; +} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-positions.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-positions.tsx deleted file mode 100644 index 421885c1..00000000 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-get-positions.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { useReactFlow } from 'reactflow'; - -export function useGetPositions() { - const { getNodes } = useReactFlow(); - return function getPositions() { - return getNodes().map(node => ({ - id: Number(node.id), - position_x: node.position.x, - position_y: node.position.y - })); - }; -} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/menu-edit-oss.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/menu-edit-oss.tsx index 8d941324..49d19c3c 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/menu-edit-oss.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/menu-edit-oss.tsx @@ -21,8 +21,7 @@ export function MenuEditOss() { menu.hide(); showRelocateConstituents({ oss: schema, - initialTarget: undefined, - positions: [] + initialTarget: undefined }); }