mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
F: Implement drag change hierarchy
This commit is contained in:
parent
5e6b6fff5b
commit
c25f22f340
|
@ -8,6 +8,7 @@ from .data_access import (
|
||||||
CreateOperationSerializer,
|
CreateOperationSerializer,
|
||||||
DeleteBlockSerializer,
|
DeleteBlockSerializer,
|
||||||
DeleteOperationSerializer,
|
DeleteOperationSerializer,
|
||||||
|
MoveItemsSerializer,
|
||||||
OperationSchemaSerializer,
|
OperationSchemaSerializer,
|
||||||
OperationSerializer,
|
OperationSerializer,
|
||||||
RelocateConstituentsSerializer,
|
RelocateConstituentsSerializer,
|
||||||
|
|
|
@ -132,6 +132,37 @@ class DeleteBlockSerializer(serializers.Serializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class MoveItemsSerializer(serializers.Serializer):
|
||||||
|
''' Serializer: Move items to another parent. '''
|
||||||
|
layout = LayoutSerializer()
|
||||||
|
operations = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'parent'))
|
||||||
|
blocks = PKField(many=True, queryset=Block.objects.all().only('oss_id', 'parent'))
|
||||||
|
destination = PKField(many=False, queryset=Block.objects.all().only('oss_id'), allow_null=True)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
oss = cast(LibraryItem, self.context['oss'])
|
||||||
|
parent_block = cast(Block, attrs['destination'])
|
||||||
|
if parent_block is not None and parent_block.oss_id != oss.pk:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'destination': msg.blockNotInOSS()
|
||||||
|
})
|
||||||
|
for operation in attrs['operations']:
|
||||||
|
if operation.oss_id != oss.pk:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'operations': msg.operationNotInOSS()
|
||||||
|
})
|
||||||
|
for block in attrs['blocks']:
|
||||||
|
if parent_block is not None and block.pk == parent_block.pk:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'destination': msg.blockSelfParent()
|
||||||
|
})
|
||||||
|
if block.oss_id != oss.pk:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'blocks': msg.blockNotInOSS()
|
||||||
|
})
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class CreateOperationSerializer(serializers.Serializer):
|
class CreateOperationSerializer(serializers.Serializer):
|
||||||
''' Serializer: Operation creation. '''
|
''' Serializer: Operation creation. '''
|
||||||
class CreateOperationData(serializers.ModelSerializer):
|
class CreateOperationData(serializers.ModelSerializer):
|
||||||
|
|
|
@ -55,12 +55,13 @@ class TestOssViewset(EndpointTester):
|
||||||
alias='3',
|
alias='3',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
layout = self.owned.layout()
|
self.layout_data = {'operations': [
|
||||||
layout.data = {'operations': [
|
|
||||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||||
], 'blocks': []}
|
], 'blocks': []}
|
||||||
|
layout = self.owned.layout()
|
||||||
|
layout.data = self.layout_data
|
||||||
layout.save()
|
layout.save()
|
||||||
|
|
||||||
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
||||||
|
@ -167,6 +168,43 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(response.data['id'], self.ks1X2.pk)
|
self.assertEqual(response.data['id'], self.ks1X2.pk)
|
||||||
self.assertEqual(response.data['schema'], self.ks1.model.pk)
|
self.assertEqual(response.data['schema'], self.ks1.model.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/move-items', method='patch')
|
||||||
|
def test_move_items(self):
|
||||||
|
self.populateData()
|
||||||
|
self.executeBadData(item=self.owned_id)
|
||||||
|
|
||||||
|
block1 = self.owned.create_block(title='1')
|
||||||
|
block2 = self.owned.create_block(title='2')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'layout': self.layout_data,
|
||||||
|
'blocks': [block2.pk],
|
||||||
|
'operations': [self.operation1.pk, self.operation2.pk],
|
||||||
|
'destination': block2.pk
|
||||||
|
}
|
||||||
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
|
data['destination'] = block1.pk
|
||||||
|
self.executeOK(data=data)
|
||||||
|
self.operation1.refresh_from_db()
|
||||||
|
self.operation2.refresh_from_db()
|
||||||
|
block2.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(self.operation1.parent.pk, block1.pk)
|
||||||
|
self.assertEqual(self.operation2.parent.pk, block1.pk)
|
||||||
|
self.assertEqual(block2.parent.pk, block1.pk)
|
||||||
|
|
||||||
|
data['destination'] = None
|
||||||
|
self.executeOK(data=data)
|
||||||
|
self.operation1.refresh_from_db()
|
||||||
|
self.operation2.refresh_from_db()
|
||||||
|
block2.refresh_from_db()
|
||||||
|
self.assertEqual(block2.parent, None)
|
||||||
|
self.assertEqual(self.operation1.parent, None)
|
||||||
|
self.assertEqual(self.operation2.parent, None)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/relocate-constituents', method='post')
|
@decl_endpoint('/api/oss/relocate-constituents', method='post')
|
||||||
def test_relocate_constituents(self):
|
def test_relocate_constituents(self):
|
||||||
self.populateData()
|
self.populateData()
|
||||||
|
|
|
@ -40,6 +40,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
'create_block',
|
'create_block',
|
||||||
'update_block',
|
'update_block',
|
||||||
'delete_block',
|
'delete_block',
|
||||||
|
'move_items',
|
||||||
'create_operation',
|
'create_operation',
|
||||||
'update_operation',
|
'update_operation',
|
||||||
'delete_operation',
|
'delete_operation',
|
||||||
|
@ -214,6 +215,41 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
data=s.OperationSchemaSerializer(oss.model).data
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='move items',
|
||||||
|
tags=['OSS'],
|
||||||
|
request=s.MoveItemsSerializer(),
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['patch'], url_path='move-items')
|
||||||
|
def move_items(self, request: Request, pk) -> HttpResponse:
|
||||||
|
''' Move items to another parent. '''
|
||||||
|
serializer = s.MoveItemsSerializer(
|
||||||
|
data=request.data,
|
||||||
|
context={'oss': self.get_object()}
|
||||||
|
)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
oss = m.OperationSchema(self.get_object())
|
||||||
|
with transaction.atomic():
|
||||||
|
oss.update_layout(serializer.validated_data['layout'])
|
||||||
|
for operation in serializer.validated_data['operations']:
|
||||||
|
operation.parent = serializer.validated_data['destination']
|
||||||
|
operation.save(update_fields=['parent'])
|
||||||
|
for block in serializer.validated_data['blocks']:
|
||||||
|
block.parent = serializer.validated_data['destination']
|
||||||
|
block.save(update_fields=['parent'])
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='create operation',
|
summary='create operation',
|
||||||
tags=['OSS'],
|
tags=['OSS'],
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
type IDeleteBlockDTO,
|
type IDeleteBlockDTO,
|
||||||
type IDeleteOperationDTO,
|
type IDeleteOperationDTO,
|
||||||
type IInputCreatedResponse,
|
type IInputCreatedResponse,
|
||||||
|
type IMoveItemsDTO,
|
||||||
type IOperationCreatedResponse,
|
type IOperationCreatedResponse,
|
||||||
type IOperationSchemaDTO,
|
type IOperationSchemaDTO,
|
||||||
type IOssLayout,
|
type IOssLayout,
|
||||||
|
@ -138,6 +139,16 @@ export const ossApi = {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
moveItems: ({ itemID, data }: { itemID: number; data: IMoveItemsDTO }) =>
|
||||||
|
axiosPatch<IMoveItemsDTO, IOperationSchemaDTO>({
|
||||||
|
schema: schemaOperationSchema,
|
||||||
|
endpoint: `/api/oss/${itemID}/move-items`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: infoMsg.moveSuccess
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
relocateConstituents: (data: IRelocateConstituentsDTO) =>
|
relocateConstituents: (data: IRelocateConstituentsDTO) =>
|
||||||
axiosPost<IRelocateConstituentsDTO, IOperationSchemaDTO>({
|
axiosPost<IRelocateConstituentsDTO, IOperationSchemaDTO>({
|
||||||
schema: schemaOperationSchema,
|
schema: schemaOperationSchema,
|
||||||
|
|
|
@ -39,6 +39,9 @@ export type IUpdateBlockDTO = z.infer<typeof schemaUpdateBlock>;
|
||||||
/** Represents {@link IBlock} data, used in Delete action. */
|
/** Represents {@link IBlock} data, used in Delete action. */
|
||||||
export type IDeleteBlockDTO = z.infer<typeof schemaDeleteBlock>;
|
export type IDeleteBlockDTO = z.infer<typeof schemaDeleteBlock>;
|
||||||
|
|
||||||
|
/** Represents data, used to move {@link IOssItem} to another parent. */
|
||||||
|
export type IMoveItemsDTO = z.infer<typeof schemaMoveItems>;
|
||||||
|
|
||||||
/** Represents {@link IOperation} data, used in Create action. */
|
/** Represents {@link IOperation} data, used in Create action. */
|
||||||
export type ICreateOperationDTO = z.infer<typeof schemaCreateOperation>;
|
export type ICreateOperationDTO = z.infer<typeof schemaCreateOperation>;
|
||||||
|
|
||||||
|
@ -208,6 +211,13 @@ export const schemaDeleteOperation = z.strictObject({
|
||||||
delete_schema: z.boolean()
|
delete_schema: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const schemaMoveItems = z.strictObject({
|
||||||
|
layout: schemaOssLayout,
|
||||||
|
operations: z.array(z.number()),
|
||||||
|
blocks: z.array(z.number()),
|
||||||
|
destination: z.number().nullable()
|
||||||
|
});
|
||||||
|
|
||||||
export const schemaUpdateInput = z.strictObject({
|
export const schemaUpdateInput = z.strictObject({
|
||||||
target: z.number(),
|
target: z.number(),
|
||||||
layout: schemaOssLayout,
|
layout: schemaOssLayout,
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
|
||||||
|
import { ossApi } from './api';
|
||||||
|
import { type IMoveItemsDTO } from './types';
|
||||||
|
|
||||||
|
export const useMoveItems = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'move-items'],
|
||||||
|
mutationFn: ossApi.moveItems,
|
||||||
|
onSuccess: data => {
|
||||||
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
return Promise.allSettled([
|
||||||
|
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
|
||||||
|
client.invalidateQueries({ queryKey: [KEYS.rsform] })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
onError: () => client.invalidateQueries()
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
moveItems: (data: { itemID: number; data: IMoveItemsDTO }) => mutation.mutateAsync(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -18,7 +18,7 @@ export const BLOCK_NODE_MIN_HEIGHT = 100;
|
||||||
|
|
||||||
export function BlockNode(node: BlockInternalNode) {
|
export function BlockNode(node: BlockInternalNode) {
|
||||||
const { selected, schema } = useOssEdit();
|
const { selected, schema } = useOssEdit();
|
||||||
const { dropTarget } = useOssFlow();
|
const { dropTarget, isDragging } = useOssFlow();
|
||||||
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
|
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
|
||||||
const setHover = useOperationTooltipStore(state => state.setHoverItem);
|
const setHover = useOperationTooltipStore(state => state.setHoverItem);
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ export function BlockNode(node: BlockInternalNode) {
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'cc-node-block h-full w-full',
|
'cc-node-block h-full w-full',
|
||||||
dropTarget && isParent && dropTarget !== node.data.block.id && 'border-destructive',
|
isDragging && isParent && dropTarget !== node.data.block.id && 'border-destructive',
|
||||||
((isParent && !dropTarget) || dropTarget === node.data.block.id) && 'border-primary',
|
((isParent && !isDragging) || dropTarget === node.data.block.id) && 'border-primary',
|
||||||
isChild && 'border-accent-orange50'
|
isChild && 'border-accent-orange50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
import { createContext, use } from 'react';
|
import { createContext, use } from 'react';
|
||||||
|
|
||||||
interface IOssFlowContext {
|
interface IOssFlowContext {
|
||||||
|
isDragging: boolean;
|
||||||
|
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
dropTarget: number | null;
|
dropTarget: number | null;
|
||||||
setDropTarget: React.Dispatch<React.SetStateAction<number | null>>;
|
setDropTarget: React.Dispatch<React.SetStateAction<number | null>>;
|
||||||
containMovement: boolean;
|
containMovement: boolean;
|
||||||
|
|
|
@ -5,12 +5,16 @@ import { useState } from 'react';
|
||||||
import { OssFlowContext } from './oss-flow-context';
|
import { OssFlowContext } from './oss-flow-context';
|
||||||
|
|
||||||
export const OssFlowState = ({ children }: React.PropsWithChildren) => {
|
export const OssFlowState = ({ children }: React.PropsWithChildren) => {
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [dropTarget, setDropTarget] = useState<number | null>(null);
|
const [dropTarget, setDropTarget] = useState<number | null>(null);
|
||||||
const [containMovement, setContainMovement] = useState(false);
|
const [containMovement, setContainMovement] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OssFlowContext
|
<OssFlowContext
|
||||||
value={{
|
value={{
|
||||||
|
isDragging,
|
||||||
|
setIsDragging,
|
||||||
|
|
||||||
dropTarget,
|
dropTarget,
|
||||||
setDropTarget,
|
setDropTarget,
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { useDeleteBlock } from '@/features/oss/backend/use-delete-block';
|
import { useDeleteBlock } from '@/features/oss/backend/use-delete-block';
|
||||||
|
import { useMoveItems } from '@/features/oss/backend/use-move-items';
|
||||||
import { type IOperationSchema } from '@/features/oss/models/oss';
|
import { type IOperationSchema } from '@/features/oss/models/oss';
|
||||||
|
|
||||||
import { useMainHeight } from '@/stores/app-layout';
|
import { useMainHeight } from '@/stores/app-layout';
|
||||||
|
@ -54,7 +55,7 @@ export function OssFlow() {
|
||||||
canDeleteOperation: canDelete
|
canDeleteOperation: canDelete
|
||||||
} = useOssEdit();
|
} = useOssEdit();
|
||||||
const { fitView, screenToFlowPosition, getIntersectingNodes } = useReactFlow();
|
const { fitView, screenToFlowPosition, getIntersectingNodes } = useReactFlow();
|
||||||
const { setDropTarget, setContainMovement, containMovement } = useOssFlow();
|
const { setDropTarget, setContainMovement, containMovement, setIsDragging } = useOssFlow();
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { resetSelectedElements, addSelectedNodes } = store.getState();
|
const { resetSelectedElements, addSelectedNodes } = store.getState();
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ export function OssFlow() {
|
||||||
const getLayout = useGetLayout();
|
const getLayout = useGetLayout();
|
||||||
const { updateLayout } = useUpdateLayout();
|
const { updateLayout } = useUpdateLayout();
|
||||||
const { deleteBlock } = useDeleteBlock();
|
const { deleteBlock } = useDeleteBlock();
|
||||||
|
const { moveItems } = useMoveItems();
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
|
@ -265,6 +267,35 @@ export function OssFlow() {
|
||||||
setMouseCoords(targetPosition);
|
setMouseCoords(targetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function determineDropTarget(event: React.MouseEvent): number | null {
|
||||||
|
const mousePosition = screenToFlowPosition({ x: event.clientX, y: event.clientY });
|
||||||
|
const blocks = getIntersectingNodes({
|
||||||
|
x: mousePosition.x,
|
||||||
|
y: mousePosition.y,
|
||||||
|
width: 1,
|
||||||
|
height: 1
|
||||||
|
})
|
||||||
|
.map(node => Number(node.id))
|
||||||
|
.filter(id => id < 0 && !selected.includes(id))
|
||||||
|
.map(id => schema.blockByID.get(-id))
|
||||||
|
.filter(block => !!block);
|
||||||
|
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (blocks.length === 1) {
|
||||||
|
return blocks[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parents = blocks.map(block => block.parent).filter(id => !!id);
|
||||||
|
const potentialTargets = blocks.map(block => block.id).filter(id => !parents.includes(id));
|
||||||
|
if (potentialTargets.length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return potentialTargets[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleDragStart(event: React.MouseEvent, target: Node) {
|
function handleDragStart(event: React.MouseEvent, target: Node) {
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
setContainMovement(true);
|
setContainMovement(true);
|
||||||
|
@ -281,6 +312,7 @@ export function OssFlow() {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setContainMovement(false);
|
setContainMovement(false);
|
||||||
|
setDropTarget(determineDropTarget(event));
|
||||||
}
|
}
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
}
|
}
|
||||||
|
@ -289,38 +321,11 @@ export function OssFlow() {
|
||||||
if (containMovement) {
|
if (containMovement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mousePosition = screenToFlowPosition({ x: event.clientX, y: event.clientY });
|
setIsDragging(true);
|
||||||
const blocks = getIntersectingNodes({
|
setDropTarget(determineDropTarget(event));
|
||||||
x: mousePosition.x,
|
|
||||||
y: mousePosition.y,
|
|
||||||
width: 1,
|
|
||||||
height: 1
|
|
||||||
})
|
|
||||||
.map(node => Number(node.id))
|
|
||||||
.filter(id => id < 0)
|
|
||||||
.map(id => schema.blockByID.get(-id))
|
|
||||||
.filter(block => !!block);
|
|
||||||
|
|
||||||
if (blocks.length === 0) {
|
|
||||||
setDropTarget(null);
|
|
||||||
return;
|
|
||||||
} else if (blocks.length === 1) {
|
|
||||||
setDropTarget(blocks[0].id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parents = blocks.map(block => block.parent).filter(id => !!id);
|
|
||||||
const potentialTargets = blocks.map(block => block.id).filter(id => !parents.includes(id));
|
|
||||||
if (potentialTargets.length === 0) {
|
|
||||||
setDropTarget(null);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setDropTarget(potentialTargets[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragStop(_: React.MouseEvent, target: Node) {
|
function handleDragStop(event: React.MouseEvent, target: Node) {
|
||||||
if (containMovement) {
|
if (containMovement) {
|
||||||
setNodes(prev =>
|
setNodes(prev =>
|
||||||
prev.map(node =>
|
prev.map(node =>
|
||||||
|
@ -334,8 +339,32 @@ export function OssFlow() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// TODO: process drop event
|
const new_parent = determineDropTarget(event);
|
||||||
|
const allSelected = [...selected.filter(id => id != Number(target.id)), Number(target.id)];
|
||||||
|
const operations = allSelected
|
||||||
|
.filter(id => id > 0)
|
||||||
|
.map(id => schema.operationByID.get(id))
|
||||||
|
.filter(operation => !!operation);
|
||||||
|
const blocks = allSelected
|
||||||
|
.filter(id => id < 0)
|
||||||
|
.map(id => schema.blockByID.get(-id))
|
||||||
|
.filter(operation => !!operation);
|
||||||
|
const parents = [...blocks.map(block => block.parent), ...operations.map(operation => operation.parent)].filter(
|
||||||
|
id => !!id
|
||||||
|
);
|
||||||
|
if ((parents.length !== 1 || parents[0] !== new_parent) && (parents.length !== 0 || new_parent !== null)) {
|
||||||
|
void moveItems({
|
||||||
|
itemID: schema.id,
|
||||||
|
data: {
|
||||||
|
layout: getLayout(),
|
||||||
|
operations: operations.map(operation => operation.id),
|
||||||
|
blocks: blocks.map(block => block.id),
|
||||||
|
destination: new_parent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
setIsDragging(false);
|
||||||
setContainMovement(false);
|
setContainMovement(false);
|
||||||
setDropTarget(null);
|
setDropTarget(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export const infoMsg = {
|
||||||
substitutionsCorrect: 'Таблица отождествлений прошла проверку',
|
substitutionsCorrect: 'Таблица отождествлений прошла проверку',
|
||||||
uploadSuccess: 'Схема загружена из файла',
|
uploadSuccess: 'Схема загружена из файла',
|
||||||
inlineSynthesisComplete: 'Встраивание завершено',
|
inlineSynthesisComplete: 'Встраивание завершено',
|
||||||
|
moveSuccess: 'Перемещение завершено',
|
||||||
|
|
||||||
newLibraryItem: 'Схема успешно создана',
|
newLibraryItem: 'Схема успешно создана',
|
||||||
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
|
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user