From cdb2f6cb79bdfdd31db207a2a2251cbdbdcbe1e2 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:19:57 +0300 Subject: [PATCH] Implementing basic oss graph --- README.md | 5 + .../backend/apps/oss/serializers/__init__.py | 2 +- .../backend/apps/oss/serializers/basics.py | 12 ++ .../apps/oss/serializers/data_access.py | 16 +- .../backend/apps/oss/tests/s_views/t_oss.py | 49 +++++- rsconcept/backend/apps/oss/views/oss.py | 3 + rsconcept/frontend/Dockerfile | 5 +- rsconcept/frontend/package.json | 2 +- rsconcept/frontend/src/app/backendAPI.ts | 61 +++----- .../components/select/PickSubstitutions.tsx | 14 +- .../src/components/select/SelectOperation.tsx | 58 +++++++ .../frontend/src/components/ui/TextArea.tsx | 4 +- rsconcept/frontend/src/context/OssContext.tsx | 33 +++- .../frontend/src/context/RSFormContext.tsx | 6 +- .../DlgCreateOperation/DlgCreateOperation.tsx | 147 ++++++++++++++++++ .../DlgCreateOperation/TabInputOperation.tsx | 65 ++++++++ .../TabSynthesisOperation.tsx | 92 +++++++++++ .../src/dialogs/DlgCreateOperation/index.tsx | 1 + .../DlgInlineSynthesis/DlgInlineSynthesis.tsx | 4 +- .../DlgInlineSynthesis/TabSubstitutions.tsx | 6 +- .../frontend/src/dialogs/DlgSubstituteCst.tsx | 5 +- rsconcept/frontend/src/models/OssLoader.ts | 28 +++- .../frontend/src/models/miscellaneous.ts | 8 + rsconcept/frontend/src/models/oss.ts | 104 ++++++++++++- rsconcept/frontend/src/models/ossAPI.ts | 18 +++ rsconcept/frontend/src/models/rsform.ts | 47 ++---- .../OssPage/EditorOssGraph/EditorOssGraph.tsx | 9 +- .../OssPage/EditorOssGraph/InputNode.tsx | 43 +++-- .../pages/OssPage/EditorOssGraph/OssFlow.tsx | 131 ++++++++++++---- .../EditorOssGraph/ToolbarOssGraph.tsx | 37 +++++ .../src/pages/OssPage/OssEditContext.tsx | 36 ++++- .../src/pages/RSFormPage/RSEditContext.tsx | 2 +- rsconcept/frontend/src/styling/overrides.css | 31 ++-- rsconcept/frontend/src/utils/labels.ts | 26 +++- 34 files changed, 914 insertions(+), 196 deletions(-) create mode 100644 rsconcept/frontend/src/components/select/SelectOperation.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgCreateOperation/TabSynthesisOperation.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgCreateOperation/index.tsx create mode 100644 rsconcept/frontend/src/models/ossAPI.ts create mode 100644 rsconcept/frontend/src/pages/OssPage/EditorOssGraph/ToolbarOssGraph.tsx diff --git a/README.md b/README.md index 51d4dd10..e676d40e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ This readme file is used mostly to document project dependencies ## ✨ Frontend [Vite + React + Typescript] +- to regenerate parsers use 'npm run generate' script +
npm install
@@ -36,6 +38,7 @@ This readme file is used mostly to document project dependencies
   - react-error-boundary
   - react-pdf
   - react-tooltip
+  - reactflow
   - js-file-download
   - use-debounce
   - framer-motion
@@ -54,6 +57,7 @@ This readme file is used mostly to document project dependencies
   - autoprefixer
   - eslint-plugin-simple-import-sort
   - eslint-plugin-tsdoc
+  - vite
   - jest
   - ts-jest
   - @types/jest
@@ -65,6 +69,7 @@ This readme file is used mostly to document project dependencies
   
   - ESLint
   - Colorize
+  - Tailwind CSS IntelliSense
   - Code Spell Checker (eng + rus)
   - Backticks
   - Svg Preview
diff --git a/rsconcept/backend/apps/oss/serializers/__init__.py b/rsconcept/backend/apps/oss/serializers/__init__.py
index fe010453..0b02f26a 100644
--- a/rsconcept/backend/apps/oss/serializers/__init__.py
+++ b/rsconcept/backend/apps/oss/serializers/__init__.py
@@ -2,7 +2,7 @@
 
 from apps.rsform.serializers import LibraryItemSerializer
 
-from .basics import OperationPositionSerializer, PositionsSerializer
+from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
 from .data_access import (
     ArgumentSerializer,
     OperationCreateSerializer,
diff --git a/rsconcept/backend/apps/oss/serializers/basics.py b/rsconcept/backend/apps/oss/serializers/basics.py
index d597ca5a..f0ea8d3b 100644
--- a/rsconcept/backend/apps/oss/serializers/basics.py
+++ b/rsconcept/backend/apps/oss/serializers/basics.py
@@ -14,3 +14,15 @@ class PositionsSerializer(serializers.Serializer):
     positions = serializers.ListField(
         child=OperationPositionSerializer()
     )
+
+
+class SubstitutionExSerializer(serializers.Serializer):
+    ''' Serializer: Substitution extended data. '''
+    operation = serializers.IntegerField()
+    original = serializers.IntegerField()
+    substitution = serializers.IntegerField()
+    transfer_term = serializers.BooleanField()
+    original_alias = serializers.CharField()
+    original_term = serializers.CharField()
+    substitution_alias = serializers.CharField()
+    substitution_term = serializers.CharField()
diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py
index eb161ff6..c187b0c3 100644
--- a/rsconcept/backend/apps/oss/serializers/data_access.py
+++ b/rsconcept/backend/apps/oss/serializers/data_access.py
@@ -10,7 +10,7 @@ from apps.rsform.serializers import LibraryItemDetailsSerializer
 from shared import messages as msg
 
 from ..models import Argument, Operation, OperationSchema, OperationType
-from .basics import OperationPositionSerializer
+from .basics import OperationPositionSerializer, SubstitutionExSerializer
 
 
 class OperationSerializer(serializers.ModelSerializer):
@@ -42,9 +42,10 @@ class OperationCreateSerializer(serializers.Serializer):
             model = Operation
             fields = \
                 'alias', 'operation_type', 'title', \
-                'comment', 'position_x', 'position_y'
+                'comment', 'result', 'position_x', 'position_y'
 
     item_data = OperationData()
+    arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
     positions = serializers.ListField(
         child=OperationPositionSerializer(),
         default=[]
@@ -75,9 +76,12 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
     items = serializers.ListField(
         child=OperationSerializer()
     )
-    graph = serializers.ListField(
+    arguments = serializers.ListField(
         child=ArgumentSerializer()
     )
+    substitutions = serializers.ListField(
+        child=SubstitutionExSerializer()
+    )
 
     class Meta:
         ''' serializer metadata. '''
@@ -90,15 +94,15 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
         result['items'] = []
         for operation in oss.operations():
             result['items'].append(OperationSerializer(operation).data)
-        result['graph'] = []
+        result['arguments'] = []
         for argument in oss.arguments():
-            result['graph'].append(ArgumentSerializer(argument).data)
+            result['arguments'].append(ArgumentSerializer(argument).data)
         result['substitutions'] = []
         for substitution in oss.substitutions().values(
             'operation',
             'original',
-            'transfer_term',
             'substitution',
+            'transfer_term',
             original_alias=F('original__alias'),
             original_term=F('original__term_resolved'),
             substitution_alias=F('substitution__alias'),
diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py
index 1247148a..d28da570 100644
--- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py
+++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py
@@ -77,12 +77,12 @@ class TestOssViewset(EndpointTester):
         self.assertEqual(sub['substitution_alias'], self.ks2x1.alias)
         self.assertEqual(sub['substitution_term'], self.ks2x1.term_resolved)
 
-        graph = response.data['graph']
-        self.assertEqual(len(graph), 2)
-        self.assertEqual(graph[0]['operation'], self.operation3.pk)
-        self.assertEqual(graph[0]['argument'], self.operation1.pk)
-        self.assertEqual(graph[1]['operation'], self.operation3.pk)
-        self.assertEqual(graph[1]['argument'], self.operation2.pk)
+        arguments = response.data['arguments']
+        self.assertEqual(len(arguments), 2)
+        self.assertEqual(arguments[0]['operation'], self.operation3.pk)
+        self.assertEqual(arguments[0]['argument'], self.operation1.pk)
+        self.assertEqual(arguments[1]['operation'], self.operation3.pk)
+        self.assertEqual(arguments[1]['argument'], self.operation2.pk)
 
         self.executeOK(item=self.unowned_id)
         self.executeForbidden(item=self.private_id)
@@ -158,6 +158,7 @@ class TestOssViewset(EndpointTester):
         self.assertEqual(new_operation['comment'], data['item_data']['comment'])
         self.assertEqual(new_operation['position_x'], data['item_data']['position_x'])
         self.assertEqual(new_operation['position_y'], data['item_data']['position_y'])
+        self.assertEqual(new_operation['result'], None)
         self.operation1.refresh_from_db()
         self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
         self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
@@ -166,6 +167,42 @@ class TestOssViewset(EndpointTester):
         self.toggle_admin(True)
         self.executeCreated(data=data, item=self.unowned_id)
 
+    @decl_endpoint('/api/oss/{item}/create-operation', method='post')
+    def test_create_operation_arguments(self):
+        self.populateData()
+        data = {
+            'item_data': {
+                'alias': 'Test4',
+                'operation_type': OperationType.SYNTHESIS
+            },
+            'positions': [],
+            'arguments': [self.operation1.pk, self.operation3.pk]
+        }
+        response = self.executeCreated(data=data, item=self.owned_id)
+        self.owned.item.refresh_from_db()
+        new_operation = response.data['new_operation']
+        arguments = self.owned.arguments()
+        self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1))
+        self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3))
+
+    @decl_endpoint('/api/oss/{item}/create-operation', method='post')
+    def test_create_operation_result(self):
+        self.populateData()
+
+        data = {
+            'item_data': {
+                'alias': 'Test4',
+                'operation_type': OperationType.INPUT,
+                'result': self.ks1.item.pk
+            },
+            'positions': [],
+        }
+        response = self.executeCreated(data=data, item=self.owned_id)
+        self.owned.item.refresh_from_db()
+        new_operation = response.data['new_operation']
+        self.assertEqual(new_operation['result'], self.ks1.item.pk)
+
+
     @decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
     def test_delete_operation(self):
         self.executeNotFound(item=self.invalid_id)
diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py
index b824f321..3c7bf2f0 100644
--- a/rsconcept/backend/apps/oss/views/oss.py
+++ b/rsconcept/backend/apps/oss/views/oss.py
@@ -98,6 +98,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
         with transaction.atomic():
             schema.update_positions(serializer.validated_data['positions'])
             new_operation = schema.create_operation(**serializer.validated_data['item_data'])
+            if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
+                for argument in serializer.validated_data['arguments']:
+                    schema.add_argument(operation=new_operation, argument=argument)
             schema.item.refresh_from_db()
 
         response = Response(
diff --git a/rsconcept/frontend/Dockerfile b/rsconcept/frontend/Dockerfile
index 6cba1d87..afe21694 100644
--- a/rsconcept/frontend/Dockerfile
+++ b/rsconcept/frontend/Dockerfile
@@ -12,12 +12,13 @@ WORKDIR /result
 
 RUN npm install -g typescript vite
 
+COPY package.json package-lock.json ./
+RUN npm ci
+
 COPY ./ ./
 COPY ./env/.env.$BUILD_TYPE ./
 RUN rm -rf ./env
 
-RUN npm ci
-
 ENV NODE_ENV=production
 RUN npm run build
 
diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json
index 7433fa66..17b3a68c 100644
--- a/rsconcept/frontend/package.json
+++ b/rsconcept/frontend/package.json
@@ -4,7 +4,7 @@
   "version": "1.0.0",
   "type": "module",
   "scripts": {
-    "prepare": "lezer-generator src/components/RSInput/rslang/rslangFull.grammar -o src/components/RSInput/rslang/parser.ts && lezer-generator src/components/RefsInput/parse/refsText.grammar -o src/components/RefsInput/parse/parser.ts",
+    "generate": "lezer-generator src/components/RSInput/rslang/rslangFull.grammar -o src/components/RSInput/rslang/parser.ts && lezer-generator src/components/RefsInput/parse/refsText.grammar -o src/components/RefsInput/parse/parser.ts",
     "test": "jest",
     "dev": "vite --host",
     "build": "tsc && vite build",
diff --git a/rsconcept/frontend/src/app/backendAPI.ts b/rsconcept/frontend/src/app/backendAPI.ts
index 01226581..c65ee65e 100644
--- a/rsconcept/frontend/src/app/backendAPI.ts
+++ b/rsconcept/frontend/src/app/backendAPI.ts
@@ -7,17 +7,14 @@ import { toast } from 'react-toastify';
 
 import { type ErrorData } from '@/components/info/InfoError';
 import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language';
-import {
-  AccessPolicy,
-  ILibraryItem,
-  ILibraryUpdateData,
-  ITargetAccessPolicy,
-  ITargetLocation,
-  IVersionData,
-  LibraryItemType
-} from '@/models/library';
+import { ILibraryItem, ILibraryUpdateData, ITargetAccessPolicy, ITargetLocation, IVersionData } from '@/models/library';
 import { ILibraryCreateData } from '@/models/library';
-import { IOperationSchemaData } from '@/models/oss';
+import {
+  ICstSubstituteData,
+  IOperationCreateData,
+  IOperationCreatedResponse,
+  IOperationSchemaData
+} from '@/models/oss';
 import {
   IConstituentaList,
   IConstituentaMeta,
@@ -25,7 +22,6 @@ import {
   ICstCreatedResponse,
   ICstMovetoData,
   ICstRenameData,
-  ICstSubstituteData,
   ICstUpdateData,
   IInlineSynthesisData,
   IProduceStructureResponse,
@@ -233,30 +229,6 @@ export function postCloneLibraryItem(target: string, request: FrontExchange) {
-  request.setLoading!(false);
-  request.onSuccess({
-    id: Number(target),
-    comment: '123',
-    alias: 'oss1',
-    access_policy: AccessPolicy.PUBLIC,
-    editors: [],
-    owner: 1,
-    item_type: LibraryItemType.OSS,
-    location: '/U',
-    read_only: false,
-    subscribers: [],
-    time_create: '0',
-    time_update: '0',
-    title: 'TestOss',
-    visible: false
-  });
-  // AxiosGet({
-  //   endpoint: `/api/oss/${target}`, // TODO: endpoint to access OSS
-  //   request: request
-  // });
-}
-
 export function getRSFormDetails(target: string, version: string, request: FrontPull) {
   if (!version) {
     AxiosGet({
@@ -357,7 +329,7 @@ export function getTRSFile(target: string, version: string, request: FrontPull) {
+export function postCreateConstituenta(schema: string, request: FrontExchange) {
   AxiosPost({
     endpoint: `/api/rsforms/${schema}/cst-create`,
     request: request
@@ -445,6 +417,23 @@ export function patchInlineSynthesis(request: FrontExchange) {
+  AxiosGet({
+    endpoint: `/api/oss/${target}/details`,
+    request: request
+  });
+}
+
+export function postCreateOperation(
+  schema: string,
+  request: FrontExchange
+) {
+  AxiosPost({
+    endpoint: `/api/oss/${schema}/create-operation`,
+    request: request
+  });
+}
+
 export function postInflectText(request: FrontExchange) {
   AxiosPost({
     endpoint: `/api/cctext/inflect`,
diff --git a/rsconcept/frontend/src/components/select/PickSubstitutions.tsx b/rsconcept/frontend/src/components/select/PickSubstitutions.tsx
index ada311e1..88e6eadc 100644
--- a/rsconcept/frontend/src/components/select/PickSubstitutions.tsx
+++ b/rsconcept/frontend/src/components/select/PickSubstitutions.tsx
@@ -8,7 +8,7 @@ import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
 import Label from '@/components/ui/Label';
 import MiniButton from '@/components/ui/MiniButton';
 import { useConceptOptions } from '@/context/ConceptOptionsContext';
-import { IConstituenta, IRSForm, ISubstitution } from '@/models/rsform';
+import { IConstituenta, IRSForm, ISingleSubstitution } from '@/models/rsform';
 import { describeConstituenta } from '@/utils/labels';
 
 import {
@@ -34,11 +34,11 @@ interface PickSubstitutionsProps {
   filter1?: (cst: IConstituenta) => boolean;
   filter2?: (cst: IConstituenta) => boolean;
 
-  items: ISubstitution[];
-  setItems: React.Dispatch>;
+  items: ISingleSubstitution[];
+  setItems: React.Dispatch>;
 }
 
-function SubstitutionIcon({ item }: { item: ISubstitution }) {
+function SubstitutionIcon({ item }: { item: ISingleSubstitution }) {
   if (item.deleteRight) {
     if (item.takeLeftTerm) {
       return ;
@@ -54,7 +54,7 @@ function SubstitutionIcon({ item }: { item: ISubstitution }) {
   }
 }
 
-const columnHelper = createColumnHelper();
+const columnHelper = createColumnHelper();
 
 function PickSubstitutions({
   items,
@@ -80,7 +80,7 @@ function PickSubstitutions({
     if (!leftCst || !rightCst) {
       return;
     }
-    const newSubstitution: ISubstitution = {
+    const newSubstitution: ISingleSubstitution = {
       leftCst: leftCst,
       rightCst: rightCst,
       deleteRight: deleteRight,
@@ -99,7 +99,7 @@ function PickSubstitutions({
   const handleDeleteRow = useCallback(
     (row: number) => {
       setItems(prev => {
-        const newItems: ISubstitution[] = [];
+        const newItems: ISingleSubstitution[] = [];
         prev.forEach((item, index) => {
           if (index !== row) {
             newItems.push(item);
diff --git a/rsconcept/frontend/src/components/select/SelectOperation.tsx b/rsconcept/frontend/src/components/select/SelectOperation.tsx
new file mode 100644
index 00000000..e51f00ef
--- /dev/null
+++ b/rsconcept/frontend/src/components/select/SelectOperation.tsx
@@ -0,0 +1,58 @@
+'use client';
+
+import clsx from 'clsx';
+import { useCallback, useMemo } from 'react';
+
+import { IOperation, OperationID } from '@/models/oss';
+import { matchOperation } from '@/models/ossAPI';
+
+import { CProps } from '../props';
+import SelectSingle from '../ui/SelectSingle';
+
+interface SelectOperationProps extends CProps.Styling {
+  items?: IOperation[];
+  value?: IOperation;
+  onSelectValue: (newValue?: IOperation) => void;
+  placeholder?: string;
+}
+
+function SelectOperation({
+  className,
+  items,
+  value,
+  onSelectValue,
+  placeholder = 'Выберите операцию',
+  ...restProps
+}: SelectOperationProps) {
+  const options = useMemo(() => {
+    return (
+      items?.map(cst => ({
+        value: cst.id,
+        label: `${cst.alias}: ${cst.title}`
+      })) ?? []
+    );
+  }, [items]);
+
+  const filter = useCallback(
+    (option: { value: OperationID | undefined; label: string }, inputValue: string) => {
+      const operation = items?.find(item => item.id === option.value);
+      return !operation ? false : matchOperation(operation, inputValue);
+    },
+    [items]
+  );
+
+  return (
+     onSelectValue(items?.find(cst => cst.id === data?.value))}
+      // @ts-expect-error: TODO: use type definitions from react-select in filter object
+      filterOption={filter}
+      placeholder={placeholder}
+      {...restProps}
+    />
+  );
+}
+
+export default SelectOperation;
diff --git a/rsconcept/frontend/src/components/ui/TextArea.tsx b/rsconcept/frontend/src/components/ui/TextArea.tsx
index cfdbc0e2..adbfb620 100644
--- a/rsconcept/frontend/src/components/ui/TextArea.tsx
+++ b/rsconcept/frontend/src/components/ui/TextArea.tsx
@@ -25,8 +25,8 @@ function TextArea({
     
void) => void; setLocation: (newLocation: string, callback?: () => void) => void; setEditors: (newEditors: UserID[], callback?: () => void) => void; + + createOperation: (data: IOperationCreateData, callback?: DataCallback) => void; } const OssContext = createContext(null); @@ -63,13 +66,11 @@ export const OssState = ({ itemID, children }: OssStateProps) => { const library = useLibrary(); const { user } = useAuth(); const { - schema: schema, // prettier: split lines + schema, // prettier: split lines error: errorLoading, setSchema, loading - } = useOssDetails({ - target: itemID - }); + } = useOssDetails({ target: itemID }); const [processing, setProcessing] = useState(false); const [processingError, setProcessingError] = useState(undefined); @@ -249,6 +250,24 @@ export const OssState = ({ itemID, children }: OssStateProps) => { [itemID, schema] ); + const createOperation = useCallback( + (data: IOperationCreateData, callback?: DataCallback) => { + setProcessingError(undefined); + postCreateOperation(itemID, { + data: data, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: newData => { + setSchema(newData.oss); + library.localUpdateTimestamp(newData.oss.id); + if (callback) callback(newData.new_operation); + } + }); + }, + [itemID, library, setSchema] + ); + return ( { setOwner, setEditors, setAccessPolicy, - setLocation + setLocation, + + createOperation }} > {children} diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx index a54e2394..211e4fbf 100644 --- a/rsconcept/frontend/src/context/RSFormContext.tsx +++ b/rsconcept/frontend/src/context/RSFormContext.tsx @@ -24,14 +24,15 @@ import { patchSubstituteConstituents, patchUploadTRS, patchVersion, + postCreateConstituenta, postCreateVersion, - postNewConstituenta, postSubscribe } from '@/app/backendAPI'; import { type ErrorData } from '@/components/info/InfoError'; import useRSFormDetails from '@/hooks/useRSFormDetails'; import { AccessPolicy, ILibraryItem, IVersionData, VersionID } from '@/models/library'; import { ILibraryUpdateData } from '@/models/library'; +import { ICstSubstituteData } from '@/models/oss'; import { ConstituentaID, IConstituentaList, @@ -39,7 +40,6 @@ import { ICstCreateData, ICstMovetoData, ICstRenameData, - ICstSubstituteData, ICstUpdateData, IInlineSynthesisData, IRSForm, @@ -399,7 +399,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) = const cstCreate = useCallback( (data: ICstCreateData, callback?: DataCallback) => { setProcessingError(undefined); - postNewConstituenta(itemID, { + postCreateConstituenta(itemID, { data: data, showError: true, setLoading: setProcessing, diff --git a/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx b/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx new file mode 100644 index 00000000..9b258aae --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgCreateOperation/DlgCreateOperation.tsx @@ -0,0 +1,147 @@ +'use client'; + +import clsx from 'clsx'; +import { useLayoutEffect, useMemo, useState } from 'react'; +import { TabList, TabPanel, Tabs } from 'react-tabs'; + +import BadgeHelp from '@/components/info/BadgeHelp'; +import Modal from '@/components/ui/Modal'; +import Overlay from '@/components/ui/Overlay'; +import TabLabel from '@/components/ui/TabLabel'; +import { useLibrary } from '@/context/LibraryContext'; +import { LibraryItemID } from '@/models/library'; +import { HelpTopic, Position2D } from '@/models/miscellaneous'; +import { IOperationCreateData, IOperationPosition, IOperationSchema, OperationID, OperationType } from '@/models/oss'; +import { PARAMETER } from '@/utils/constants'; +import { describeOperationType, labelOperationType } from '@/utils/labels'; + +import TabInputOperation from './TabInputOperation'; +import TabSynthesisOperation from './TabSynthesisOperation'; + +interface DlgCreateOperationProps { + hideWindow: () => void; + oss: IOperationSchema; + positions: IOperationPosition[]; + insertPosition: Position2D; + onCreate: (data: IOperationCreateData) => void; +} + +export enum TabID { + INPUT = 0, + SYNTHESIS = 1 +} + +function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCreate }: DlgCreateOperationProps) { + const library = useLibrary(); + const [activeTab, setActiveTab] = useState(TabID.INPUT); + + const [alias, setAlias] = useState(''); + const [title, setTitle] = useState(''); + const [comment, setComment] = useState(''); + const [inputs, setInputs] = useState([]); + const [attachedID, setAttachedID] = useState(undefined); + + const isValid = useMemo(() => alias !== '', [alias]); + + useLayoutEffect(() => { + if (attachedID) { + const schema = library.items.find(value => value.id === attachedID); + if (schema) { + setAlias(schema.alias); + setTitle(schema.title); + setComment(schema.comment); + } + } + }, [attachedID, library]); + + const handleSubmit = () => { + const data: IOperationCreateData = { + item_data: { + position_x: insertPosition.x, + position_y: insertPosition.y, + alias: alias, + title: title, + comment: comment, + operation_type: activeTab === TabID.INPUT ? OperationType.INPUT : OperationType.SYNTHESIS, + result: activeTab === TabID.INPUT ? attachedID ?? null : null + }, + positions: positions, + arguments: activeTab === TabID.INPUT ? undefined : inputs.length > 0 ? inputs : undefined + }; + onCreate(data); + }; + + const inputPanel = useMemo( + () => ( + + + + ), + [alias, comment, title, attachedID] + ); + + const synthesisPanel = useMemo( + () => ( + + + + ), + [oss, alias, comment, title] + ); + + return ( + + + + + + + + + + + + {inputPanel} + {synthesisPanel} + + + ); +} + +export default DlgCreateOperation; diff --git a/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx b/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx new file mode 100644 index 00000000..b4e015c4 --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgCreateOperation/TabInputOperation.tsx @@ -0,0 +1,65 @@ +import PickSchema from '@/components/select/PickSchema'; +import Label from '@/components/ui/Label'; +import TextArea from '@/components/ui/TextArea'; +import TextInput from '@/components/ui/TextInput'; +import AnimateFade from '@/components/wrap/AnimateFade'; +import { LibraryItemID } from '@/models/library'; +import { limits, patterns } from '@/utils/constants'; + +interface TabInputOperationProps { + alias: string; + setAlias: React.Dispatch>; + title: string; + setTitle: React.Dispatch>; + comment: string; + setComment: React.Dispatch>; + attachedID: LibraryItemID | undefined; + setAttachedID: React.Dispatch>; +} + +function TabInputOperation({ + alias, + setAlias, + title, + setTitle, + comment, + setComment, + attachedID, + setAttachedID +}: TabInputOperationProps) { + return ( + + setTitle(event.target.value)} + /> +
+ setAlias(event.target.value)} + /> + +