Implementing Synthesis frontend pt1
Some checks failed
Backend CI / build (3.12) (push) Waiting to run
Frontend CI / build (18.x) (push) Has been cancelled

This commit is contained in:
Ivan 2024-07-14 14:41:05 +03:00
parent 5b0321b9d1
commit 6f3d3c830e
17 changed files with 1346 additions and 1442 deletions

View File

@ -124,6 +124,7 @@
"pymorphy", "pymorphy",
"Quantor", "Quantor",
"razdel", "razdel",
"reactflow",
"reagraph", "reagraph",
"redef", "redef",
"REDOC", "REDOC",

View File

@ -1,22 +1,4 @@
[ [
{
"model": "auth.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$720000$gFZkaBswurtL0naQKiUnW7$3b6SUN3fY2Xl1H7erAszVpQl2LpoKusan+yJP7Bp3JA=",
"last_login": "2024-06-03T20:57:02.522Z",
"is_superuser": true,
"username": "admin",
"first_name": "Администратор",
"last_name": "",
"email": "admin@mail.ru",
"is_staff": true,
"is_active": true,
"date_joined": "2024-05-27T18:31:48.913Z",
"groups": [],
"user_permissions": []
}
},
{ {
"model": "auth.user", "model": "auth.user",
"pk": 3, "pk": 3,

View File

@ -7,7 +7,7 @@ drf-spectacular==0.27.2
drf-spectacular-sidecar==2024.6.1 drf-spectacular-sidecar==2024.6.1
coreapi==2.3.3 coreapi==2.3.3
django-rest-passwordreset==1.4.1 django-rest-passwordreset==1.4.1
cctext==0.1.3 cctext==0.1.4
pyconcept==0.1.6 pyconcept==0.1.6
psycopg2-binary==2.9.9 psycopg2-binary==2.9.9

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,12 @@
}, },
"dependencies": { "dependencies": {
"@lezer/lr": "^1.4.1", "@lezer/lr": "^1.4.1",
"@tanstack/react-table": "^8.17.3", "@tanstack/react-table": "^8.19.3",
"@uiw/codemirror-themes": "^4.22.2", "@uiw/codemirror-themes": "^4.23.0",
"@uiw/react-codemirror": "^4.22.2", "@uiw/react-codemirror": "^4.23.0",
"axios": "^1.7.2", "axios": "^1.7.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^11.0.10", "framer-motion": "^11.3.2",
"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",
@ -26,36 +26,37 @@
"react-icons": "^5.2.1", "react-icons": "^5.2.1",
"react-intl": "^6.6.8", "react-intl": "^6.6.8",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-pdf": "^9.0.0", "react-pdf": "^9.1.0",
"react-router-dom": "^6.24.0", "react-router-dom": "^6.24.1",
"react-select": "^5.8.0", "react-select": "^5.8.0",
"react-tabs": "^6.0.2", "react-tabs": "^6.0.2",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
"react-tooltip": "^5.27.0", "react-tooltip": "^5.27.1",
"reactflow": "^11.11.4",
"reagraph": "^4.19.2", "reagraph": "^4.19.2",
"use-debounce": "^10.0.1" "use-debounce": "^10.0.1"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^20.14.9", "@types/node": "^20.14.10",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.14.1", "@typescript-eslint/parser": "^7.16.0",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react-refresh": "^0.4.8",
"eslint-plugin-simple-import-sort": "^12.1.0", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tsdoc": "^0.3.0", "eslint-plugin-tsdoc": "^0.3.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"postcss": "^8.4.38", "postcss": "^8.4.39",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.4",
"ts-jest": "^29.1.5", "ts-jest": "^29.2.2",
"typescript": "^5.5.2", "typescript": "^5.5.3",
"vite": "^5.3.1" "vite": "^5.3.3"
}, },
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",

View File

@ -234,6 +234,7 @@ export function postCloneLibraryItem(target: string, request: FrontExchange<IRSF
} }
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) { export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
request.setLoading!(false);
request.onSuccess({ request.onSuccess({
id: Number(target), id: Number(target),
comment: '123', comment: '123',

View File

@ -48,6 +48,7 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
anonymous={!user} anonymous={!user}
onSubmit={initiateSubmit} onSubmit={initiateSubmit}
onDestroy={onDestroy} onDestroy={onDestroy}
controller={controller}
/> />
<AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row')}> <AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row')}>
<FlexColumn className='px-3'> <FlexColumn className='px-3'>

View File

@ -1,14 +1,21 @@
'use client'; 'use client';
import { ReactFlowProvider } from 'reactflow';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
import { useOssEdit } from '../OssEditContext';
import OssFlow from './OssFlow';
function EditorOssGraph() { function EditorOssGraph() {
// TODO: Implement OSS editing UI here const controller = useOssEdit();
return ( return (
<ReactFlowProvider>
<AnimateFade> <AnimateFade>
<div className='py-3'>Реализация графического интерфейса</div> <OssFlow controller={controller} />
</AnimateFade> </AnimateFade>
</ReactFlowProvider>
); );
} }

View File

@ -0,0 +1,58 @@
import { CiSquareRemove } from 'react-icons/ci';
import { PiPlugsConnected } from 'react-icons/pi';
import { Handle, Position } from 'reactflow';
import MiniButton from '@/components/ui/MiniButton.tsx';
import { useOssEdit } from '../OssEditContext';
interface InputNodeProps {
id: string;
}
function InputNode({ id }: InputNodeProps) {
const controller = useOssEdit();
console.log(controller.isMutable);
const handleDelete = () => {
console.log('delete node ' + id);
};
const handleClick = () => {
// controller.selectNode(id);
// controller.showSelectInput();
};
return (
<>
<Handle type='target' position={Position.Bottom} />
<div>
<MiniButton
className='float-right'
icon={<CiSquareRemove className='icon-red' />}
title='Удалить'
onClick={handleDelete}
color={'red'}
/>
<div>
Тип: <strong>Ввод</strong>
</div>
<div>
{/* Схема:{controller.getBind(id) === undefined ? '' : controller.getBind(id)} */}
<strong>
<MiniButton
className='float-right'
icon={<PiPlugsConnected className='icon-green' />}
title='Привязать схему'
onClick={() => {
handleClick();
}}
/>
</strong>
</div>
</div>
</>
);
}
export default InputNode;

View File

@ -0,0 +1,70 @@
import { CiSquareRemove } from 'react-icons/ci';
import { IoGitNetworkSharp } from 'react-icons/io5';
import { VscDebugStart } from 'react-icons/vsc';
import { Handle, Position } from 'reactflow';
import MiniButton from '@/components/ui/MiniButton.tsx';
import { useOssEdit } from '../OssEditContext';
interface OperationNodeProps {
id: string;
}
function OperationNode({ id }: OperationNodeProps) {
const controller = useOssEdit();
console.log(controller.isMutable);
const handleDelete = () => {
console.log('delete node ' + id);
// onDelete(id);
};
const handleEditOperation = () => {
console.log('edit operation ' + id);
//controller.selectNode(id);
//controller.showSynthesis();
};
const handleRunOperation = () => {
console.log('run operation');
// controller.singleSynthesis(id);
};
return (
<>
<Handle type='target' position={Position.Bottom} />
<div>
<MiniButton
className='float-right'
icon={<CiSquareRemove className='icon-red' />}
title='Удалить'
onClick={handleDelete}
color={'red'}
/>
<div>
Тип: <strong>Отождествление</strong>
</div>
<div>
Схема: <strong></strong>
<MiniButton
className='float-right'
icon={<VscDebugStart className='icon-green' />}
title='Синтез'
onClick={() => handleRunOperation()}
/>
<MiniButton
className='float-right'
icon={<IoGitNetworkSharp className='icon-green' />}
title='Отождествления'
onClick={() => handleEditOperation()}
/>
</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' }} />
</>
);
}
export default OperationNode;

View File

@ -0,0 +1,50 @@
'use client';
import { useMemo } from 'react';
import { NodeTypes, ProOptions, ReactFlow } from 'reactflow';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useOSS } from '@/context/OssContext';
import { IOssEditContext } from '../OssEditContext';
import InputNode from './InputNode';
import OperationNode from './OperationNode';
const OssNodeTypes: NodeTypes = {
synthesis: OperationNode,
input: InputNode
};
interface OssFlowProps {
controller: IOssEditContext;
}
function OssFlow({ controller }: OssFlowProps) {
const { calculateHeight } = useConceptOptions();
const model = useOSS();
console.log(model.loading);
console.log(controller.isMutable);
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' }, type: 'input' },
{ id: '2', position: { x: 0, y: 100 }, data: { label: '2' }, type: 'synthesis' }
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
const proOptions: ProOptions = { hideAttribution: true };
const canvasWidth = useMemo(() => {
return 'calc(100vw - 1rem)';
}, []);
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
return (
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
<ReactFlow nodes={initialNodes} edges={initialEdges} fitView proOptions={proOptions} nodeTypes={OssNodeTypes} />
</div>
);
}
export default OssFlow;

View File

@ -15,7 +15,7 @@ import { IOperationSchema } from '@/models/oss';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserLevel } from '@/models/user';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
interface IOssEditContext { export interface IOssEditContext {
schema?: IOperationSchema; schema?: IOperationSchema;
isMutable: boolean; isMutable: boolean;

View File

@ -49,6 +49,7 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
anonymous={!user} anonymous={!user}
onSubmit={initiateSubmit} onSubmit={initiateSubmit}
onDestroy={onDestroy} onDestroy={onDestroy}
controller={controller}
/> />
<AnimateFade <AnimateFade
onKeyDown={handleInput} onKeyDown={handleInput}

View File

@ -8,29 +8,34 @@ import BadgeHelp from '@/components/info/BadgeHelp';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { AccessPolicy } from '@/models/library'; import { AccessPolicy, ILibraryItemEditor } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { UserLevel } from '@/models/user'; import { UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels'; import { prepareTooltip, tooltips } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext';
interface ToolbarRSFormCardProps { interface ToolbarRSFormCardProps {
modified: boolean; modified: boolean;
subscribed: boolean; subscribed: boolean;
anonymous: boolean; anonymous: boolean;
onSubmit: () => void; onSubmit: () => void;
onDestroy: () => void; onDestroy: () => void;
controller: ILibraryItemEditor;
} }
function ToolbarRSFormCard({ modified, anonymous, subscribed, onSubmit, onDestroy }: ToolbarRSFormCardProps) { function ToolbarRSFormCard({
const controller = useRSEdit(); modified,
anonymous,
controller,
subscribed,
onSubmit,
onDestroy
}: ToolbarRSFormCardProps) {
const { accessLevel } = useAccessMode(); const { accessLevel } = useAccessMode();
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]); const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
return ( return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'> <Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
{controller.isContentEditable || modified ? ( {controller.isMutable || modified ? (
<MiniButton <MiniButton
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')} titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
disabled={!canSave} disabled={!canSave}
@ -56,7 +61,7 @@ function ToolbarRSFormCard({ modified, anonymous, subscribed, onSubmit, onDestro
<MiniButton <MiniButton
title='Удалить схему' title='Удалить схему'
icon={<IconDestroy size='1.25rem' className='icon-red' />} icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={!controller.isContentEditable || controller.isProcessing || accessLevel < UserLevel.OWNER} disabled={!controller.isMutable || controller.isProcessing || accessLevel < UserLevel.OWNER}
onClick={onDestroy} onClick={onDestroy}
/> />
) : null} ) : null}

View File

@ -8,8 +8,8 @@ import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; 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 { useConceptNavigation } from '@/context/NavigationContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { useRSForm } from '@/context/RSFormContext'; import { useRSForm } from '@/context/RSFormContext';
import DlgChangeLocation from '@/dialogs/DlgChangeLocation'; import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
import DlgCloneLibraryItem from '@/dialogs/DlgCloneLibraryItem'; import DlgCloneLibraryItem from '@/dialogs/DlgCloneLibraryItem';
@ -46,7 +46,7 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { information, prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
interface IRSEditContext { export interface IRSEditContext {
schema?: IRSForm; schema?: IRSForm;
selected: ConstituentaID[]; selected: ConstituentaID[];

View File

@ -3,3 +3,4 @@
*/ */
@import 'react-toastify/dist/ReactToastify.css'; @import 'react-toastify/dist/ReactToastify.css';
@import 'reactflow/dist/style.css';

View File

@ -33,3 +33,22 @@
color: var(--cd-fg-60); color: var(--cd-fg-60);
} }
} }
.Flow {
flex-grow: 1;
font-size: 12px;
}
.react-flow__node-input {
border: 1px solid #555;
padding: 10px;
width: 150px;
border-radius: 5px;
}
.react-flow__node-synthesis {
border: 1px solid #555;
padding: 10px;
width: 250px;
border-radius: 5px;
}