From 7b39b764981a1ff335380f6697c2505718f70c8d Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:17:36 +0300 Subject: [PATCH] Implementing basic oss graph pt2 --- README.md | 3 + .../apps/oss/serializers/data_access.py | 1 + .../backend/apps/oss/tests/s_views/t_oss.py | 19 +++ rsconcept/backend/apps/oss/views/oss.py | 3 + rsconcept/frontend/Dockerfile | 5 +- rsconcept/frontend/package.json | 2 +- .../src/components/select/SelectOperation.tsx | 58 +++++++ .../frontend/src/components/ui/TextArea.tsx | 4 +- .../DlgCreateOperation/DlgCreateOperation.tsx | 147 ++++++++++++++++++ .../DlgCreateOperation/TabInputOperation.tsx | 65 ++++++++ .../TabSynthesisOperation.tsx | 92 +++++++++++ .../src/dialogs/DlgCreateOperation/index.tsx | 1 + rsconcept/frontend/src/models/oss.ts | 17 +- rsconcept/frontend/src/models/ossAPI.ts | 18 +++ .../pages/OssPage/EditorOssGraph/OssFlow.tsx | 43 +++-- .../EditorOssGraph/ToolbarOssGraph.tsx | 2 +- .../src/pages/OssPage/OssEditContext.tsx | 18 ++- rsconcept/frontend/src/utils/labels.ts | 23 +++ 18 files changed, 495 insertions(+), 26 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 diff --git a/README.md b/README.md index eac3ef10..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
@@ -67,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/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py
index c2d83d01..2ebdaf6c 100644
--- a/rsconcept/backend/apps/oss/serializers/data_access.py
+++ b/rsconcept/backend/apps/oss/serializers/data_access.py
@@ -45,6 +45,7 @@ class OperationCreateSerializer(serializers.Serializer):
                 'comment', 'position_x', 'position_y'
 
     item_data = OperationData()
+    arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
     positions = serializers.ListField(
         child=OperationPositionSerializer(),
         default=[]
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 ba9b7052..21e60424 100644
--- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py
+++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py
@@ -166,6 +166,25 @@ 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}/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/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; + 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)} + /> + +