mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implementing basic oss graph
This commit is contained in:
parent
0ee0e65ac5
commit
cdb2f6cb79
|
@ -21,6 +21,8 @@ This readme file is used mostly to document project dependencies
|
||||||
|
|
||||||
## ✨ Frontend [Vite + React + Typescript]
|
## ✨ Frontend [Vite + React + Typescript]
|
||||||
|
|
||||||
|
- to regenerate parsers use 'npm run generate' script
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>npm install</summary>
|
<summary>npm install</summary>
|
||||||
<pre>
|
<pre>
|
||||||
|
@ -36,6 +38,7 @@ This readme file is used mostly to document project dependencies
|
||||||
- react-error-boundary
|
- react-error-boundary
|
||||||
- react-pdf
|
- react-pdf
|
||||||
- react-tooltip
|
- react-tooltip
|
||||||
|
- reactflow
|
||||||
- js-file-download
|
- js-file-download
|
||||||
- use-debounce
|
- use-debounce
|
||||||
- framer-motion
|
- framer-motion
|
||||||
|
@ -54,6 +57,7 @@ This readme file is used mostly to document project dependencies
|
||||||
- autoprefixer
|
- autoprefixer
|
||||||
- eslint-plugin-simple-import-sort
|
- eslint-plugin-simple-import-sort
|
||||||
- eslint-plugin-tsdoc
|
- eslint-plugin-tsdoc
|
||||||
|
- vite
|
||||||
- jest
|
- jest
|
||||||
- ts-jest
|
- ts-jest
|
||||||
- @types/jest
|
- @types/jest
|
||||||
|
@ -65,6 +69,7 @@ This readme file is used mostly to document project dependencies
|
||||||
<pre>
|
<pre>
|
||||||
- ESLint
|
- ESLint
|
||||||
- Colorize
|
- Colorize
|
||||||
|
- Tailwind CSS IntelliSense
|
||||||
- Code Spell Checker (eng + rus)
|
- Code Spell Checker (eng + rus)
|
||||||
- Backticks
|
- Backticks
|
||||||
- Svg Preview
|
- Svg Preview
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from apps.rsform.serializers import LibraryItemSerializer
|
from apps.rsform.serializers import LibraryItemSerializer
|
||||||
|
|
||||||
from .basics import OperationPositionSerializer, PositionsSerializer
|
from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
ArgumentSerializer,
|
ArgumentSerializer,
|
||||||
OperationCreateSerializer,
|
OperationCreateSerializer,
|
||||||
|
|
|
@ -14,3 +14,15 @@ class PositionsSerializer(serializers.Serializer):
|
||||||
positions = serializers.ListField(
|
positions = serializers.ListField(
|
||||||
child=OperationPositionSerializer()
|
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()
|
||||||
|
|
|
@ -10,7 +10,7 @@ from apps.rsform.serializers import LibraryItemDetailsSerializer
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from ..models import Argument, Operation, OperationSchema, OperationType
|
from ..models import Argument, Operation, OperationSchema, OperationType
|
||||||
from .basics import OperationPositionSerializer
|
from .basics import OperationPositionSerializer, SubstitutionExSerializer
|
||||||
|
|
||||||
|
|
||||||
class OperationSerializer(serializers.ModelSerializer):
|
class OperationSerializer(serializers.ModelSerializer):
|
||||||
|
@ -42,9 +42,10 @@ class OperationCreateSerializer(serializers.Serializer):
|
||||||
model = Operation
|
model = Operation
|
||||||
fields = \
|
fields = \
|
||||||
'alias', 'operation_type', 'title', \
|
'alias', 'operation_type', 'title', \
|
||||||
'comment', 'position_x', 'position_y'
|
'comment', 'result', 'position_x', 'position_y'
|
||||||
|
|
||||||
item_data = OperationData()
|
item_data = OperationData()
|
||||||
|
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
|
||||||
positions = serializers.ListField(
|
positions = serializers.ListField(
|
||||||
child=OperationPositionSerializer(),
|
child=OperationPositionSerializer(),
|
||||||
default=[]
|
default=[]
|
||||||
|
@ -75,9 +76,12 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
||||||
items = serializers.ListField(
|
items = serializers.ListField(
|
||||||
child=OperationSerializer()
|
child=OperationSerializer()
|
||||||
)
|
)
|
||||||
graph = serializers.ListField(
|
arguments = serializers.ListField(
|
||||||
child=ArgumentSerializer()
|
child=ArgumentSerializer()
|
||||||
)
|
)
|
||||||
|
substitutions = serializers.ListField(
|
||||||
|
child=SubstitutionExSerializer()
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
|
@ -90,15 +94,15 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
||||||
result['items'] = []
|
result['items'] = []
|
||||||
for operation in oss.operations():
|
for operation in oss.operations():
|
||||||
result['items'].append(OperationSerializer(operation).data)
|
result['items'].append(OperationSerializer(operation).data)
|
||||||
result['graph'] = []
|
result['arguments'] = []
|
||||||
for argument in oss.arguments():
|
for argument in oss.arguments():
|
||||||
result['graph'].append(ArgumentSerializer(argument).data)
|
result['arguments'].append(ArgumentSerializer(argument).data)
|
||||||
result['substitutions'] = []
|
result['substitutions'] = []
|
||||||
for substitution in oss.substitutions().values(
|
for substitution in oss.substitutions().values(
|
||||||
'operation',
|
'operation',
|
||||||
'original',
|
'original',
|
||||||
'transfer_term',
|
|
||||||
'substitution',
|
'substitution',
|
||||||
|
'transfer_term',
|
||||||
original_alias=F('original__alias'),
|
original_alias=F('original__alias'),
|
||||||
original_term=F('original__term_resolved'),
|
original_term=F('original__term_resolved'),
|
||||||
substitution_alias=F('substitution__alias'),
|
substitution_alias=F('substitution__alias'),
|
||||||
|
|
|
@ -77,12 +77,12 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(sub['substitution_alias'], self.ks2x1.alias)
|
self.assertEqual(sub['substitution_alias'], self.ks2x1.alias)
|
||||||
self.assertEqual(sub['substitution_term'], self.ks2x1.term_resolved)
|
self.assertEqual(sub['substitution_term'], self.ks2x1.term_resolved)
|
||||||
|
|
||||||
graph = response.data['graph']
|
arguments = response.data['arguments']
|
||||||
self.assertEqual(len(graph), 2)
|
self.assertEqual(len(arguments), 2)
|
||||||
self.assertEqual(graph[0]['operation'], self.operation3.pk)
|
self.assertEqual(arguments[0]['operation'], self.operation3.pk)
|
||||||
self.assertEqual(graph[0]['argument'], self.operation1.pk)
|
self.assertEqual(arguments[0]['argument'], self.operation1.pk)
|
||||||
self.assertEqual(graph[1]['operation'], self.operation3.pk)
|
self.assertEqual(arguments[1]['operation'], self.operation3.pk)
|
||||||
self.assertEqual(graph[1]['argument'], self.operation2.pk)
|
self.assertEqual(arguments[1]['argument'], self.operation2.pk)
|
||||||
|
|
||||||
self.executeOK(item=self.unowned_id)
|
self.executeOK(item=self.unowned_id)
|
||||||
self.executeForbidden(item=self.private_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['comment'], data['item_data']['comment'])
|
||||||
self.assertEqual(new_operation['position_x'], data['item_data']['position_x'])
|
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['position_y'], data['item_data']['position_y'])
|
||||||
|
self.assertEqual(new_operation['result'], None)
|
||||||
self.operation1.refresh_from_db()
|
self.operation1.refresh_from_db()
|
||||||
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
||||||
self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
|
self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
|
||||||
|
@ -166,6 +167,42 @@ class TestOssViewset(EndpointTester):
|
||||||
self.toggle_admin(True)
|
self.toggle_admin(True)
|
||||||
self.executeCreated(data=data, item=self.unowned_id)
|
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')
|
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||||
def test_delete_operation(self):
|
def test_delete_operation(self):
|
||||||
self.executeNotFound(item=self.invalid_id)
|
self.executeNotFound(item=self.invalid_id)
|
||||||
|
|
|
@ -98,6 +98,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
schema.update_positions(serializer.validated_data['positions'])
|
schema.update_positions(serializer.validated_data['positions'])
|
||||||
new_operation = schema.create_operation(**serializer.validated_data['item_data'])
|
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()
|
schema.item.refresh_from_db()
|
||||||
|
|
||||||
response = Response(
|
response = Response(
|
||||||
|
|
|
@ -12,12 +12,13 @@ WORKDIR /result
|
||||||
|
|
||||||
RUN npm install -g typescript vite
|
RUN npm install -g typescript vite
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
COPY ./ ./
|
COPY ./ ./
|
||||||
COPY ./env/.env.$BUILD_TYPE ./
|
COPY ./env/.env.$BUILD_TYPE ./
|
||||||
RUN rm -rf ./env
|
RUN rm -rf ./env
|
||||||
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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",
|
"test": "jest",
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
|
|
@ -7,17 +7,14 @@ import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language';
|
import { ILexemeData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language';
|
||||||
import {
|
import { ILibraryItem, ILibraryUpdateData, ITargetAccessPolicy, ITargetLocation, IVersionData } from '@/models/library';
|
||||||
AccessPolicy,
|
|
||||||
ILibraryItem,
|
|
||||||
ILibraryUpdateData,
|
|
||||||
ITargetAccessPolicy,
|
|
||||||
ITargetLocation,
|
|
||||||
IVersionData,
|
|
||||||
LibraryItemType
|
|
||||||
} from '@/models/library';
|
|
||||||
import { ILibraryCreateData } from '@/models/library';
|
import { ILibraryCreateData } from '@/models/library';
|
||||||
import { IOperationSchemaData } from '@/models/oss';
|
import {
|
||||||
|
ICstSubstituteData,
|
||||||
|
IOperationCreateData,
|
||||||
|
IOperationCreatedResponse,
|
||||||
|
IOperationSchemaData
|
||||||
|
} from '@/models/oss';
|
||||||
import {
|
import {
|
||||||
IConstituentaList,
|
IConstituentaList,
|
||||||
IConstituentaMeta,
|
IConstituentaMeta,
|
||||||
|
@ -25,7 +22,6 @@ import {
|
||||||
ICstCreatedResponse,
|
ICstCreatedResponse,
|
||||||
ICstMovetoData,
|
ICstMovetoData,
|
||||||
ICstRenameData,
|
ICstRenameData,
|
||||||
ICstSubstituteData,
|
|
||||||
ICstUpdateData,
|
ICstUpdateData,
|
||||||
IInlineSynthesisData,
|
IInlineSynthesisData,
|
||||||
IProduceStructureResponse,
|
IProduceStructureResponse,
|
||||||
|
@ -233,30 +229,6 @@ export function postCloneLibraryItem(target: string, request: FrontExchange<IRSF
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
|
|
||||||
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<IRSFormData>) {
|
export function getRSFormDetails(target: string, version: string, request: FrontPull<IRSFormData>) {
|
||||||
if (!version) {
|
if (!version) {
|
||||||
AxiosGet({
|
AxiosGet({
|
||||||
|
@ -357,7 +329,7 @@ export function getTRSFile(target: string, version: string, request: FrontPull<B
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postNewConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
export function postCreateConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/rsforms/${schema}/cst-create`,
|
endpoint: `/api/rsforms/${schema}/cst-create`,
|
||||||
request: request
|
request: request
|
||||||
|
@ -445,6 +417,23 @@ export function patchInlineSynthesis(request: FrontExchange<IInlineSynthesisData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
|
||||||
|
AxiosGet({
|
||||||
|
endpoint: `/api/oss/${target}/details`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCreateOperation(
|
||||||
|
schema: string,
|
||||||
|
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
||||||
|
) {
|
||||||
|
AxiosPost({
|
||||||
|
endpoint: `/api/oss/${schema}/create-operation`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
export function postInflectText(request: FrontExchange<IWordFormPlain, ITextResult>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/cctext/inflect`,
|
endpoint: `/api/cctext/inflect`,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
|
||||||
import Label from '@/components/ui/Label';
|
import Label from '@/components/ui/Label';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
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 { describeConstituenta } from '@/utils/labels';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -34,11 +34,11 @@ interface PickSubstitutionsProps {
|
||||||
filter1?: (cst: IConstituenta) => boolean;
|
filter1?: (cst: IConstituenta) => boolean;
|
||||||
filter2?: (cst: IConstituenta) => boolean;
|
filter2?: (cst: IConstituenta) => boolean;
|
||||||
|
|
||||||
items: ISubstitution[];
|
items: ISingleSubstitution[];
|
||||||
setItems: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
|
setItems: React.Dispatch<React.SetStateAction<ISingleSubstitution[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubstitutionIcon({ item }: { item: ISubstitution }) {
|
function SubstitutionIcon({ item }: { item: ISingleSubstitution }) {
|
||||||
if (item.deleteRight) {
|
if (item.deleteRight) {
|
||||||
if (item.takeLeftTerm) {
|
if (item.takeLeftTerm) {
|
||||||
return <IconPageRight size='1.2rem' />;
|
return <IconPageRight size='1.2rem' />;
|
||||||
|
@ -54,7 +54,7 @@ function SubstitutionIcon({ item }: { item: ISubstitution }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<ISubstitution>();
|
const columnHelper = createColumnHelper<ISingleSubstitution>();
|
||||||
|
|
||||||
function PickSubstitutions({
|
function PickSubstitutions({
|
||||||
items,
|
items,
|
||||||
|
@ -80,7 +80,7 @@ function PickSubstitutions({
|
||||||
if (!leftCst || !rightCst) {
|
if (!leftCst || !rightCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newSubstitution: ISubstitution = {
|
const newSubstitution: ISingleSubstitution = {
|
||||||
leftCst: leftCst,
|
leftCst: leftCst,
|
||||||
rightCst: rightCst,
|
rightCst: rightCst,
|
||||||
deleteRight: deleteRight,
|
deleteRight: deleteRight,
|
||||||
|
@ -99,7 +99,7 @@ function PickSubstitutions({
|
||||||
const handleDeleteRow = useCallback(
|
const handleDeleteRow = useCallback(
|
||||||
(row: number) => {
|
(row: number) => {
|
||||||
setItems(prev => {
|
setItems(prev => {
|
||||||
const newItems: ISubstitution[] = [];
|
const newItems: ISingleSubstitution[] = [];
|
||||||
prev.forEach((item, index) => {
|
prev.forEach((item, index) => {
|
||||||
if (index !== row) {
|
if (index !== row) {
|
||||||
newItems.push(item);
|
newItems.push(item);
|
||||||
|
|
58
rsconcept/frontend/src/components/select/SelectOperation.tsx
Normal file
58
rsconcept/frontend/src/components/select/SelectOperation.tsx
Normal file
|
@ -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 (
|
||||||
|
<SelectSingle
|
||||||
|
className={clsx('text-ellipsis', className)}
|
||||||
|
options={options}
|
||||||
|
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : undefined}
|
||||||
|
onChange={data => 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;
|
|
@ -25,8 +25,8 @@ function TextArea({
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
{
|
{
|
||||||
'flex flex-col gap-2': !dense,
|
'flex flex-col flex-grow gap-2': !dense,
|
||||||
'flex items-center gap-3': dense
|
'flex flex-grow items-center gap-3': dense
|
||||||
},
|
},
|
||||||
dense && className
|
dense && className
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -10,13 +10,14 @@ import {
|
||||||
patchSetAccessPolicy,
|
patchSetAccessPolicy,
|
||||||
patchSetLocation,
|
patchSetLocation,
|
||||||
patchSetOwner,
|
patchSetOwner,
|
||||||
|
postCreateOperation,
|
||||||
postSubscribe
|
postSubscribe
|
||||||
} from '@/app/backendAPI';
|
} from '@/app/backendAPI';
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
import useOssDetails from '@/hooks/useOssDetails';
|
import useOssDetails from '@/hooks/useOssDetails';
|
||||||
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
||||||
import { ILibraryUpdateData } from '@/models/library';
|
import { ILibraryUpdateData } from '@/models/library';
|
||||||
import { IOperationSchema } from '@/models/oss';
|
import { IOperation, IOperationCreateData, IOperationSchema } from '@/models/oss';
|
||||||
import { UserID } from '@/models/user';
|
import { UserID } from '@/models/user';
|
||||||
import { contextOutsideScope } from '@/utils/labels';
|
import { contextOutsideScope } from '@/utils/labels';
|
||||||
|
|
||||||
|
@ -43,6 +44,8 @@ interface IOssContext {
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void;
|
||||||
setLocation: (newLocation: string, callback?: () => void) => void;
|
setLocation: (newLocation: string, callback?: () => void) => void;
|
||||||
setEditors: (newEditors: UserID[], callback?: () => void) => void;
|
setEditors: (newEditors: UserID[], callback?: () => void) => void;
|
||||||
|
|
||||||
|
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssContext = createContext<IOssContext | null>(null);
|
const OssContext = createContext<IOssContext | null>(null);
|
||||||
|
@ -63,13 +66,11 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
const library = useLibrary();
|
const library = useLibrary();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const {
|
const {
|
||||||
schema: schema, // prettier: split lines
|
schema, // prettier: split lines
|
||||||
error: errorLoading,
|
error: errorLoading,
|
||||||
setSchema,
|
setSchema,
|
||||||
loading
|
loading
|
||||||
} = useOssDetails({
|
} = useOssDetails({ target: itemID });
|
||||||
target: itemID
|
|
||||||
});
|
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||||
|
|
||||||
|
@ -249,6 +250,24 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
[itemID, schema]
|
[itemID, schema]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createOperation = useCallback(
|
||||||
|
(data: IOperationCreateData, callback?: DataCallback<IOperation>) => {
|
||||||
|
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 (
|
return (
|
||||||
<OssContext.Provider
|
<OssContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -267,7 +286,9 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
setOwner,
|
setOwner,
|
||||||
setEditors,
|
setEditors,
|
||||||
setAccessPolicy,
|
setAccessPolicy,
|
||||||
setLocation
|
setLocation,
|
||||||
|
|
||||||
|
createOperation
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -24,14 +24,15 @@ import {
|
||||||
patchSubstituteConstituents,
|
patchSubstituteConstituents,
|
||||||
patchUploadTRS,
|
patchUploadTRS,
|
||||||
patchVersion,
|
patchVersion,
|
||||||
|
postCreateConstituenta,
|
||||||
postCreateVersion,
|
postCreateVersion,
|
||||||
postNewConstituenta,
|
|
||||||
postSubscribe
|
postSubscribe
|
||||||
} from '@/app/backendAPI';
|
} from '@/app/backendAPI';
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
import useRSFormDetails from '@/hooks/useRSFormDetails';
|
import useRSFormDetails from '@/hooks/useRSFormDetails';
|
||||||
import { AccessPolicy, ILibraryItem, IVersionData, VersionID } from '@/models/library';
|
import { AccessPolicy, ILibraryItem, IVersionData, VersionID } from '@/models/library';
|
||||||
import { ILibraryUpdateData } from '@/models/library';
|
import { ILibraryUpdateData } from '@/models/library';
|
||||||
|
import { ICstSubstituteData } from '@/models/oss';
|
||||||
import {
|
import {
|
||||||
ConstituentaID,
|
ConstituentaID,
|
||||||
IConstituentaList,
|
IConstituentaList,
|
||||||
|
@ -39,7 +40,6 @@ import {
|
||||||
ICstCreateData,
|
ICstCreateData,
|
||||||
ICstMovetoData,
|
ICstMovetoData,
|
||||||
ICstRenameData,
|
ICstRenameData,
|
||||||
ICstSubstituteData,
|
|
||||||
ICstUpdateData,
|
ICstUpdateData,
|
||||||
IInlineSynthesisData,
|
IInlineSynthesisData,
|
||||||
IRSForm,
|
IRSForm,
|
||||||
|
@ -399,7 +399,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
||||||
const cstCreate = useCallback(
|
const cstCreate = useCallback(
|
||||||
(data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => {
|
(data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => {
|
||||||
setProcessingError(undefined);
|
setProcessingError(undefined);
|
||||||
postNewConstituenta(itemID, {
|
postCreateConstituenta(itemID, {
|
||||||
data: data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
|
|
|
@ -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<OperationID[]>([]);
|
||||||
|
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(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(
|
||||||
|
() => (
|
||||||
|
<TabPanel>
|
||||||
|
<TabInputOperation
|
||||||
|
alias={alias}
|
||||||
|
setAlias={setAlias}
|
||||||
|
comment={comment}
|
||||||
|
setComment={setComment}
|
||||||
|
title={title}
|
||||||
|
setTitle={setTitle}
|
||||||
|
attachedID={attachedID}
|
||||||
|
setAttachedID={setAttachedID}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
),
|
||||||
|
[alias, comment, title, attachedID]
|
||||||
|
);
|
||||||
|
|
||||||
|
const synthesisPanel = useMemo(
|
||||||
|
() => (
|
||||||
|
<TabPanel>
|
||||||
|
<TabSynthesisOperation
|
||||||
|
oss={oss}
|
||||||
|
alias={alias}
|
||||||
|
setAlias={setAlias}
|
||||||
|
comment={comment}
|
||||||
|
setComment={setComment}
|
||||||
|
title={title}
|
||||||
|
setTitle={setTitle}
|
||||||
|
setInputs={setInputs}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
),
|
||||||
|
[oss, alias, comment, title]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
header='Создание операции'
|
||||||
|
submitText='Создать'
|
||||||
|
hideWindow={hideWindow}
|
||||||
|
canSubmit={isValid}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className='w-[40rem] px-6 min-h-[35rem]'
|
||||||
|
>
|
||||||
|
<Overlay position='top-0 right-0'>
|
||||||
|
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />
|
||||||
|
</Overlay>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
selectedTabClassName='clr-selected'
|
||||||
|
className='flex flex-col'
|
||||||
|
selectedIndex={activeTab}
|
||||||
|
onSelect={setActiveTab}
|
||||||
|
>
|
||||||
|
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
||||||
|
<TabLabel
|
||||||
|
title={describeOperationType(OperationType.INPUT)}
|
||||||
|
label={labelOperationType(OperationType.INPUT)}
|
||||||
|
/>
|
||||||
|
<TabLabel
|
||||||
|
title={describeOperationType(OperationType.SYNTHESIS)}
|
||||||
|
label={labelOperationType(OperationType.SYNTHESIS)}
|
||||||
|
/>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
{inputPanel}
|
||||||
|
{synthesisPanel}
|
||||||
|
</Tabs>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DlgCreateOperation;
|
|
@ -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<React.SetStateAction<string>>;
|
||||||
|
title: string;
|
||||||
|
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
comment: string;
|
||||||
|
setComment: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
attachedID: LibraryItemID | undefined;
|
||||||
|
setAttachedID: React.Dispatch<React.SetStateAction<LibraryItemID | undefined>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabInputOperation({
|
||||||
|
alias,
|
||||||
|
setAlias,
|
||||||
|
title,
|
||||||
|
setTitle,
|
||||||
|
comment,
|
||||||
|
setComment,
|
||||||
|
attachedID,
|
||||||
|
setAttachedID
|
||||||
|
}: TabInputOperationProps) {
|
||||||
|
return (
|
||||||
|
<AnimateFade className='cc-column'>
|
||||||
|
<TextInput
|
||||||
|
id='operation_title'
|
||||||
|
label='Полное название'
|
||||||
|
value={title}
|
||||||
|
onChange={event => setTitle(event.target.value)}
|
||||||
|
/>
|
||||||
|
<div className='flex gap-6'>
|
||||||
|
<TextInput
|
||||||
|
id='operation_alias'
|
||||||
|
label='Сокращение'
|
||||||
|
className='w-[14rem]'
|
||||||
|
pattern={patterns.library_alias}
|
||||||
|
title={`не более ${limits.library_alias_len} символов`}
|
||||||
|
value={alias}
|
||||||
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
id='operation_comment'
|
||||||
|
label='Описание'
|
||||||
|
noResize
|
||||||
|
rows={3}
|
||||||
|
value={comment}
|
||||||
|
onChange={event => setComment(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Label text='Загружаемая концептуальная схема' />
|
||||||
|
<PickSchema value={attachedID} onSelectValue={setAttachedID} rows={8} />
|
||||||
|
</AnimateFade>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabInputOperation;
|
|
@ -0,0 +1,92 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import SelectOperation from '@/components/select/SelectOperation';
|
||||||
|
import FlexColumn from '@/components/ui/FlexColumn';
|
||||||
|
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 { IOperation, IOperationSchema, OperationID } from '@/models/oss';
|
||||||
|
import { limits, patterns } from '@/utils/constants';
|
||||||
|
|
||||||
|
interface TabSynthesisOperationProps {
|
||||||
|
oss: IOperationSchema;
|
||||||
|
alias: string;
|
||||||
|
setAlias: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
title: string;
|
||||||
|
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
comment: string;
|
||||||
|
setComment: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setInputs: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabSynthesisOperation({
|
||||||
|
oss,
|
||||||
|
alias,
|
||||||
|
setAlias,
|
||||||
|
title,
|
||||||
|
setTitle,
|
||||||
|
comment,
|
||||||
|
setComment,
|
||||||
|
setInputs
|
||||||
|
}: TabSynthesisOperationProps) {
|
||||||
|
const [left, setLeft] = useState<IOperation | undefined>(undefined);
|
||||||
|
const [right, setRight] = useState<IOperation | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const inputs: OperationID[] = [];
|
||||||
|
if (left) {
|
||||||
|
inputs.push(left.id);
|
||||||
|
}
|
||||||
|
if (right) {
|
||||||
|
inputs.push(right.id);
|
||||||
|
}
|
||||||
|
setInputs(inputs);
|
||||||
|
}, [setInputs, left, right]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimateFade className='cc-column'>
|
||||||
|
<TextInput
|
||||||
|
id='operation_title'
|
||||||
|
label='Полное название'
|
||||||
|
value={title}
|
||||||
|
onChange={event => setTitle(event.target.value)}
|
||||||
|
/>
|
||||||
|
<div className='flex gap-6'>
|
||||||
|
<TextInput
|
||||||
|
id='operation_alias'
|
||||||
|
label='Сокращение'
|
||||||
|
className='w-[14rem]'
|
||||||
|
pattern={patterns.library_alias}
|
||||||
|
title={`не более ${limits.library_alias_len} символов`}
|
||||||
|
value={alias}
|
||||||
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
id='operation_comment'
|
||||||
|
label='Описание'
|
||||||
|
noResize
|
||||||
|
rows={3}
|
||||||
|
value={comment}
|
||||||
|
onChange={event => setComment(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<FlexColumn>
|
||||||
|
<Label text='Аргумент 1' />
|
||||||
|
<SelectOperation items={oss.items} value={left} onSelectValue={setLeft} />
|
||||||
|
</FlexColumn>
|
||||||
|
<FlexColumn>
|
||||||
|
<Label text='Аргумент 2' className='text-right' />
|
||||||
|
<SelectOperation items={oss.items} value={right} onSelectValue={setRight} />
|
||||||
|
</FlexColumn>
|
||||||
|
</div>
|
||||||
|
</AnimateFade>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabSynthesisOperation;
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './DlgCreateOperation';
|
|
@ -8,7 +8,7 @@ import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import TabLabel from '@/components/ui/TabLabel';
|
import TabLabel from '@/components/ui/TabLabel';
|
||||||
import useRSFormDetails from '@/hooks/useRSFormDetails';
|
import useRSFormDetails from '@/hooks/useRSFormDetails';
|
||||||
import { LibraryItemID } from '@/models/library';
|
import { LibraryItemID } from '@/models/library';
|
||||||
import { IInlineSynthesisData, IRSForm, ISubstitution } from '@/models/rsform';
|
import { IInlineSynthesisData, IRSForm, ISingleSubstitution } from '@/models/rsform';
|
||||||
|
|
||||||
import TabConstituents from './TabConstituents';
|
import TabConstituents from './TabConstituents';
|
||||||
import TabSchema from './TabSchema';
|
import TabSchema from './TabSchema';
|
||||||
|
@ -30,7 +30,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
|
||||||
|
|
||||||
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
|
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
|
||||||
const [selected, setSelected] = useState<LibraryItemID[]>([]);
|
const [selected, setSelected] = useState<LibraryItemID[]>([]);
|
||||||
const [substitutions, setSubstitutions] = useState<ISubstitution[]>([]);
|
const [substitutions, setSubstitutions] = useState<ISingleSubstitution[]>([]);
|
||||||
|
|
||||||
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });
|
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { ErrorData } from '@/components/info/InfoError';
|
import { ErrorData } from '@/components/info/InfoError';
|
||||||
import DataLoader from '@/components/wrap/DataLoader';
|
import DataLoader from '@/components/wrap/DataLoader';
|
||||||
import { ConstituentaID, IRSForm, ISubstitution } from '@/models/rsform';
|
import { ConstituentaID, IRSForm, ISingleSubstitution } from '@/models/rsform';
|
||||||
import { prefixes } from '@/utils/constants';
|
import { prefixes } from '@/utils/constants';
|
||||||
|
|
||||||
import PickSubstitutions from '../../components/select/PickSubstitutions';
|
import PickSubstitutions from '../../components/select/PickSubstitutions';
|
||||||
|
@ -15,8 +15,8 @@ interface TabSubstitutionsProps {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
error?: ErrorData;
|
error?: ErrorData;
|
||||||
|
|
||||||
substitutions: ISubstitution[];
|
substitutions: ISingleSubstitution[];
|
||||||
setSubstitutions: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
|
setSubstitutions: React.Dispatch<React.SetStateAction<ISingleSubstitution[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabSubstitutions({
|
function TabSubstitutions({
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { useMemo, useState } from 'react';
|
||||||
import PickSubstitutions from '@/components/select/PickSubstitutions';
|
import PickSubstitutions from '@/components/select/PickSubstitutions';
|
||||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import { useRSForm } from '@/context/RSFormContext';
|
import { useRSForm } from '@/context/RSFormContext';
|
||||||
import { ICstSubstituteData, ISubstitution } from '@/models/rsform';
|
import { ICstSubstituteData } from '@/models/oss';
|
||||||
|
import { ISingleSubstitution } from '@/models/rsform';
|
||||||
import { prefixes } from '@/utils/constants';
|
import { prefixes } from '@/utils/constants';
|
||||||
|
|
||||||
interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
@ -16,7 +17,7 @@ interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
|
function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
|
||||||
const { schema } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
|
|
||||||
const [substitutions, setSubstitutions] = useState<ISubstitution[]>([]);
|
const [substitutions, setSubstitutions] = useState<ISingleSubstitution[]>([]);
|
||||||
|
|
||||||
const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]);
|
const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]);
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,40 @@
|
||||||
* Module: OSS data loading and processing.
|
* Module: OSS data loading and processing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IOperationSchema, IOperationSchemaData } from './oss';
|
import { Graph } from './Graph';
|
||||||
|
import { IOperation, IOperationSchema, IOperationSchemaData, OperationID } from './oss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaData}.
|
* Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaData}.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class OssLoader {
|
export class OssLoader {
|
||||||
private schema: IOperationSchemaData;
|
private oss: IOperationSchemaData;
|
||||||
|
private graph: Graph = new Graph();
|
||||||
|
private operationByID: Map<OperationID, IOperation> = new Map();
|
||||||
|
|
||||||
constructor(input: IOperationSchemaData) {
|
constructor(input: IOperationSchemaData) {
|
||||||
this.schema = input;
|
this.oss = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
produceOSS(): IOperationSchema {
|
produceOSS(): IOperationSchema {
|
||||||
const result = this.schema as IOperationSchema;
|
const result = this.oss as IOperationSchema;
|
||||||
result.producedData = [1, 2, 3]; // TODO: put data processing here
|
this.prepareLookups();
|
||||||
|
this.createGraph();
|
||||||
|
|
||||||
|
result.operationByID = this.operationByID;
|
||||||
|
result.graph = this.graph;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private prepareLookups() {
|
||||||
|
this.oss.items.forEach(operation => {
|
||||||
|
this.operationByID.set(operation.id, operation);
|
||||||
|
this.graph.addNode(operation.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createGraph() {
|
||||||
|
this.oss.arguments.forEach(argument => this.graph.addEdge(argument.argument, argument.operation));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,3 +177,11 @@ export interface GraphFilterParams {
|
||||||
allowConstant: boolean;
|
allowConstant: boolean;
|
||||||
allowTheorem: boolean;
|
allowTheorem: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents XY Position.
|
||||||
|
*/
|
||||||
|
export interface Position2D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
|
@ -2,18 +2,112 @@
|
||||||
* Module: Schema of Synthesis Operations.
|
* Module: Schema of Synthesis Operations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ILibraryItemData } from './library';
|
import { Graph } from './Graph';
|
||||||
|
import { ILibraryItemData, LibraryItemID } from './library';
|
||||||
|
import { ConstituentaID } from './rsform';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents backend data for Schema of Synthesis Operations.
|
* Represents {@link IOperation} identifier type.
|
||||||
|
*/
|
||||||
|
export type OperationID = number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} type.
|
||||||
|
*/
|
||||||
|
export enum OperationType {
|
||||||
|
INPUT = 'input',
|
||||||
|
SYNTHESIS = 'synthesis'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents Operation.
|
||||||
|
*/
|
||||||
|
export interface IOperation {
|
||||||
|
id: OperationID;
|
||||||
|
operation_type: OperationType;
|
||||||
|
oss: LibraryItemID;
|
||||||
|
|
||||||
|
alias: string;
|
||||||
|
title: string;
|
||||||
|
comment: string;
|
||||||
|
position_x: number;
|
||||||
|
position_y: number;
|
||||||
|
|
||||||
|
result: LibraryItemID | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} position.
|
||||||
|
*/
|
||||||
|
export interface IOperationPosition extends Pick<IOperation, 'id' | 'position_x' | 'position_y'> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data, used in creation process.
|
||||||
|
*/
|
||||||
|
export interface IOperationCreateData {
|
||||||
|
item_data: Pick<
|
||||||
|
IOperation,
|
||||||
|
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result'
|
||||||
|
>;
|
||||||
|
arguments: OperationID[] | undefined;
|
||||||
|
positions: IOperationPosition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} Argument.
|
||||||
|
*/
|
||||||
|
export interface IArgument {
|
||||||
|
operation: OperationID;
|
||||||
|
argument: OperationID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used in merging single {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface ICstSubstitute {
|
||||||
|
original: ConstituentaID;
|
||||||
|
substitution: ConstituentaID;
|
||||||
|
transfer_term: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data, used in merging multiple {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export interface ICstSubstituteData {
|
||||||
|
substitutions: ICstSubstitute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link ICstSubstitute} extended data.
|
||||||
|
*/
|
||||||
|
export interface ICstSubstituteEx extends ICstSubstitute {
|
||||||
|
original_alias: string;
|
||||||
|
original_term: string;
|
||||||
|
substitution_alias: string;
|
||||||
|
substitution_term: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents backend data for {@link IOperationSchema}.
|
||||||
*/
|
*/
|
||||||
export interface IOperationSchemaData extends ILibraryItemData {
|
export interface IOperationSchemaData extends ILibraryItemData {
|
||||||
additional_data?: number[];
|
items: IOperation[];
|
||||||
|
arguments: IArgument[];
|
||||||
|
substitutions: ICstSubstituteEx[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents Schema of Synthesis Operations.
|
* Represents OperationSchema.
|
||||||
*/
|
*/
|
||||||
export interface IOperationSchema extends IOperationSchemaData {
|
export interface IOperationSchema extends IOperationSchemaData {
|
||||||
producedData: number[]; // TODO: modify this to store calculated state on load
|
graph: Graph;
|
||||||
|
operationByID: Map<OperationID, IOperation>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IOperation}.
|
||||||
|
*/
|
||||||
|
export interface IOperationCreatedResponse {
|
||||||
|
new_operation: IOperation;
|
||||||
|
oss: IOperationSchemaData;
|
||||||
}
|
}
|
||||||
|
|
18
rsconcept/frontend/src/models/ossAPI.ts
Normal file
18
rsconcept/frontend/src/models/ossAPI.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Module: API for OperationSystem.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TextMatcher } from '@/utils/utils';
|
||||||
|
|
||||||
|
import { IOperation } from './oss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given target {@link IOperation} matches the specified query using.
|
||||||
|
*
|
||||||
|
* @param target - The target object to be matched.
|
||||||
|
* @param query - The query string used for matching.
|
||||||
|
*/
|
||||||
|
export function matchOperation(target: IOperation, query: string): boolean {
|
||||||
|
const matcher = new TextMatcher(query);
|
||||||
|
return matcher.test(target.alias) || matcher.test(target.title);
|
||||||
|
}
|
|
@ -5,10 +5,11 @@
|
||||||
import { Graph } from '@/models/Graph';
|
import { Graph } from '@/models/Graph';
|
||||||
|
|
||||||
import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library';
|
import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library';
|
||||||
|
import { ICstSubstitute } from './oss';
|
||||||
import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang';
|
import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents Constituenta type.
|
* Represents {@link IConstituenta} type.
|
||||||
*/
|
*/
|
||||||
export enum CstType {
|
export enum CstType {
|
||||||
BASE = 'basic',
|
BASE = 'basic',
|
||||||
|
@ -21,7 +22,7 @@ export enum CstType {
|
||||||
THEOREM = 'theorem'
|
THEOREM = 'theorem'
|
||||||
}
|
}
|
||||||
|
|
||||||
// CstType constant for category dividers in TemplateSchemas. TODO: create separate structure for templates
|
// CstType constant for category dividers in TemplateSchemas
|
||||||
export const CATEGORY_CST_TYPE = CstType.THEOREM;
|
export const CATEGORY_CST_TYPE = CstType.THEOREM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +31,7 @@ export const CATEGORY_CST_TYPE = CstType.THEOREM;
|
||||||
export type Position = number;
|
export type Position = number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents {@link Constituenta} identifier type.
|
* Represents {@link IConstituenta} identifier type.
|
||||||
*/
|
*/
|
||||||
export type ConstituentaID = number;
|
export type ConstituentaID = number;
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ export interface IConstituentaList {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents constituenta data, used in creation process.
|
* Represents {@link IConstituenta} data, used in creation process.
|
||||||
*/
|
*/
|
||||||
export interface ICstCreateData
|
export interface ICstCreateData
|
||||||
extends Pick<
|
extends Pick<
|
||||||
|
@ -135,7 +136,7 @@ export interface ICstCreateData
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents data, used in ordering constituents in a list.
|
* Represents data, used in ordering a list of {@link IConstituenta}.
|
||||||
*/
|
*/
|
||||||
export interface ICstMovetoData extends IConstituentaList {
|
export interface ICstMovetoData extends IConstituentaList {
|
||||||
move_to: Position;
|
move_to: Position;
|
||||||
|
@ -158,32 +159,6 @@ export interface ICstUpdateData
|
||||||
*/
|
*/
|
||||||
export interface ICstRenameData extends ITargetCst, Pick<IConstituentaMeta, 'alias' | 'cst_type'> {}
|
export interface ICstRenameData extends ITargetCst, Pick<IConstituentaMeta, 'alias' | 'cst_type'> {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents data, used in merging single {@link IConstituenta}.
|
|
||||||
*/
|
|
||||||
export interface ICstSubstitute {
|
|
||||||
original: ConstituentaID;
|
|
||||||
substitution: ConstituentaID;
|
|
||||||
transfer_term: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents data, used in merging multiple {@link IConstituenta}.
|
|
||||||
*/
|
|
||||||
export interface ICstSubstituteData {
|
|
||||||
substitutions: ICstSubstitute[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents single substitution for synthesis table.
|
|
||||||
*/
|
|
||||||
export interface ISubstitution {
|
|
||||||
leftCst: IConstituenta;
|
|
||||||
rightCst: IConstituenta;
|
|
||||||
deleteRight: boolean;
|
|
||||||
takeLeftTerm: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents data response when creating {@link IConstituenta}.
|
* Represents data response when creating {@link IConstituenta}.
|
||||||
*/
|
*/
|
||||||
|
@ -265,6 +240,16 @@ export interface IVersionCreatedResponse {
|
||||||
schema: IRSFormData;
|
schema: IRSFormData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents single substitution for synthesis table.
|
||||||
|
*/
|
||||||
|
export interface ISingleSubstitution {
|
||||||
|
leftCst: IConstituenta;
|
||||||
|
rightCst: IConstituenta;
|
||||||
|
deleteRight: boolean;
|
||||||
|
takeLeftTerm: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents input data for inline synthesis.
|
* Represents input data for inline synthesis.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,19 +2,12 @@
|
||||||
|
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
|
||||||
|
|
||||||
import { useOssEdit } from '../OssEditContext';
|
|
||||||
import OssFlow from './OssFlow';
|
import OssFlow from './OssFlow';
|
||||||
|
|
||||||
function EditorOssGraph() {
|
function EditorOssGraph() {
|
||||||
const controller = useOssEdit();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<AnimateFade>
|
<OssFlow />
|
||||||
<OssFlow controller={controller} />
|
|
||||||
</AnimateFade>
|
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,12 @@ import { useOssEdit } from '../OssEditContext';
|
||||||
|
|
||||||
interface InputNodeProps {
|
interface InputNodeProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputNode({ id }: InputNodeProps) {
|
function InputNode({ id, data }: InputNodeProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
console.log(controller.isMutable);
|
console.log(controller.isMutable);
|
||||||
|
|
||||||
|
@ -26,29 +29,21 @@ function InputNode({ id }: InputNodeProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Handle type='target' position={Position.Bottom} />
|
<Handle type='target' position={Position.Bottom} />
|
||||||
<div>
|
<div className='flex justify-between'>
|
||||||
<MiniButton
|
<div className='flex-grow text-center'>{data.label}</div>
|
||||||
className='float-right'
|
<div className='cc-icons'>
|
||||||
icon={<CiSquareRemove className='icon-red' />}
|
<MiniButton
|
||||||
title='Удалить'
|
icon={<PiPlugsConnected className='icon-green' />}
|
||||||
onClick={handleDelete}
|
title='Привязать схему'
|
||||||
color={'red'}
|
onClick={() => {
|
||||||
/>
|
handleClick();
|
||||||
<div>
|
}}
|
||||||
Тип: <strong>Ввод</strong>
|
/>
|
||||||
</div>
|
<MiniButton
|
||||||
<div>
|
icon={<CiSquareRemove className='icon-red' size='1rem' />}
|
||||||
{/* Схема:{controller.getBind(id) === undefined ? '' : controller.getBind(id)} */}
|
title='Удалить'
|
||||||
<strong>
|
onClick={handleDelete}
|
||||||
<MiniButton
|
/>
|
||||||
className='float-right'
|
|
||||||
icon={<PiPlugsConnected className='icon-green' />}
|
|
||||||
title='Привязать схему'
|
|
||||||
onClick={() => {
|
|
||||||
handleClick();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</strong>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,49 +1,126 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useCallback, useLayoutEffect, useMemo } from 'react';
|
||||||
import { NodeTypes, ProOptions, ReactFlow } from 'reactflow';
|
import {
|
||||||
|
EdgeChange,
|
||||||
|
NodeChange,
|
||||||
|
NodeTypes,
|
||||||
|
ProOptions,
|
||||||
|
ReactFlow,
|
||||||
|
useEdgesState,
|
||||||
|
useNodesState,
|
||||||
|
useViewport
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
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 { IOssEditContext } from '../OssEditContext';
|
import { useOssEdit } from '../OssEditContext';
|
||||||
import InputNode from './InputNode';
|
import InputNode from './InputNode';
|
||||||
import OperationNode from './OperationNode';
|
import OperationNode from './OperationNode';
|
||||||
|
import ToolbarOssGraph from './ToolbarOssGraph';
|
||||||
|
|
||||||
const OssNodeTypes: NodeTypes = {
|
function OssFlow() {
|
||||||
synthesis: OperationNode,
|
|
||||||
input: InputNode
|
|
||||||
};
|
|
||||||
|
|
||||||
interface OssFlowProps {
|
|
||||||
controller: IOssEditContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
function OssFlow({ controller }: OssFlowProps) {
|
|
||||||
const { calculateHeight } = useConceptOptions();
|
const { calculateHeight } = useConceptOptions();
|
||||||
const model = useOSS();
|
const model = useOSS();
|
||||||
|
const controller = useOssEdit();
|
||||||
|
const viewport = useViewport();
|
||||||
|
|
||||||
console.log(model.loading);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
console.log(controller.isMutable);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
|
|
||||||
const initialNodes = [
|
useLayoutEffect(() => {
|
||||||
{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' }, type: 'input' },
|
if (!model.schema) {
|
||||||
{ id: '2', position: { x: 0, y: 100 }, data: { label: '2' }, type: 'synthesis' }
|
setNodes([]);
|
||||||
];
|
setEdges([]);
|
||||||
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
|
return;
|
||||||
|
}
|
||||||
|
setNodes(
|
||||||
|
model.schema.items.map(operation => ({
|
||||||
|
id: String(operation.id),
|
||||||
|
data: { label: operation.alias },
|
||||||
|
position: { x: operation.position_x, y: operation.position_y },
|
||||||
|
type: operation.operation_type.toString()
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
setEdges(
|
||||||
|
model.schema.arguments.map((argument, index) => ({
|
||||||
|
id: String(index),
|
||||||
|
source: String(argument.argument),
|
||||||
|
target: String(argument.operation)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}, [model.schema, setNodes, setEdges]);
|
||||||
|
|
||||||
const proOptions: ProOptions = { hideAttribution: true };
|
const getPositions = useCallback(
|
||||||
|
() =>
|
||||||
|
nodes.map(node => ({
|
||||||
|
id: Number(node.id),
|
||||||
|
position_x: node.position.x,
|
||||||
|
position_y: node.position.y
|
||||||
|
})),
|
||||||
|
[nodes]
|
||||||
|
);
|
||||||
|
|
||||||
const canvasWidth = useMemo(() => {
|
const handleNodesChange = useCallback(
|
||||||
return 'calc(100vw - 1rem)';
|
(changes: NodeChange[]) => {
|
||||||
}, []);
|
onNodesChange(changes);
|
||||||
|
},
|
||||||
|
[onNodesChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEdgesChange = useCallback(
|
||||||
|
(changes: EdgeChange[]) => {
|
||||||
|
onEdgesChange(changes);
|
||||||
|
},
|
||||||
|
[onEdgesChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreateOperation = useCallback(() => {
|
||||||
|
// TODO: calculate insert location
|
||||||
|
controller.promptCreateOperation(viewport.x, viewport.y, getPositions());
|
||||||
|
}, [controller, viewport, getPositions]);
|
||||||
|
|
||||||
|
const proOptions: ProOptions = useMemo(() => ({ hideAttribution: true }), []);
|
||||||
|
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
|
||||||
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||||
|
|
||||||
|
const OssNodeTypes: NodeTypes = useMemo(
|
||||||
|
() => ({
|
||||||
|
synthesis: OperationNode,
|
||||||
|
input: InputNode
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const graph = useMemo(
|
||||||
|
() => (
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={handleNodesChange}
|
||||||
|
onEdgesChange={handleEdgesChange}
|
||||||
|
fitView
|
||||||
|
proOptions={proOptions}
|
||||||
|
nodeTypes={OssNodeTypes}
|
||||||
|
maxZoom={2}
|
||||||
|
minZoom={0.75}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[nodes, edges, proOptions, handleNodesChange, handleEdgesChange, OssNodeTypes]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
<AnimateFade>
|
||||||
<ReactFlow nodes={initialNodes} edges={initialEdges} fitView proOptions={proOptions} nodeTypes={OssNodeTypes} />
|
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
|
||||||
</div>
|
<ToolbarOssGraph onCreate={handleCreateOperation} />
|
||||||
|
</Overlay>
|
||||||
|
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||||
|
{graph}
|
||||||
|
</div>
|
||||||
|
</AnimateFade>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { IconNewItem } 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 { useOssEdit } from '../OssEditContext';
|
||||||
|
|
||||||
|
interface ToolbarOssGraphProps {
|
||||||
|
onCreate: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolbarOssGraph({ onCreate }: ToolbarOssGraphProps) {
|
||||||
|
const controller = useOssEdit();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='cc-icons'>
|
||||||
|
{controller.isMutable ? (
|
||||||
|
<MiniButton
|
||||||
|
title='Новая операция'
|
||||||
|
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||||
|
disabled={controller.isProcessing}
|
||||||
|
onClick={onCreate}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<BadgeHelp
|
||||||
|
topic={HelpTopic.UI_OSS_GRAPH}
|
||||||
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||||
|
offset={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolbarOssGraph;
|
|
@ -9,9 +9,11 @@ import { useAuth } from '@/context/AuthContext';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
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 DlgEditEditors from '@/dialogs/DlgEditEditors';
|
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
||||||
import { AccessPolicy } from '@/models/library';
|
import { AccessPolicy } from '@/models/library';
|
||||||
import { IOperationSchema } from '@/models/oss';
|
import { Position2D } from '@/models/miscellaneous';
|
||||||
|
import { IOperationCreateData, IOperationPosition, 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';
|
||||||
|
|
||||||
|
@ -28,6 +30,8 @@ export interface IOssEditContext {
|
||||||
toggleSubscribe: () => void;
|
toggleSubscribe: () => void;
|
||||||
|
|
||||||
share: () => void;
|
share: () => void;
|
||||||
|
|
||||||
|
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||||
|
@ -59,6 +63,10 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
||||||
const [showEditEditors, setShowEditEditors] = useState(false);
|
const [showEditEditors, setShowEditEditors] = useState(false);
|
||||||
const [showEditLocation, setShowEditLocation] = useState(false);
|
const [showEditLocation, setShowEditLocation] = useState(false);
|
||||||
|
|
||||||
|
const [showCreateOperation, setShowCreateOperation] = useState(false);
|
||||||
|
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
|
||||||
|
const [positions, setPositions] = useState<IOperationPosition[]>([]);
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() =>
|
() =>
|
||||||
setAccessLevel(prev => {
|
setAccessLevel(prev => {
|
||||||
|
@ -136,6 +144,19 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
||||||
[model]
|
[model]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const promptCreateOperation = useCallback((x: number, y: number, positions: IOperationPosition[]) => {
|
||||||
|
setInsertPosition({ x: x, y: y });
|
||||||
|
setPositions(positions);
|
||||||
|
setShowCreateOperation(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCreateOperation = useCallback(
|
||||||
|
(data: IOperationCreateData) => {
|
||||||
|
model.createOperation(data, operation => toast.success(information.newOperation(operation.alias)));
|
||||||
|
},
|
||||||
|
[model]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OssEditContext.Provider
|
<OssEditContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -149,7 +170,9 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
||||||
promptEditors,
|
promptEditors,
|
||||||
promptLocation,
|
promptLocation,
|
||||||
|
|
||||||
share
|
share,
|
||||||
|
|
||||||
|
promptCreateOperation
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{model.schema ? (
|
{model.schema ? (
|
||||||
|
@ -168,6 +191,15 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
|
||||||
onChangeLocation={handleSetLocation}
|
onChangeLocation={handleSetLocation}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{showCreateOperation ? (
|
||||||
|
<DlgCreateOperation
|
||||||
|
hideWindow={() => setShowCreateOperation(false)}
|
||||||
|
oss={model.schema}
|
||||||
|
positions={positions}
|
||||||
|
insertPosition={insertPosition}
|
||||||
|
onCreate={handleCreateOperation}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
||||||
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
|
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
|
||||||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
||||||
import { AccessPolicy, IVersionData, LocationHead, VersionID } from '@/models/library';
|
import { AccessPolicy, IVersionData, LocationHead, VersionID } from '@/models/library';
|
||||||
|
import { ICstSubstituteData } from '@/models/oss';
|
||||||
import {
|
import {
|
||||||
ConstituentaID,
|
ConstituentaID,
|
||||||
CstType,
|
CstType,
|
||||||
|
@ -33,7 +34,6 @@ import {
|
||||||
ICstCreateData,
|
ICstCreateData,
|
||||||
ICstMovetoData,
|
ICstMovetoData,
|
||||||
ICstRenameData,
|
ICstRenameData,
|
||||||
ICstSubstituteData,
|
|
||||||
ICstUpdateData,
|
ICstUpdateData,
|
||||||
IInlineSynthesisData,
|
IInlineSynthesisData,
|
||||||
IRSForm,
|
IRSForm,
|
||||||
|
|
|
@ -34,21 +34,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Flow {
|
.react-flow__node-input,
|
||||||
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 {
|
.react-flow__node-synthesis {
|
||||||
border: 1px solid #555;
|
border: 1px solid;
|
||||||
padding: 10px;
|
padding: 2px;
|
||||||
width: 250px;
|
width: 120px;
|
||||||
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
background-color: var(--cl-bg-120);
|
||||||
|
|
||||||
|
color: var(--cl-fg-100);
|
||||||
|
border-color: var(--cl-bg-40);
|
||||||
|
background-color: var(--cl-bg-120);
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
color: var(--cd-fg-100);
|
||||||
|
border-color: var(--cd-bg-40);
|
||||||
|
background-color: var(--cd-bg-120);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { GramData, Grammeme, ReferenceType } from '@/models/language';
|
||||||
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
||||||
import { validateLocation } from '@/models/libraryAPI';
|
import { validateLocation } from '@/models/libraryAPI';
|
||||||
import { CstMatchMode, DependencyMode, GraphColoring, GraphSizing, HelpTopic } from '@/models/miscellaneous';
|
import { CstMatchMode, DependencyMode, GraphColoring, GraphSizing, HelpTopic } from '@/models/miscellaneous';
|
||||||
|
import { OperationType } from '@/models/oss';
|
||||||
import { CstClass, CstType, ExpressionStatus, IConstituenta, IRSForm } from '@/models/rsform';
|
import { CstClass, CstType, ExpressionStatus, IConstituenta, IRSForm } from '@/models/rsform';
|
||||||
import {
|
import {
|
||||||
IArgumentInfo,
|
IArgumentInfo,
|
||||||
|
@ -889,6 +890,28 @@ export function describeLibraryItemType(itemType: LibraryItemType): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves label for {@link OperationType}.
|
||||||
|
*/
|
||||||
|
export function labelOperationType(itemType: OperationType): string {
|
||||||
|
// prettier-ignore
|
||||||
|
switch (itemType) {
|
||||||
|
case OperationType.INPUT: return 'Загрузка';
|
||||||
|
case OperationType.SYNTHESIS: return 'Синтез';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves description for {@link OperationType}.
|
||||||
|
*/
|
||||||
|
export function describeOperationType(itemType: OperationType): string {
|
||||||
|
// prettier-ignore
|
||||||
|
switch (itemType) {
|
||||||
|
case OperationType.INPUT: return 'Загрузка концептуальной схемы в ОСС';
|
||||||
|
case OperationType.SYNTHESIS: return 'Синтез концептуальных схем';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI info descriptors.
|
* UI info descriptors.
|
||||||
*/
|
*/
|
||||||
|
@ -909,8 +932,9 @@ export const information = {
|
||||||
|
|
||||||
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
|
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
|
||||||
newLibraryItem: 'Схема успешно создана',
|
newLibraryItem: 'Схема успешно создана',
|
||||||
newConstituent: (alias: string) => `Конституента добавлена: ${alias}`,
|
|
||||||
newVersion: (version: string) => `Версия создана: ${version}`,
|
newVersion: (version: string) => `Версия создана: ${version}`,
|
||||||
|
newConstituent: (alias: string) => `Конституента добавлена: ${alias}`,
|
||||||
|
newOperation: (alias: string) => `Операция добавлена: ${alias}`,
|
||||||
renameComplete: (oldAlias: string, newAlias: string) => `Переименование: ${oldAlias} -> ${newAlias}`,
|
renameComplete: (oldAlias: string, newAlias: string) => `Переименование: ${oldAlias} -> ${newAlias}`,
|
||||||
|
|
||||||
versionDestroyed: 'Версия удалена',
|
versionDestroyed: 'Версия удалена',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user