Improve OSS frontend
This commit is contained in:
parent
f277ce288b
commit
8977c0fadc
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
|||
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import Constituenta, LibraryItem, RSForm
|
||||
from ..models import Constituenta, RSForm
|
||||
from ..utils import fix_old_references
|
||||
|
||||
_CST_TYPE = 'constituenta'
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
ICstSubstituteData,
|
||||
IOperationCreateData,
|
||||
IOperationCreatedResponse,
|
||||
IOperationSchemaData
|
||||
IOperationSchemaData,
|
||||
IPositionsData,
|
||||
ITargetOperation
|
||||
} from '@/models/oss';
|
||||
import {
|
||||
IConstituentaList,
|
||||
|
@ -424,6 +426,13 @@ export function getOssDetails(target: string, request: FrontPull<IOperationSchem
|
|||
});
|
||||
}
|
||||
|
||||
export function patchUpdatePositions(schema: string, request: FrontPush<IPositionsData>) {
|
||||
AxiosPatch({
|
||||
endpoint: `/api/oss/${schema}/update-positions`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postCreateOperation(
|
||||
schema: string,
|
||||
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
||||
|
@ -434,6 +443,13 @@ export function postCreateOperation(
|
|||
});
|
||||
}
|
||||
|
||||
export function patchDeleteOperation(schema: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||
AxiosPatch({
|
||||
endpoint: `/api/oss/${schema}/delete-operation`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
||||
AxiosPost({
|
||||
endpoint: `/api/cctext/inflect`,
|
||||
|
|
|
@ -5,11 +5,13 @@ import { createContext, useCallback, useContext, useMemo, useState } from 'react
|
|||
import {
|
||||
type DataCallback,
|
||||
deleteUnsubscribe,
|
||||
patchDeleteOperation,
|
||||
patchEditorsSet as patchSetEditors,
|
||||
patchLibraryItem,
|
||||
patchSetAccessPolicy,
|
||||
patchSetLocation,
|
||||
patchSetOwner,
|
||||
patchUpdatePositions,
|
||||
postCreateOperation,
|
||||
postSubscribe
|
||||
} from '@/app/backendAPI';
|
||||
|
@ -17,7 +19,7 @@ import { type ErrorData } from '@/components/info/InfoError';
|
|||
import useOssDetails from '@/hooks/useOssDetails';
|
||||
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
||||
import { ILibraryUpdateData } from '@/models/library';
|
||||
import { IOperation, IOperationCreateData, IOperationSchema } from '@/models/oss';
|
||||
import { IOperation, IOperationCreateData, IOperationSchema, IPositionsData, ITargetOperation } from '@/models/oss';
|
||||
import { UserID } from '@/models/user';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
|
@ -45,7 +47,9 @@ interface IOssContext {
|
|||
setLocation: (newLocation: string, callback?: () => void) => void;
|
||||
setEditors: (newEditors: UserID[], callback?: () => void) => void;
|
||||
|
||||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
||||
}
|
||||
|
||||
const OssContext = createContext<IOssContext | null>(null);
|
||||
|
@ -250,6 +254,23 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
[itemID, schema]
|
||||
);
|
||||
|
||||
const savePositions = useCallback(
|
||||
(data: IPositionsData, callback?: () => void) => {
|
||||
setProcessingError(undefined);
|
||||
patchUpdatePositions(itemID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
library.localUpdateTimestamp(Number(itemID));
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library]
|
||||
);
|
||||
|
||||
const createOperation = useCallback(
|
||||
(data: IOperationCreateData, callback?: DataCallback<IOperation>) => {
|
||||
setProcessingError(undefined);
|
||||
|
@ -268,6 +289,24 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
[itemID, library, setSchema]
|
||||
);
|
||||
|
||||
const deleteOperation = useCallback(
|
||||
(data: ITargetOperation, callback?: () => void) => {
|
||||
setProcessingError(undefined);
|
||||
patchDeleteOperation(itemID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library, setSchema]
|
||||
);
|
||||
|
||||
return (
|
||||
<OssContext.Provider
|
||||
value={{
|
||||
|
@ -288,7 +327,9 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setAccessPolicy,
|
||||
setLocation,
|
||||
|
||||
createOperation
|
||||
savePositions,
|
||||
createOperation,
|
||||
deleteOperation
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -41,16 +41,29 @@ export interface IOperation {
|
|||
*/
|
||||
export interface IOperationPosition extends Pick<IOperation, 'id' | 'position_x' | 'position_y'> {}
|
||||
|
||||
/**
|
||||
* Represents all {@link IOperation} positions in {@link IOperationSchema}.
|
||||
*/
|
||||
export interface IPositionsData {
|
||||
positions: IOperationPosition[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents target {@link IOperation}.
|
||||
*/
|
||||
export interface ITargetOperation extends IPositionsData {
|
||||
target: OperationID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} data, used in creation process.
|
||||
*/
|
||||
export interface IOperationCreateData {
|
||||
export interface IOperationCreateData extends IPositionsData {
|
||||
item_data: Pick<
|
||||
IOperation,
|
||||
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result'
|
||||
>;
|
||||
arguments: OperationID[] | undefined;
|
||||
positions: IOperationPosition[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,10 +4,15 @@ import { ReactFlowProvider } from 'reactflow';
|
|||
|
||||
import OssFlow from './OssFlow';
|
||||
|
||||
function EditorOssGraph() {
|
||||
interface EditorOssGraphProps {
|
||||
isModified: boolean;
|
||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<OssFlow />
|
||||
<OssFlow isModified={isModified} setIsModified={setIsModified} />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { CiSquareRemove } from 'react-icons/ci';
|
||||
import { PiPlugsConnected } from 'react-icons/pi';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { IconDestroy, IconEdit2 } from '@/components/Icons';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
|
@ -21,27 +20,30 @@ function InputNode({ id, data }: InputNodeProps) {
|
|||
console.log('delete node ' + id);
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
const handleEditOperation = () => {
|
||||
console.log('edit operation ' + id);
|
||||
//controller.selectNode(id);
|
||||
// controller.showSelectInput();
|
||||
//controller.showSynthesis();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Handle type='target' position={Position.Bottom} />
|
||||
<div className='flex justify-between'>
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex-grow text-center'>{data.label}</div>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
icon={<PiPlugsConnected className='icon-green' />}
|
||||
title='Привязать схему'
|
||||
icon={<IconEdit2 className='icon-primary' size='0.75rem' />}
|
||||
noPadding
|
||||
title='Редактировать'
|
||||
onClick={() => {
|
||||
handleClick();
|
||||
handleEditOperation();
|
||||
}}
|
||||
/>
|
||||
<MiniButton
|
||||
icon={<CiSquareRemove className='icon-red' size='1rem' />}
|
||||
title='Удалить'
|
||||
noPadding
|
||||
icon={<IconDestroy className='icon-red' size='0.75rem' />}
|
||||
title='Удалить операцию'
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { CiSquareRemove } from 'react-icons/ci';
|
||||
import { IoGitNetworkSharp } from 'react-icons/io5';
|
||||
import { VscDebugStart } from 'react-icons/vsc';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
import { IconDestroy, IconEdit2 } from '@/components/Icons';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
interface OperationNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
};
|
||||
}
|
||||
|
||||
function OperationNode({ id }: OperationNodeProps) {
|
||||
function OperationNode({ id, data }: OperationNodeProps) {
|
||||
const controller = useOssEdit();
|
||||
console.log(controller.isMutable);
|
||||
|
||||
|
@ -32,37 +34,33 @@ function OperationNode({ id }: OperationNodeProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Handle type='target' position={Position.Bottom} />
|
||||
<div>
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex-grow text-center'>{data.label}</div>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
className='float-right'
|
||||
icon={<CiSquareRemove className='icon-red' />}
|
||||
title='Удалить'
|
||||
onClick={handleDelete}
|
||||
color={'red'}
|
||||
icon={<IconEdit2 className='icon-primary' size='1rem' />}
|
||||
title='Редактировать'
|
||||
onClick={() => {
|
||||
handleEditOperation();
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
Тип: <strong>Отождествление</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема: <strong></strong>
|
||||
<MiniButton
|
||||
className='float-right'
|
||||
icon={<VscDebugStart className='icon-green' />}
|
||||
icon={<VscDebugStart className='icon-green' size='1rem' />}
|
||||
title='Синтез'
|
||||
onClick={() => handleRunOperation()}
|
||||
/>
|
||||
<MiniButton
|
||||
className='float-right'
|
||||
icon={<IoGitNetworkSharp className='icon-green' />}
|
||||
title='Отождествления'
|
||||
onClick={() => handleEditOperation()}
|
||||
icon={<IconDestroy className='icon-red' size='1rem' />}
|
||||
title='Удалить операцию'
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Handle type='source' position={Position.Top} id='a' style={{ left: 50 }} />
|
||||
<Handle type='source' position={Position.Top} id='b' style={{ right: 50, left: 'auto' }} />
|
||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||
<Handle type='target' position={Position.Top} id='right' style={{ right: 40, left: 'auto' }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,42 +1,61 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useLayoutEffect, useMemo } from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EdgeChange,
|
||||
Node,
|
||||
NodeChange,
|
||||
NodeTypes,
|
||||
ProOptions,
|
||||
ReactFlow,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useViewport
|
||||
useOnSelectionChange,
|
||||
useReactFlow
|
||||
} from 'reactflow';
|
||||
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
import InputNode from './InputNode';
|
||||
import OperationNode from './OperationNode';
|
||||
import ToolbarOssGraph from './ToolbarOssGraph';
|
||||
|
||||
function OssFlow() {
|
||||
interface OssFlowProps {
|
||||
isModified: boolean;
|
||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const model = useOSS();
|
||||
const controller = useOssEdit();
|
||||
const viewport = useViewport();
|
||||
const flow = useReactFlow();
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
||||
const onSelectionChange = useCallback(
|
||||
({ nodes }: { nodes: Node[] }) => {
|
||||
controller.setSelected(nodes.map(node => Number(node.id)));
|
||||
console.log(nodes);
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
||||
useOnSelectionChange({
|
||||
onChange: onSelectionChange
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!model.schema) {
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setNodes(
|
||||
model.schema.items.map(operation => ({
|
||||
id: String(operation.id),
|
||||
|
@ -49,10 +68,19 @@ function OssFlow() {
|
|||
model.schema.arguments.map((argument, index) => ({
|
||||
id: String(index),
|
||||
source: String(argument.argument),
|
||||
target: String(argument.operation)
|
||||
target: String(argument.operation),
|
||||
targetHandle:
|
||||
model.schema!.operationByID.get(argument.argument)!.position_x >
|
||||
model.schema!.operationByID.get(argument.operation)!.position_x
|
||||
? 'right'
|
||||
: 'left'
|
||||
}))
|
||||
);
|
||||
}, [model.schema, setNodes, setEdges]);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setIsModified(false);
|
||||
}, PARAMETER.graphRefreshDelay);
|
||||
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset]);
|
||||
|
||||
const getPositions = useCallback(
|
||||
() =>
|
||||
|
@ -66,22 +94,46 @@ function OssFlow() {
|
|||
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => {
|
||||
if (changes.some(change => change.type === 'position' && change.position)) {
|
||||
setIsModified(true);
|
||||
}
|
||||
onNodesChange(changes);
|
||||
},
|
||||
[onNodesChange]
|
||||
[onNodesChange, setIsModified]
|
||||
);
|
||||
|
||||
const handleEdgesChange = useCallback(
|
||||
(changes: EdgeChange[]) => {
|
||||
onEdgesChange(changes);
|
||||
},
|
||||
[onEdgesChange]
|
||||
);
|
||||
const handleSavePositions = useCallback(() => {
|
||||
controller.savePositions(getPositions(), () => setIsModified(false));
|
||||
}, [controller, getPositions, setIsModified]);
|
||||
|
||||
const handleCreateOperation = useCallback(() => {
|
||||
// TODO: calculate insert location
|
||||
controller.promptCreateOperation(viewport.x, viewport.y, getPositions());
|
||||
}, [controller, viewport, getPositions]);
|
||||
const center = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
console.log(center);
|
||||
controller.promptCreateOperation(center.x, center.y, getPositions());
|
||||
}, [controller, getPositions, flow]);
|
||||
|
||||
const handleDeleteOperation = useCallback(() => {
|
||||
if (controller.selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
controller.deleteOperation(controller.selected[0], getPositions());
|
||||
}, [controller, getPositions]);
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
// Hotkeys implementation
|
||||
if (controller.isProcessing) {
|
||||
return;
|
||||
}
|
||||
if (!controller.isMutable) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Delete') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleDeleteOperation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const proOptions: ProOptions = useMemo(() => ({ hideAttribution: true }), []);
|
||||
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
|
||||
|
@ -101,21 +153,29 @@ function OssFlow() {
|
|||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={handleEdgesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
fitView
|
||||
proOptions={proOptions}
|
||||
nodeTypes={OssNodeTypes}
|
||||
maxZoom={2}
|
||||
minZoom={0.75}
|
||||
nodesConnectable={false}
|
||||
/>
|
||||
),
|
||||
[nodes, edges, proOptions, handleNodesChange, handleEdgesChange, OssNodeTypes]
|
||||
[nodes, edges, proOptions, handleNodesChange, onEdgesChange, OssNodeTypes]
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimateFade>
|
||||
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
|
||||
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
|
||||
<ToolbarOssGraph onCreate={handleCreateOperation} />
|
||||
<ToolbarOssGraph
|
||||
isModified={isModified}
|
||||
onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })}
|
||||
onCreate={handleCreateOperation}
|
||||
onDelete={handleDeleteOperation}
|
||||
onResetPositions={() => setToggleReset(prev => !prev)}
|
||||
onSavePositions={handleSavePositions}
|
||||
/>
|
||||
</Overlay>
|
||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||
{graph}
|
||||
|
|
|
@ -1,22 +1,56 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { IconNewItem } from '@/components/Icons';
|
||||
import { IconDestroy, IconFitImage, IconNewItem, IconReset, IconSave } from '@/components/Icons';
|
||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { prepareTooltip } from '@/utils/labels';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
|
||||
interface ToolbarOssGraphProps {
|
||||
isModified: boolean;
|
||||
onCreate: () => void;
|
||||
onDelete: () => void;
|
||||
onFitView: () => void;
|
||||
onSavePositions: () => void;
|
||||
onResetPositions: () => void;
|
||||
}
|
||||
|
||||
function ToolbarOssGraph({ onCreate }: ToolbarOssGraphProps) {
|
||||
function ToolbarOssGraph({
|
||||
isModified,
|
||||
onCreate,
|
||||
onDelete,
|
||||
onFitView,
|
||||
onSavePositions,
|
||||
onResetPositions
|
||||
}: ToolbarOssGraphProps) {
|
||||
const controller = useOssEdit();
|
||||
|
||||
return (
|
||||
<div className='cc-icons'>
|
||||
{controller.isMutable ? (
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.isProcessing || !isModified}
|
||||
onClick={onSavePositions}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable ? (
|
||||
<MiniButton
|
||||
title='Сбросить изменения'
|
||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||
disabled={!isModified}
|
||||
onClick={onResetPositions}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||
title='Сбросить вид'
|
||||
onClick={onFitView}
|
||||
/>
|
||||
{controller.isMutable ? (
|
||||
<MiniButton
|
||||
title='Новая операция'
|
||||
|
@ -25,6 +59,14 @@ function ToolbarOssGraph({ onCreate }: ToolbarOssGraphProps) {
|
|||
onClick={onCreate}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable ? (
|
||||
<MiniButton
|
||||
title='Удалить выбранную'
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
) : null}
|
||||
<BadgeHelp
|
||||
topic={HelpTopic.UI_OSS_GRAPH}
|
||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||
|
|
|
@ -13,12 +13,13 @@ import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
|||
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
||||
import { AccessPolicy } from '@/models/library';
|
||||
import { Position2D } from '@/models/miscellaneous';
|
||||
import { IOperationCreateData, IOperationPosition, IOperationSchema } from '@/models/oss';
|
||||
import { IOperationCreateData, IOperationPosition, IOperationSchema, OperationID } from '@/models/oss';
|
||||
import { UserID, UserLevel } from '@/models/user';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
export interface IOssEditContext {
|
||||
schema?: IOperationSchema;
|
||||
selected: OperationID[];
|
||||
|
||||
isMutable: boolean;
|
||||
isProcessing: boolean;
|
||||
|
@ -29,9 +30,13 @@ export interface IOssEditContext {
|
|||
promptLocation: () => void;
|
||||
toggleSubscribe: () => void;
|
||||
|
||||
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||
|
||||
share: () => void;
|
||||
|
||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
}
|
||||
|
||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||
|
@ -45,10 +50,12 @@ export const useOssEdit = () => {
|
|||
|
||||
interface OssEditStateProps {
|
||||
// isModified: boolean;
|
||||
selected: OperationID[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const OssEditState = ({ children }: OssEditStateProps) => {
|
||||
export const OssEditState = ({ selected, setSelected, children }: OssEditStateProps) => {
|
||||
// const router = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
const { adminMode } = useConceptOptions();
|
||||
|
@ -144,6 +151,23 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
|||
[model]
|
||||
);
|
||||
|
||||
const savePositions = useCallback(
|
||||
(positions: IOperationPosition[], callback?: () => void) => {
|
||||
model.savePositions({ positions: positions }, () => {
|
||||
positions.forEach(item => {
|
||||
const operation = model.schema?.operationByID.get(item.id);
|
||||
if (operation) {
|
||||
operation.position_x = item.position_x;
|
||||
operation.position_y = item.position_y;
|
||||
}
|
||||
});
|
||||
toast.success(information.changesSaved);
|
||||
if (callback) callback();
|
||||
});
|
||||
},
|
||||
[model]
|
||||
);
|
||||
|
||||
const promptCreateOperation = useCallback((x: number, y: number, positions: IOperationPosition[]) => {
|
||||
setInsertPosition({ x: x, y: y });
|
||||
setPositions(positions);
|
||||
|
@ -157,10 +181,21 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
|||
[model]
|
||||
);
|
||||
|
||||
const deleteOperation = useCallback(
|
||||
(target: OperationID, positions: IOperationPosition[]) => {
|
||||
model.deleteOperation({ target: target, positions: positions }, () =>
|
||||
toast.success(information.operationDestroyed)
|
||||
);
|
||||
},
|
||||
[model]
|
||||
);
|
||||
|
||||
return (
|
||||
<OssEditContext.Provider
|
||||
value={{
|
||||
schema: model.schema,
|
||||
selected,
|
||||
|
||||
isMutable,
|
||||
isProcessing: model.processing,
|
||||
|
||||
|
@ -171,8 +206,11 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
|||
promptLocation,
|
||||
|
||||
share,
|
||||
setSelected,
|
||||
|
||||
promptCreateOperation
|
||||
savePositions,
|
||||
promptCreateOperation,
|
||||
deleteOperation
|
||||
}}
|
||||
>
|
||||
{model.schema ? (
|
||||
|
|
|
@ -17,6 +17,7 @@ import { useLibrary } from '@/context/LibraryContext';
|
|||
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import useQueryStrings from '@/hooks/useQueryStrings';
|
||||
import { OperationID } from '@/models/oss';
|
||||
import { information, prompts } from '@/utils/labels';
|
||||
|
||||
import EditorRSForm from './EditorOssCard';
|
||||
|
@ -39,6 +40,7 @@ function OssTabs() {
|
|||
const { destroyItem } = useLibrary();
|
||||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const [selected, setSelected] = useState<OperationID[]>([]);
|
||||
useBlockNavigation(isModified);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
@ -112,14 +114,14 @@ function OssTabs() {
|
|||
const graphPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<EditorTermGraph />
|
||||
<EditorTermGraph isModified={isModified} setIsModified={setIsModified} />
|
||||
</TabPanel>
|
||||
),
|
||||
[]
|
||||
[isModified]
|
||||
);
|
||||
|
||||
return (
|
||||
<OssEditState>
|
||||
<OssEditState selected={selected} setSelected={setSelected}>
|
||||
{loading ? <Loader /> : null}
|
||||
{errorLoading ? <ProcessError error={errorLoading} /> : null}
|
||||
{schema && !loading ? (
|
||||
|
|
|
@ -311,7 +311,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
showParamsDialog={() => setShowParamsDialog(true)}
|
||||
onCreate={handleCreateCst}
|
||||
onDelete={handleDeleteCst}
|
||||
onResetViewpoint={() => setToggleResetView(prev => !prev)}
|
||||
onFitView={() => setToggleResetView(prev => !prev)}
|
||||
onSaveImage={handleSaveImage}
|
||||
toggleOrbit={() => setOrbit(prev => !prev)}
|
||||
toggleFoldDerived={handleFoldDerived}
|
||||
|
|
|
@ -29,7 +29,7 @@ interface ToolbarTermGraphProps {
|
|||
showParamsDialog: () => void;
|
||||
onCreate: () => void;
|
||||
onDelete: () => void;
|
||||
onResetViewpoint: () => void;
|
||||
onFitView: () => void;
|
||||
onSaveImage: () => void;
|
||||
|
||||
toggleFoldDerived: () => void;
|
||||
|
@ -48,7 +48,7 @@ function ToolbarTermGraph({
|
|||
showParamsDialog,
|
||||
onCreate,
|
||||
onDelete,
|
||||
onResetViewpoint,
|
||||
onFitView,
|
||||
onSaveImage
|
||||
}: ToolbarTermGraphProps) {
|
||||
const controller = useRSEdit();
|
||||
|
@ -63,7 +63,7 @@ function ToolbarTermGraph({
|
|||
<MiniButton
|
||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||
title='Граф целиком'
|
||||
onClick={onResetViewpoint}
|
||||
onClick={onFitView}
|
||||
/>
|
||||
<MiniButton
|
||||
title={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||
|
|
|
@ -34,11 +34,36 @@
|
|||
}
|
||||
}
|
||||
|
||||
.react-flow__node-input,
|
||||
.react-flow__node-synthesis {
|
||||
.react-flow__handle {
|
||||
cursor: default !important;
|
||||
|
||||
border-color: var(--cl-bg-40);
|
||||
background-color: var(--cl-bg-120);
|
||||
|
||||
.selected & {
|
||||
border-color: var(--cd-bg-40);
|
||||
}
|
||||
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
background-color: var(--cd-bg-120);
|
||||
|
||||
.selected & {
|
||||
border-color: var(--cl-bg-40);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react-flow__pane {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:is(.react-flow__node-input, .react-flow__node-synthesis) {
|
||||
cursor: pointer;
|
||||
|
||||
border: 1px solid;
|
||||
padding: 2px;
|
||||
width: 120px;
|
||||
width: 150px;
|
||||
|
||||
border-radius: 5px;
|
||||
background-color: var(--cl-bg-120);
|
||||
|
@ -47,9 +72,25 @@
|
|||
border-color: var(--cl-bg-40);
|
||||
background-color: var(--cl-bg-120);
|
||||
|
||||
&.dark {
|
||||
&:hover:not(.selected) {
|
||||
box-shadow: 0 0 0 2px var(--cl-prim-bg-80) !important;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: var(--cd-bg-40);
|
||||
}
|
||||
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
border-color: var(--cd-bg-40);
|
||||
background-color: var(--cd-bg-120);
|
||||
|
||||
&:hover:not(.selected) {
|
||||
box-shadow: 0 0 0 3px var(--cd-prim-bg-80) !important;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: var(--cl-bg-40);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ export const PARAMETER = {
|
|||
refreshTimeout: 100, // milliseconds delay for post-refresh actions
|
||||
minimalTimeout: 10, // milliseconds delay for fast updates
|
||||
|
||||
zoomDuration: 500, // milliseconds animation duration
|
||||
|
||||
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
|
||||
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
|
||||
graphPopupDelay: 500, // milliseconds delay for graph popup selections
|
||||
|
|
|
@ -939,6 +939,7 @@ export const information = {
|
|||
|
||||
versionDestroyed: 'Версия удалена',
|
||||
itemDestroyed: 'Схема удалена',
|
||||
operationDestroyed: 'Операция удалена',
|
||||
constituentsDestroyed: (aliases: string) => `Конституенты удалены: ${aliases}`
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user