R: Restructuring layout data pt2

This commit is contained in:
Ivan 2025-04-06 15:47:40 +03:00
parent 3271d9244c
commit 5efce874b2
27 changed files with 161 additions and 174 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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': [
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': []}
], 'blocks': []
}
self.toggle_admin(True)
self.executeOK(data=data, item=self.unowned_id)

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
}
}),

View File

@ -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,

View File

@ -23,8 +23,8 @@ export type IOperationDTO = z.infer<typeof schemaOperation>;
/** Represents backend data for {@link IOperationSchema}. */
export type IOperationSchemaDTO = z.infer<typeof schemaOperationSchema>;
/** Represents {@link IOperation} position. */
export type IOperationPosition = z.infer<typeof schemaOperationPosition>;
/** Represents {@link schemaOperation} layout. */
export type IOssLayout = z.infer<typeof schemaOssLayout>;
/** Represents {@link IOperation} data, used in creation process. */
export type IOperationCreateDTO = z.infer<typeof schemaOperationCreate>;
@ -35,7 +35,7 @@ export type IOperationCreatedResponse = z.infer<typeof schemaOperationCreatedRes
* Represents target {@link IOperation}.
*/
export interface ITargetOperation {
positions: IOperationPosition[];
layout: IOssLayout;
target: number;
}
@ -69,9 +69,7 @@ export const schemaOperation = z.strictObject({
title: z.string(),
description: z.string(),
position_x: z.number(),
position_y: z.number(),
parent: z.number().nullable(),
result: z.number().nullable()
});
@ -83,9 +81,21 @@ export const schemaCstSubstituteInfo = schemaCstSubstitute.extend({
substitution_term: z.string()
});
export const schemaPosition = z.strictObject({
id: z.number(),
x: z.number(),
y: z.number()
});
export const schemaOssLayout = z.strictObject({
operations: z.array(schemaPosition),
blocks: z.array(schemaPosition)
});
export const schemaOperationSchema = schemaLibraryItem.extend({
editors: z.number().array(),
items: z.array(schemaOperation),
operations: z.array(schemaOperation),
layout: schemaOssLayout,
arguments: z
.object({
operation: z.number(),
@ -95,23 +105,18 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
substitutions: z.array(schemaCstSubstituteInfo)
});
export const schemaOperationPosition = z.strictObject({
id: z.number(),
position_x: z.number(),
position_y: z.number()
});
export const schemaOperationCreate = z.strictObject({
positions: z.array(schemaOperationPosition),
layout: schemaOssLayout,
item_data: z.strictObject({
alias: z.string().nonempty(),
operation_type: schemaOperationType,
title: z.string(),
description: z.string(),
position_x: z.number(),
position_y: z.number(),
parent: z.number().nullable(),
result: z.number().nullable()
}),
position_x: z.number(),
position_y: z.number(),
arguments: z.array(z.number()),
create_schema: z.boolean()
});
@ -123,14 +128,14 @@ export const schemaOperationCreatedResponse = z.strictObject({
export const schemaOperationDelete = z.strictObject({
target: z.number(),
positions: z.array(schemaOperationPosition),
layout: schemaOssLayout,
keep_constituents: z.boolean(),
delete_schema: z.boolean()
});
export const schemaInputUpdate = z.strictObject({
target: z.number(),
positions: z.array(schemaOperationPosition),
layout: schemaOssLayout,
input: z.number().nullable()
});
@ -141,7 +146,7 @@ export const schemaInputCreatedResponse = z.strictObject({
export const schemaOperationUpdate = z.strictObject({
target: z.number(),
positions: z.array(schemaOperationPosition),
layout: schemaOssLayout,
item_data: z.strictObject({
alias: z.string().nonempty(errorMsg.requiredField),
title: z.string(),

View File

@ -14,7 +14,7 @@ export const useOperationUpdate = () => {
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;
}

View File

@ -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)
};

View File

@ -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<IInputUpdateDTO>({
resolver: zodResolver(schemaInputUpdate),
defaultValues: {
target: target.id,
positions: positions,
layout: layout,
input: target.result
}
});

View File

@ -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));
}

View File

@ -50,7 +50,7 @@ export function TabSynthesisOperation() {
name='arguments'
control={control}
render={({ field }) => (
<PickMultiOperation items={oss.items} value={field.value} onChange={field.onChange} rows={6} />
<PickMultiOperation items={oss.operations} value={field.value} onChange={field.onChange} rows={6} />
)}
/>
</div>

View File

@ -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<IOperationDeleteDTO>({
resolver: zodResolver(schemaOperationDelete),
defaultValues: {
target: target.id,
positions: positions,
layout: layout,
keep_constituents: false,
delete_schema: false
}

View File

@ -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<IOperationUpdateDTO>({
@ -55,7 +50,7 @@ export function DlgEditOperation() {
original: sub.original,
substitution: sub.substitution
})),
positions: positions
layout: layout
},
mode: 'onChange'
});

View File

@ -13,7 +13,7 @@ export function TabArguments() {
const { control, setValue } = useFormContext<IOperationUpdateDTO>();
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 });

View File

@ -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));
}
}

View File

@ -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;

View File

@ -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[];

View File

@ -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()
});
}

View File

@ -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()
});
}

View File

@ -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()
});
}

View File

@ -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: []
};
};
}

View File

@ -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
}));
};
}

View File

@ -21,8 +21,7 @@ export function MenuEditOss() {
menu.hide();
showRelocateConstituents({
oss: schema,
initialTarget: undefined,
positions: []
initialTarget: undefined
});
}