mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
This commit is contained in:
parent
acd24a28e5
commit
8376c6bda1
|
@ -43,6 +43,7 @@ This readme file is used mostly to document project dependencies
|
||||||
- use-debounce
|
- use-debounce
|
||||||
- framer-motion
|
- framer-motion
|
||||||
- reagraph
|
- reagraph
|
||||||
|
- html-to-image
|
||||||
- @tanstack/react-table
|
- @tanstack/react-table
|
||||||
- @uiw/react-codemirror
|
- @uiw/react-codemirror
|
||||||
- @uiw/codemirror-themes
|
- @uiw/codemirror-themes
|
||||||
|
|
7
rsconcept/frontend/package-lock.json
generated
7
rsconcept/frontend/package-lock.json
generated
|
@ -15,6 +15,7 @@
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.3.8",
|
"framer-motion": "^11.3.8",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
@ -6808,6 +6809,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/html-to-image": {
|
||||||
|
"version": "1.11.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz",
|
||||||
|
"integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.3.8",
|
"framer-motion": "^11.3.8",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|
|
@ -38,6 +38,7 @@ export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu';
|
||||||
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
||||||
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
||||||
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
||||||
|
export { TbGridDots as IconGrid } from 'react-icons/tb';
|
||||||
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
||||||
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
||||||
export { BiCaretDown as IconSortDesc } from 'react-icons/bi';
|
export { BiCaretDown as IconSortDesc } from 'react-icons/bi';
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
|
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||||
|
import { storage } from '@/utils/constants';
|
||||||
|
|
||||||
import OssFlow from './OssFlow';
|
import OssFlow from './OssFlow';
|
||||||
|
|
||||||
interface EditorOssGraphProps {
|
interface EditorOssGraphProps {
|
||||||
|
@ -10,9 +13,11 @@ interface EditorOssGraphProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
||||||
|
const [showGrid, setShowGrid] = useLocalStorage<boolean>(storage.ossShowGrid, false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<OssFlow isModified={isModified} setIsModified={setIsModified} />
|
<OssFlow isModified={isModified} setIsModified={setIsModified} showGrid={showGrid} setShowGrid={setShowGrid} />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
|
|
||||||
import { IconDestroy, IconEdit2 } from '@/components/Icons';
|
import { IconRSForm } from '@/components/Icons';
|
||||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||||
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
import { useOSS } from '@/context/OssContext';
|
||||||
|
|
||||||
import { useOssEdit } from '../OssEditContext';
|
import { useOssEdit } from '../OssEditContext';
|
||||||
|
|
||||||
|
@ -14,40 +16,30 @@ interface InputNodeProps {
|
||||||
|
|
||||||
function InputNode({ id, data }: InputNodeProps) {
|
function InputNode({ id, data }: InputNodeProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
console.log(controller.isMutable);
|
const model = useOSS();
|
||||||
|
|
||||||
const handleDelete = () => {
|
const hasFile = !!model.schema?.operationByID.get(Number(id))?.result;
|
||||||
console.log('delete node ' + id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditOperation = () => {
|
const handleOpenSchema = () => {
|
||||||
console.log('edit operation ' + id);
|
controller.openOperationSchema(Number(id));
|
||||||
//controller.selectNode(id);
|
|
||||||
//controller.showSynthesis();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type='source' position={Position.Bottom} />
|
<Handle type='source' position={Position.Bottom} />
|
||||||
<div className='flex justify-between items-center'>
|
|
||||||
<div className='flex-grow text-center'>{data.label}</div>
|
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
|
||||||
<div className='cc-icons'>
|
<MiniButton
|
||||||
<MiniButton
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
icon={<IconEdit2 className='icon-primary' size='0.75rem' />}
|
noHover
|
||||||
noPadding
|
title='Связанная КС'
|
||||||
title='Редактировать'
|
onClick={() => {
|
||||||
onClick={() => {
|
handleOpenSchema();
|
||||||
handleEditOperation();
|
}}
|
||||||
}}
|
disabled={!hasFile}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
</Overlay>
|
||||||
noPadding
|
<div className='flex-grow text-center text-sm'>{data.label}</div>
|
||||||
icon={<IconDestroy className='icon-red' size='0.75rem' />}
|
|
||||||
title='Удалить операцию'
|
|
||||||
onClick={handleDelete}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { VscDebugStart } from 'react-icons/vsc';
|
|
||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
|
|
||||||
import { IconDestroy, IconEdit2 } from '@/components/Icons';
|
import { IconRSForm } from '@/components/Icons';
|
||||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||||
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
import { useOSS } from '@/context/OssContext';
|
||||||
|
|
||||||
import { useOssEdit } from '../OssEditContext';
|
import { useOssEdit } from '../OssEditContext';
|
||||||
interface OperationNodeProps {
|
interface OperationNodeProps {
|
||||||
|
@ -14,50 +15,30 @@ interface OperationNodeProps {
|
||||||
|
|
||||||
function OperationNode({ id, data }: OperationNodeProps) {
|
function OperationNode({ id, data }: OperationNodeProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
console.log(controller.isMutable);
|
const model = useOSS();
|
||||||
|
|
||||||
const handleDelete = () => {
|
const hasFile = !!model.schema?.operationByID.get(Number(id))?.result;
|
||||||
console.log('delete node ' + id);
|
|
||||||
// onDelete(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditOperation = () => {
|
const handleOpenSchema = () => {
|
||||||
console.log('edit operation ' + id);
|
controller.openOperationSchema(Number(id));
|
||||||
//controller.selectNode(id);
|
|
||||||
//controller.showSynthesis();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRunOperation = () => {
|
|
||||||
console.log('run operation');
|
|
||||||
// controller.singleSynthesis(id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type='source' position={Position.Bottom} />
|
<Handle type='source' position={Position.Bottom} />
|
||||||
<div className='flex justify-between'>
|
|
||||||
<div className='flex-grow text-center'>{data.label}</div>
|
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
|
||||||
<div className='cc-icons'>
|
<MiniButton
|
||||||
<MiniButton
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
icon={<IconEdit2 className='icon-primary' size='1rem' />}
|
noHover
|
||||||
title='Редактировать'
|
title='Связанная КС'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleEditOperation();
|
handleOpenSchema();
|
||||||
}}
|
}}
|
||||||
/>
|
disabled={!hasFile}
|
||||||
<MiniButton
|
/>
|
||||||
className='float-right'
|
</Overlay>
|
||||||
icon={<VscDebugStart className='icon-green' size='1rem' />}
|
<div className='flex-grow text-center text-sm'>{data.label}</div>
|
||||||
title='Синтез'
|
|
||||||
onClick={() => handleRunOperation()}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
icon={<IconDestroy className='icon-red' size='1rem' />}
|
|
||||||
title='Удалить операцию'
|
|
||||||
onClick={handleDelete}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||||
<Handle type='target' position={Position.Top} id='right' style={{ right: 40, left: 'auto' }} />
|
<Handle type='target' position={Position.Top} id='right' style={{ right: 40, left: 'auto' }} />
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { toSvg } from 'html-to-image';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import {
|
import {
|
||||||
|
Background,
|
||||||
|
getNodesBounds,
|
||||||
|
getViewportForBounds,
|
||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
@ -18,6 +23,7 @@ import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { useOSS } from '@/context/OssContext';
|
import { useOSS } from '@/context/OssContext';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
import { errors } from '@/utils/labels';
|
||||||
|
|
||||||
import { useOssEdit } from '../OssEditContext';
|
import { useOssEdit } from '../OssEditContext';
|
||||||
import InputNode from './InputNode';
|
import InputNode from './InputNode';
|
||||||
|
@ -27,10 +33,12 @@ import ToolbarOssGraph from './ToolbarOssGraph';
|
||||||
interface OssFlowProps {
|
interface OssFlowProps {
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
showGrid: boolean;
|
||||||
|
setShowGrid: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowProps) {
|
||||||
const { calculateHeight } = useConceptOptions();
|
const { calculateHeight, colors } = useConceptOptions();
|
||||||
const model = useOSS();
|
const model = useOSS();
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
const flow = useReactFlow();
|
const flow = useReactFlow();
|
||||||
|
@ -119,6 +127,47 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
controller.deleteOperation(controller.selected[0], getPositions());
|
controller.deleteOperation(controller.selected[0], getPositions());
|
||||||
}, [controller, getPositions]);
|
}, [controller, getPositions]);
|
||||||
|
|
||||||
|
const handleFitView = useCallback(() => {
|
||||||
|
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||||
|
}, [flow]);
|
||||||
|
|
||||||
|
const handleResetPositions = useCallback(() => {
|
||||||
|
setToggleReset(prev => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSaveImage = useCallback(() => {
|
||||||
|
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
|
||||||
|
if (canvas === null) {
|
||||||
|
toast.error(errors.imageFailed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageWidth = PARAMETER.ossImageWidth;
|
||||||
|
const imageHeight = PARAMETER.ossImageHeight;
|
||||||
|
const nodesBounds = getNodesBounds(nodes);
|
||||||
|
const viewport = getViewportForBounds(nodesBounds, imageWidth, imageHeight, 0.5, 2);
|
||||||
|
toSvg(canvas, {
|
||||||
|
backgroundColor: colors.bgDefault,
|
||||||
|
width: imageWidth,
|
||||||
|
height: imageHeight,
|
||||||
|
style: {
|
||||||
|
width: String(imageWidth),
|
||||||
|
height: String(imageHeight),
|
||||||
|
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(dataURL => {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.setAttribute('download', 'reactflow.svg');
|
||||||
|
a.setAttribute('href', dataURL);
|
||||||
|
a.click();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(errors.imageFailed);
|
||||||
|
});
|
||||||
|
}, [colors, nodes]);
|
||||||
|
|
||||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
// Hotkeys implementation
|
// Hotkeys implementation
|
||||||
if (controller.isProcessing) {
|
if (controller.isProcessing) {
|
||||||
|
@ -127,6 +176,12 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
if (!controller.isMutable) {
|
if (!controller.isMutable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleSavePositions();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === 'Delete') {
|
if (event.key === 'Delete') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -160,9 +215,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
maxZoom={2}
|
maxZoom={2}
|
||||||
minZoom={0.75}
|
minZoom={0.75}
|
||||||
nodesConnectable={false}
|
nodesConnectable={false}
|
||||||
/>
|
snapToGrid={true}
|
||||||
|
snapGrid={[10, 10]}
|
||||||
|
>
|
||||||
|
{showGrid ? <Background gap={10} /> : null}
|
||||||
|
</ReactFlow>
|
||||||
),
|
),
|
||||||
[nodes, edges, proOptions, handleNodesChange, onEdgesChange, OssNodeTypes]
|
[nodes, edges, proOptions, handleNodesChange, onEdgesChange, OssNodeTypes, showGrid]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -170,11 +229,14 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
|
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
|
||||||
<ToolbarOssGraph
|
<ToolbarOssGraph
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })}
|
showGrid={showGrid}
|
||||||
|
onFitView={handleFitView}
|
||||||
onCreate={handleCreateOperation}
|
onCreate={handleCreateOperation}
|
||||||
onDelete={handleDeleteOperation}
|
onDelete={handleDeleteOperation}
|
||||||
onResetPositions={() => setToggleReset(prev => !prev)}
|
onResetPositions={handleResetPositions}
|
||||||
onSavePositions={handleSavePositions}
|
onSavePositions={handleSavePositions}
|
||||||
|
onSaveImage={handleSaveImage}
|
||||||
|
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { IconDestroy, IconFitImage, IconNewItem, IconReset, IconSave } from '@/components/Icons';
|
import { IconDestroy, IconFitImage, IconGrid, IconImage, IconNewItem, IconReset, IconSave } from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
@ -11,20 +11,26 @@ import { useOssEdit } from '../OssEditContext';
|
||||||
|
|
||||||
interface ToolbarOssGraphProps {
|
interface ToolbarOssGraphProps {
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
|
showGrid: boolean;
|
||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onFitView: () => void;
|
onFitView: () => void;
|
||||||
|
onSaveImage: () => void;
|
||||||
onSavePositions: () => void;
|
onSavePositions: () => void;
|
||||||
onResetPositions: () => void;
|
onResetPositions: () => void;
|
||||||
|
toggleShowGrid: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolbarOssGraph({
|
function ToolbarOssGraph({
|
||||||
isModified,
|
isModified,
|
||||||
|
showGrid,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onFitView,
|
onFitView,
|
||||||
|
onSaveImage,
|
||||||
onSavePositions,
|
onSavePositions,
|
||||||
onResetPositions
|
onResetPositions,
|
||||||
|
toggleShowGrid
|
||||||
}: ToolbarOssGraphProps) {
|
}: ToolbarOssGraphProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
|
|
||||||
|
@ -51,6 +57,17 @@ function ToolbarOssGraph({
|
||||||
title='Сбросить вид'
|
title='Сбросить вид'
|
||||||
onClick={onFitView}
|
onClick={onFitView}
|
||||||
/>
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title={showGrid ? 'Скрыть сетку' : 'Отобразить сетку'}
|
||||||
|
icon={
|
||||||
|
showGrid ? (
|
||||||
|
<IconGrid size='1.25rem' className='icon-green' />
|
||||||
|
) : (
|
||||||
|
<IconGrid size='1.25rem' className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleShowGrid}
|
||||||
|
/>
|
||||||
{controller.isMutable ? (
|
{controller.isMutable ? (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Новая операция'
|
title='Новая операция'
|
||||||
|
@ -67,6 +84,11 @@ function ToolbarOssGraph({
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<MiniButton
|
||||||
|
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
||||||
|
title='Сохранить изображение'
|
||||||
|
onClick={onSaveImage}
|
||||||
|
/>
|
||||||
<BadgeHelp
|
<BadgeHelp
|
||||||
topic={HelpTopic.UI_OSS_GRAPH}
|
topic={HelpTopic.UI_OSS_GRAPH}
|
||||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import { AnimatePresence } from 'framer-motion';
|
||||||
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
|
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import { urls } from '@/app/urls';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
import { useOSS } from '@/context/OssContext';
|
import { useOSS } from '@/context/OssContext';
|
||||||
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
||||||
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
||||||
|
@ -34,6 +36,8 @@ export interface IOssEditContext {
|
||||||
|
|
||||||
share: () => void;
|
share: () => void;
|
||||||
|
|
||||||
|
openOperationSchema: (target: OperationID) => void;
|
||||||
|
|
||||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||||
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
||||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
|
@ -56,7 +60,7 @@ interface OssEditStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OssEditState = ({ selected, setSelected, children }: OssEditStateProps) => {
|
export const OssEditState = ({ selected, setSelected, children }: OssEditStateProps) => {
|
||||||
// const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { adminMode } = useConceptOptions();
|
const { adminMode } = useConceptOptions();
|
||||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||||
|
@ -151,6 +155,17 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
[model]
|
[model]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const openOperationSchema = useCallback(
|
||||||
|
(target: OperationID) => {
|
||||||
|
const node = model.schema?.operationByID.get(target);
|
||||||
|
if (!node || !node.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push(urls.schema(node.result));
|
||||||
|
},
|
||||||
|
[router, model]
|
||||||
|
);
|
||||||
|
|
||||||
const savePositions = useCallback(
|
const savePositions = useCallback(
|
||||||
(positions: IOperationPosition[], callback?: () => void) => {
|
(positions: IOperationPosition[], callback?: () => void) => {
|
||||||
model.savePositions({ positions: positions }, () => {
|
model.savePositions({ positions: positions }, () => {
|
||||||
|
@ -208,6 +223,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
share,
|
share,
|
||||||
setSelected,
|
setSelected,
|
||||||
|
|
||||||
|
openOperationSchema,
|
||||||
savePositions,
|
savePositions,
|
||||||
promptCreateOperation,
|
promptCreateOperation,
|
||||||
deleteOperation
|
deleteOperation
|
||||||
|
|
|
@ -12,6 +12,8 @@ export const PARAMETER = {
|
||||||
minimalTimeout: 10, // milliseconds delay for fast updates
|
minimalTimeout: 10, // milliseconds delay for fast updates
|
||||||
|
|
||||||
zoomDuration: 500, // milliseconds animation duration
|
zoomDuration: 500, // milliseconds animation duration
|
||||||
|
ossImageWidth: 1280, // pixels - size of OSS image
|
||||||
|
ossImageHeight: 960, // pixels - size of OSS image
|
||||||
|
|
||||||
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
|
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
|
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
|
||||||
|
@ -110,6 +112,8 @@ export const storage = {
|
||||||
rsgraphSizing: 'rsgraph.sizing',
|
rsgraphSizing: 'rsgraph.sizing',
|
||||||
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
||||||
|
|
||||||
|
ossShowGrid: 'oss.show_grid',
|
||||||
|
|
||||||
cstFilterMatch: 'cst.filter.match',
|
cstFilterMatch: 'cst.filter.match',
|
||||||
cstFilterGraph: 'cst.filter.graph'
|
cstFilterGraph: 'cst.filter.graph'
|
||||||
};
|
};
|
||||||
|
|
|
@ -948,7 +948,8 @@ export const information = {
|
||||||
*/
|
*/
|
||||||
export const errors = {
|
export const errors = {
|
||||||
astFailed: 'Невозможно построить дерево разбора',
|
astFailed: 'Невозможно построить дерево разбора',
|
||||||
passwordsMismatch: 'Пароли не совпадают'
|
passwordsMismatch: 'Пароли не совпадают',
|
||||||
|
imageFailed: 'Ошибка при создании изображения'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue
Block a user