mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
F: Improve Graph selection and View retention
This commit is contained in:
parent
935cd42306
commit
54073dbbed
|
@ -79,7 +79,7 @@ export const ossApi = {
|
||||||
successMessage: infoMsg.changesSaved
|
successMessage: infoMsg.changesSaved
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
deleteBlock: ({ itemID, data }: { itemID: number; data: IDeleteBlockDTO }) =>
|
deleteBlock: ({ itemID, data }: { itemID: number; data: IDeleteBlockDTO; beforeUpdate?: () => void }) =>
|
||||||
axiosPatch<IDeleteBlockDTO, IOperationSchemaDTO>({
|
axiosPatch<IDeleteBlockDTO, IOperationSchemaDTO>({
|
||||||
schema: schemaOperationSchema,
|
schema: schemaOperationSchema,
|
||||||
endpoint: `/api/oss/${itemID}/delete-block`,
|
endpoint: `/api/oss/${itemID}/delete-block`,
|
||||||
|
@ -101,7 +101,7 @@ export const ossApi = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
deleteReference: ({ itemID, data }: { itemID: number; data: IDeleteReferenceDTO }) =>
|
deleteReference: ({ itemID, data }: { itemID: number; data: IDeleteReferenceDTO; beforeUpdate?: () => void }) =>
|
||||||
axiosPatch<IDeleteReferenceDTO, IOperationSchemaDTO>({
|
axiosPatch<IDeleteReferenceDTO, IOperationSchemaDTO>({
|
||||||
schema: schemaOperationSchema,
|
schema: schemaOperationSchema,
|
||||||
endpoint: `/api/oss/${itemID}/delete-reference`,
|
endpoint: `/api/oss/${itemID}/delete-reference`,
|
||||||
|
@ -168,7 +168,7 @@ export const ossApi = {
|
||||||
successMessage: infoMsg.changesSaved
|
successMessage: infoMsg.changesSaved
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO }) =>
|
deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO; beforeUpdate?: () => void }) =>
|
||||||
axiosPatch<IDeleteOperationDTO, IOperationSchemaDTO>({
|
axiosPatch<IDeleteOperationDTO, IOperationSchemaDTO>({
|
||||||
schema: schemaOperationSchema,
|
schema: schemaOperationSchema,
|
||||||
endpoint: `/api/oss/${itemID}/delete-operation`,
|
endpoint: `/api/oss/${itemID}/delete-operation`,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
import { ossApi } from './api';
|
import { ossApi } from './api';
|
||||||
import { type IDeleteBlockDTO } from './types';
|
import { type IDeleteBlockDTO } from './types';
|
||||||
|
@ -13,7 +14,11 @@ export const useDeleteBlock = () => {
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-block'],
|
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-block'],
|
||||||
mutationFn: ossApi.deleteBlock,
|
mutationFn: ossApi.deleteBlock,
|
||||||
onSuccess: async data => {
|
onSuccess: async (data, variables) => {
|
||||||
|
if (variables.beforeUpdate) {
|
||||||
|
variables.beforeUpdate();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, PARAMETER.minimalTimeout));
|
||||||
|
}
|
||||||
updateTimestamp(data.id, data.time_update);
|
updateTimestamp(data.id, data.time_update);
|
||||||
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
|
@ -24,6 +29,8 @@ export const useDeleteBlock = () => {
|
||||||
onError: () => client.invalidateQueries()
|
onError: () => client.invalidateQueries()
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
deleteBlock: (data: { itemID: number; data: IDeleteBlockDTO }) => mutation.mutateAsync(data)
|
deleteBlock: (data: { itemID: number; data: IDeleteBlockDTO; beforeUpdate?: () => void }) => {
|
||||||
|
mutation.mutate(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
import { ossApi } from './api';
|
import { ossApi } from './api';
|
||||||
import { type IDeleteOperationDTO } from './types';
|
import { type IDeleteOperationDTO } from './types';
|
||||||
|
@ -13,7 +14,11 @@ export const useDeleteOperation = () => {
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-operation'],
|
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-operation'],
|
||||||
mutationFn: ossApi.deleteOperation,
|
mutationFn: ossApi.deleteOperation,
|
||||||
onSuccess: async data => {
|
onSuccess: async (data, variables) => {
|
||||||
|
if (variables.beforeUpdate) {
|
||||||
|
variables.beforeUpdate();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, PARAMETER.minimalTimeout));
|
||||||
|
}
|
||||||
updateTimestamp(data.id, data.time_update);
|
updateTimestamp(data.id, data.time_update);
|
||||||
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
|
@ -24,6 +29,8 @@ export const useDeleteOperation = () => {
|
||||||
onError: () => client.invalidateQueries()
|
onError: () => client.invalidateQueries()
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
deleteOperation: (data: { itemID: number; data: IDeleteOperationDTO }) => mutation.mutateAsync(data)
|
deleteOperation: (data: { itemID: number; data: IDeleteOperationDTO; beforeUpdate?: () => void }) => {
|
||||||
|
mutation.mutate(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
import { KEYS } from '@/backend/configuration';
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
import { ossApi } from './api';
|
import { ossApi } from './api';
|
||||||
import { type IDeleteReferenceDTO } from './types';
|
import { type IDeleteReferenceDTO } from './types';
|
||||||
|
@ -13,7 +14,11 @@ export const useDeleteReference = () => {
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-reference'],
|
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-reference'],
|
||||||
mutationFn: ossApi.deleteReference,
|
mutationFn: ossApi.deleteReference,
|
||||||
onSuccess: async data => {
|
onSuccess: async (data, variables) => {
|
||||||
|
if (variables.beforeUpdate) {
|
||||||
|
variables.beforeUpdate();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, PARAMETER.minimalTimeout));
|
||||||
|
}
|
||||||
updateTimestamp(data.id, data.time_update);
|
updateTimestamp(data.id, data.time_update);
|
||||||
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
|
@ -24,6 +29,8 @@ export const useDeleteReference = () => {
|
||||||
onError: () => client.invalidateQueries()
|
onError: () => client.invalidateQueries()
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
deleteReference: (data: { itemID: number; data: IDeleteReferenceDTO }) => mutation.mutateAsync(data)
|
deleteReference: (data: { itemID: number; data: IDeleteReferenceDTO; beforeUpdate?: () => void }) => {
|
||||||
|
mutation.mutate(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,10 +17,11 @@ export interface DlgDeleteOperationProps {
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
target: IOperationInput | IOperationSynthesis;
|
target: IOperationInput | IOperationSynthesis;
|
||||||
layout: IOssLayout;
|
layout: IOssLayout;
|
||||||
|
beforeDelete?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DlgDeleteOperation() {
|
export function DlgDeleteOperation() {
|
||||||
const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteOperationProps);
|
const { oss, target, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteOperationProps);
|
||||||
const { deleteOperation } = useDeleteOperation();
|
const { deleteOperation } = useDeleteOperation();
|
||||||
|
|
||||||
const { handleSubmit, control } = useForm<IDeleteOperationDTO>({
|
const { handleSubmit, control } = useForm<IDeleteOperationDTO>({
|
||||||
|
@ -34,7 +35,7 @@ export function DlgDeleteOperation() {
|
||||||
});
|
});
|
||||||
|
|
||||||
function onSubmit(data: IDeleteOperationDTO) {
|
function onSubmit(data: IDeleteOperationDTO) {
|
||||||
return deleteOperation({ itemID: oss.id, data: data });
|
return deleteOperation({ itemID: oss.id, data: data, beforeUpdate: beforeDelete });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -17,10 +17,11 @@ export interface DlgDeleteReferenceProps {
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
target: IOperationReference;
|
target: IOperationReference;
|
||||||
layout: IOssLayout;
|
layout: IOssLayout;
|
||||||
|
beforeDelete?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DlgDeleteReference() {
|
export function DlgDeleteReference() {
|
||||||
const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteReferenceProps);
|
const { oss, target, layout, beforeDelete } = useDialogsStore(state => state.props as DlgDeleteReferenceProps);
|
||||||
const { deleteReference } = useDeleteReference();
|
const { deleteReference } = useDeleteReference();
|
||||||
|
|
||||||
const { handleSubmit, control } = useForm<IDeleteReferenceDTO>({
|
const { handleSubmit, control } = useForm<IDeleteReferenceDTO>({
|
||||||
|
@ -35,7 +36,7 @@ export function DlgDeleteReference() {
|
||||||
const keep_connections = useWatch({ control, name: 'keep_connections' });
|
const keep_connections = useWatch({ control, name: 'keep_connections' });
|
||||||
|
|
||||||
function onSubmit(data: IDeleteReferenceDTO) {
|
function onSubmit(data: IDeleteReferenceDTO) {
|
||||||
return deleteReference({ itemID: oss.id, data: data });
|
return deleteReference({ itemID: oss.id, data: data, beforeUpdate: beforeDelete });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -38,7 +38,7 @@ interface MenuOperationProps {
|
||||||
|
|
||||||
export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
||||||
const { items: libraryItems } = useLibrary();
|
const { items: libraryItems } = useLibrary();
|
||||||
const { schema, setSelected, navigateOperationSchema, isMutable, canDeleteOperation } = useOssEdit();
|
const { schema, setSelected, navigateOperationSchema, isMutable, canDeleteOperation, deselectAll } = useOssEdit();
|
||||||
const isProcessing = useMutatingOss();
|
const isProcessing = useMutatingOss();
|
||||||
const getLayout = useGetLayout();
|
const getLayout = useGetLayout();
|
||||||
|
|
||||||
|
@ -115,7 +115,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
||||||
showDeleteReference({
|
showDeleteReference({
|
||||||
oss: schema,
|
oss: schema,
|
||||||
target: operation,
|
target: operation,
|
||||||
layout: getLayout()
|
layout: getLayout(),
|
||||||
|
beforeDelete: deselectAll
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case OperationType.INPUT:
|
case OperationType.INPUT:
|
||||||
|
@ -123,7 +124,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
||||||
showDeleteOperation({
|
showDeleteOperation({
|
||||||
oss: schema,
|
oss: schema,
|
||||||
target: operation,
|
target: operation,
|
||||||
layout: getLayout()
|
layout: getLayout(),
|
||||||
|
beforeDelete: deselectAll
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
|
||||||
const [containMovement, setContainMovement] = useState(false);
|
const [containMovement, setContainMovement] = useState(false);
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
||||||
|
const prevSelected = useRef<string[]>([]);
|
||||||
|
|
||||||
function onSelectionChange({ nodes }: { nodes: Node[] }) {
|
function onSelectionChange({ nodes }: { nodes: Node[] }) {
|
||||||
const ids = nodes.map(node => node.id);
|
const ids = nodes.map(node => node.id);
|
||||||
|
@ -39,19 +40,21 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
|
||||||
onChange: onSelectionChange
|
onChange: onSelectionChange
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetGraph = useCallback(() => {
|
const reloadData = useCallback(() => {
|
||||||
const newNodes: Node[] = schema.hierarchy.topologicalOrder().map(nodeID => {
|
const newNodes: Node[] = schema.hierarchy.topologicalOrder().map(nodeID => {
|
||||||
const item = schema.itemByNodeID.get(nodeID)!;
|
const item = schema.itemByNodeID.get(nodeID)!;
|
||||||
if (item.nodeType === NodeType.BLOCK) {
|
if (item.nodeType === NodeType.BLOCK) {
|
||||||
return {
|
return {
|
||||||
id: nodeID,
|
id: nodeID,
|
||||||
type: 'block',
|
type: 'block',
|
||||||
|
selected: prevSelected.current.includes(item.nodeID),
|
||||||
data: { label: item.title, block: item },
|
data: { label: item.title, block: item },
|
||||||
position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent),
|
position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent),
|
||||||
style: {
|
style: {
|
||||||
width: item.width,
|
width: item.width,
|
||||||
height: item.height
|
height: item.height
|
||||||
},
|
},
|
||||||
|
|
||||||
parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined,
|
parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined,
|
||||||
zIndex: Z_BLOCK
|
zIndex: Z_BLOCK
|
||||||
};
|
};
|
||||||
|
@ -59,6 +62,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
|
||||||
return {
|
return {
|
||||||
id: item.nodeID,
|
id: item.nodeID,
|
||||||
type: item.operation_type.toString(),
|
type: item.operation_type.toString(),
|
||||||
|
selected: prevSelected.current.includes(item.nodeID),
|
||||||
data: { label: item.alias, operation: item },
|
data: { label: item.alias, operation: item },
|
||||||
position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent),
|
position: computeRelativePosition(schema, { x: item.x, y: item.y }, item.parent),
|
||||||
parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined,
|
parentId: item.parent ? constructNodeID(NodeType.BLOCK, item.parent) : undefined,
|
||||||
|
@ -83,19 +87,23 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
|
||||||
|
|
||||||
setNodes(newNodes);
|
setNodes(newNodes);
|
||||||
setEdges(newEdges);
|
setEdges(newEdges);
|
||||||
|
}, [schema, setNodes, setEdges, edgeAnimate, edgeStraight]);
|
||||||
setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout);
|
|
||||||
}, [schema, setNodes, setEdges, edgeAnimate, edgeStraight, fitView]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetGraph();
|
reloadData();
|
||||||
}, [schema, edgeAnimate, edgeStraight, resetGraph]);
|
}, [schema, edgeAnimate, edgeStraight, reloadData]);
|
||||||
|
|
||||||
function resetView() {
|
function resetView() {
|
||||||
setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout);
|
setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevSelected = useRef<string[]>([]);
|
function resetGraph() {
|
||||||
|
setSelected([]);
|
||||||
|
prevSelected.current = [];
|
||||||
|
reloadData();
|
||||||
|
setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
viewportInitialized &&
|
viewportInitialized &&
|
||||||
(prevSelected.current.length !== selected.length || prevSelected.current.some((id, i) => id !== selected[i]))
|
(prevSelected.current.length !== selected.length || prevSelected.current.some((id, i) => id !== selected[i]))
|
||||||
|
|
|
@ -46,8 +46,16 @@ export const flowOptions = {
|
||||||
|
|
||||||
export function OssFlow() {
|
export function OssFlow() {
|
||||||
const mainHeight = useMainHeight();
|
const mainHeight = useMainHeight();
|
||||||
const { navigateOperationSchema, schema, selected, setSelected, selectedItems, isMutable, canDeleteOperation } =
|
const {
|
||||||
useOssEdit();
|
navigateOperationSchema,
|
||||||
|
schema,
|
||||||
|
selected,
|
||||||
|
setSelected,
|
||||||
|
selectedItems,
|
||||||
|
isMutable,
|
||||||
|
deselectAll,
|
||||||
|
canDeleteOperation
|
||||||
|
} = useOssEdit();
|
||||||
const { screenToFlowPosition } = useReactFlow();
|
const { screenToFlowPosition } = useReactFlow();
|
||||||
const { containMovement, nodes, onNodesChange, edges, onEdgesChange, resetGraph, resetView } = useOssFlow();
|
const { containMovement, nodes, onNodesChange, edges, onEdgesChange, resetGraph, resetView } = useOssFlow();
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
@ -157,7 +165,8 @@ export function OssFlow() {
|
||||||
showDeleteReference({
|
showDeleteReference({
|
||||||
oss: schema,
|
oss: schema,
|
||||||
target: item,
|
target: item,
|
||||||
layout: getLayout()
|
layout: getLayout(),
|
||||||
|
beforeDelete: deselectAll
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case OperationType.INPUT:
|
case OperationType.INPUT:
|
||||||
|
@ -165,14 +174,19 @@ export function OssFlow() {
|
||||||
showDeleteOperation({
|
showDeleteOperation({
|
||||||
oss: schema,
|
oss: schema,
|
||||||
target: item,
|
target: item,
|
||||||
layout: getLayout()
|
layout: getLayout(),
|
||||||
|
beforeDelete: deselectAll
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!window.confirm(promptText.deleteBlock)) {
|
if (!window.confirm(promptText.deleteBlock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void deleteBlock({ itemID: schema.id, data: { target: item.id, layout: getLayout() } });
|
void deleteBlock({
|
||||||
|
itemID: schema.id,
|
||||||
|
data: { target: item.id, layout: getLayout() },
|
||||||
|
beforeUpdate: deselectAll
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user