Compare commits
8 Commits
2eff1b27b9
...
01c0eb201e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
01c0eb201e | ||
![]() |
81d378d076 | ||
![]() |
c7da60325c | ||
![]() |
f67e304a79 | ||
![]() |
54ca6a5279 | ||
![]() |
4899860a05 | ||
![]() |
342a1837ed | ||
![]() |
12963c08dd |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -142,6 +142,7 @@
|
||||||
"setexpr",
|
"setexpr",
|
||||||
"SIDELIST",
|
"SIDELIST",
|
||||||
"signup",
|
"signup",
|
||||||
|
"simplebezier",
|
||||||
"Slng",
|
"Slng",
|
||||||
"SMALLPR",
|
"SMALLPR",
|
||||||
"Stylesheet",
|
"Stylesheet",
|
||||||
|
|
|
@ -39,6 +39,7 @@ This readme file is used mostly to document project dependencies and conventions
|
||||||
- react-error-boundary
|
- react-error-boundary
|
||||||
- react-pdf
|
- react-pdf
|
||||||
- react-tooltip
|
- react-tooltip
|
||||||
|
- react-zoom-pan-pinch
|
||||||
- reactflow
|
- reactflow
|
||||||
- js-file-download
|
- js-file-download
|
||||||
- use-debounce
|
- use-debounce
|
||||||
|
@ -143,7 +144,8 @@ This readme file is used mostly to document project dependencies and conventions
|
||||||
|
|
||||||
- 🚀 F: major feature implementation
|
- 🚀 F: major feature implementation
|
||||||
- 💄 D: UI design
|
- 💄 D: UI design
|
||||||
- 🚑 B: bug fix
|
- 🔥 B: bug fix
|
||||||
|
- 🚑 M: Minor fixes
|
||||||
- 🔧 R: refactoring and code improvement
|
- 🔧 R: refactoring and code improvement
|
||||||
- 📝 I: documentation
|
- 📝 I: documentation
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ from .basics import OperationPositionSerializer, PositionsSerializer, Substituti
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
ArgumentSerializer,
|
ArgumentSerializer,
|
||||||
OperationCreateSerializer,
|
OperationCreateSerializer,
|
||||||
OperationDeleteSerializer,
|
|
||||||
OperationSchemaSerializer,
|
OperationSchemaSerializer,
|
||||||
OperationSerializer
|
OperationSerializer,
|
||||||
|
OperationTargetSerializer
|
||||||
)
|
)
|
||||||
from .responses import NewOperationResponse
|
from .responses import NewOperationResponse, NewSchemaResponse
|
||||||
|
|
|
@ -53,7 +53,7 @@ class OperationCreateSerializer(serializers.Serializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OperationDeleteSerializer(serializers.Serializer):
|
class OperationTargetSerializer(serializers.Serializer):
|
||||||
''' Serializer: Delete operation. '''
|
''' Serializer: Delete operation. '''
|
||||||
target = PKField(many=False, queryset=Operation.objects.all())
|
target = PKField(many=False, queryset=Operation.objects.all())
|
||||||
positions = serializers.ListField(
|
positions = serializers.ListField(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from apps.library.serializers import LibraryItemSerializer
|
||||||
|
|
||||||
from .data_access import OperationSchemaSerializer, OperationSerializer
|
from .data_access import OperationSchemaSerializer, OperationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,3 +10,9 @@ class NewOperationResponse(serializers.Serializer):
|
||||||
''' Serializer: Create operation response. '''
|
''' Serializer: Create operation response. '''
|
||||||
new_operation = OperationSerializer()
|
new_operation = OperationSerializer()
|
||||||
oss = OperationSchemaSerializer()
|
oss = OperationSchemaSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
class NewSchemaResponse(serializers.Serializer):
|
||||||
|
''' Serializer: Create RSForm for input operation response. '''
|
||||||
|
new_schema = LibraryItemSerializer()
|
||||||
|
oss = OperationSchemaSerializer()
|
||||||
|
|
|
@ -127,8 +127,6 @@ class TestOssViewset(EndpointTester):
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||||
def test_create_operation(self):
|
def test_create_operation(self):
|
||||||
|
|
||||||
|
|
||||||
self.populateData()
|
self.populateData()
|
||||||
self.executeBadData(item=self.owned_id)
|
self.executeBadData(item=self.owned_id)
|
||||||
|
|
||||||
|
@ -231,23 +229,6 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||||
self.assertEqual(schema.location, self.owned.model.location)
|
self.assertEqual(schema.location, self.owned.model.location)
|
||||||
|
|
||||||
@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.model.pk
|
|
||||||
},
|
|
||||||
'positions': [],
|
|
||||||
}
|
|
||||||
response = self.executeCreated(data=data, item=self.owned_id)
|
|
||||||
self.owned.refresh_from_db()
|
|
||||||
new_operation = response.data['new_operation']
|
|
||||||
self.assertEqual(new_operation['result'], self.ks1.model.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)
|
||||||
|
@ -269,3 +250,40 @@ class TestOssViewset(EndpointTester):
|
||||||
self.login()
|
self.login()
|
||||||
response = self.executeOK(data=data)
|
response = self.executeOK(data=data)
|
||||||
self.assertEqual(len(response.data['items']), 2)
|
self.assertEqual(len(response.data['items']), 2)
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
|
||||||
|
def test_create_input(self):
|
||||||
|
self.populateData()
|
||||||
|
self.executeBadData(item=self.owned_id)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'positions': []
|
||||||
|
}
|
||||||
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
|
data['target'] = self.operation1.pk
|
||||||
|
self.toggle_admin(True)
|
||||||
|
self.executeBadData(data=data, item=self.unowned_id)
|
||||||
|
self.logout()
|
||||||
|
self.executeForbidden(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.login()
|
||||||
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.operation1.result = None
|
||||||
|
self.operation1.comment = 'TestComment'
|
||||||
|
self.operation1.title = 'TestTitle'
|
||||||
|
self.operation1.sync_text = False
|
||||||
|
self.operation1.save()
|
||||||
|
response = self.executeOK(data=data)
|
||||||
|
self.operation1.refresh_from_db()
|
||||||
|
|
||||||
|
new_schema = response.data['new_schema']
|
||||||
|
self.assertEqual(self.operation1.sync_text, True)
|
||||||
|
self.assertEqual(new_schema['id'], self.operation1.result.pk)
|
||||||
|
self.assertEqual(new_schema['alias'], self.operation1.alias)
|
||||||
|
self.assertEqual(new_schema['title'], self.operation1.title)
|
||||||
|
self.assertEqual(new_schema['comment'], self.operation1.comment)
|
||||||
|
|
||||||
|
data['target'] = self.operation3.pk
|
||||||
|
self.executeBadData(data=data)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import cast
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import generics
|
from rest_framework import generics, serializers
|
||||||
from rest_framework import status as c
|
from rest_framework import status as c
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -12,6 +12,7 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from apps.library.models import LibraryItem, LibraryItemType
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
from apps.library.serializers import LibraryItemSerializer
|
from apps.library.serializers import LibraryItemSerializer
|
||||||
|
from shared import messages as msg
|
||||||
from shared import permissions
|
from shared import permissions
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
@ -33,7 +34,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
if self.action in [
|
if self.action in [
|
||||||
'create_operation',
|
'create_operation',
|
||||||
'delete_operation',
|
'delete_operation',
|
||||||
'update_positions'
|
'update_positions',
|
||||||
|
'create_input'
|
||||||
]:
|
]:
|
||||||
permission_list = [permissions.ItemEditor]
|
permission_list = [permissions.ItemEditor]
|
||||||
elif self.action in ['details']:
|
elif self.action in ['details']:
|
||||||
|
@ -117,19 +119,18 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
oss.add_argument(operation=new_operation, argument=argument)
|
oss.add_argument(operation=new_operation, argument=argument)
|
||||||
|
|
||||||
oss.refresh_from_db()
|
oss.refresh_from_db()
|
||||||
response = Response(
|
return Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data={
|
data={
|
||||||
'new_operation': s.OperationSerializer(new_operation).data,
|
'new_operation': s.OperationSerializer(new_operation).data,
|
||||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return response
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='delete operation',
|
summary='delete operation',
|
||||||
tags=['OSS'],
|
tags=['OSS'],
|
||||||
request=s.OperationDeleteSerializer,
|
request=s.OperationTargetSerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
@ -140,7 +141,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||||
def delete_operation(self, request: Request, pk):
|
def delete_operation(self, request: Request, pk):
|
||||||
''' Endpoint: Delete operation. '''
|
''' Endpoint: Delete operation. '''
|
||||||
serializer = s.OperationDeleteSerializer(
|
serializer = s.OperationTargetSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'oss': self.get_object()}
|
context={'oss': self.get_object()}
|
||||||
)
|
)
|
||||||
|
@ -156,3 +157,59 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.OperationSchemaSerializer(oss.model).data
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='create input schema for target operation',
|
||||||
|
tags=['OSS'],
|
||||||
|
request=s.OperationTargetSerializer(),
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: s.NewSchemaResponse,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['patch'], url_path='create-input')
|
||||||
|
def create_input(self, request: Request, pk):
|
||||||
|
''' Create new input RSForm. '''
|
||||||
|
serializer = s.OperationTargetSerializer(
|
||||||
|
data=request.data,
|
||||||
|
context={'oss': self.get_object()}
|
||||||
|
)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||||
|
if operation.operation_type != m.OperationType.INPUT:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'target': msg.operationNotInput(operation.alias)
|
||||||
|
})
|
||||||
|
if operation.result is not None:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'target': msg.operationResultNotEmpty(operation.alias)
|
||||||
|
})
|
||||||
|
|
||||||
|
oss = m.OperationSchema(self.get_object())
|
||||||
|
with transaction.atomic():
|
||||||
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
|
schema = LibraryItem.objects.create(
|
||||||
|
item_type=LibraryItemType.RSFORM,
|
||||||
|
owner=oss.model.owner,
|
||||||
|
alias=operation.alias,
|
||||||
|
title=operation.title,
|
||||||
|
comment=operation.comment,
|
||||||
|
visible=False,
|
||||||
|
access_policy=oss.model.access_policy,
|
||||||
|
location=oss.model.location
|
||||||
|
)
|
||||||
|
operation.result = schema
|
||||||
|
operation.sync_text = True
|
||||||
|
operation.save()
|
||||||
|
|
||||||
|
oss.refresh_from_db()
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data={
|
||||||
|
'new_schema': LibraryItemSerializer(schema).data,
|
||||||
|
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -18,6 +18,14 @@ def schemaNotOwned():
|
||||||
return 'Нет доступа к схеме'
|
return 'Нет доступа к схеме'
|
||||||
|
|
||||||
|
|
||||||
|
def operationNotInput(title: str):
|
||||||
|
return f'Операция не является Загрузкой: {title}'
|
||||||
|
|
||||||
|
|
||||||
|
def operationResultNotEmpty(title: str):
|
||||||
|
return f'Результат операции не пуст: {title}'
|
||||||
|
|
||||||
|
|
||||||
def renameTrivial(name: str):
|
def renameTrivial(name: str):
|
||||||
return f'Имя должно отличаться от текущего: {name}'
|
return f'Имя должно отличаться от текущего: {name}'
|
||||||
|
|
||||||
|
|
15
rsconcept/frontend/package-lock.json
generated
15
rsconcept/frontend/package-lock.json
generated
|
@ -29,6 +29,7 @@
|
||||||
"react-tabs": "^6.0.2",
|
"react-tabs": "^6.0.2",
|
||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"react-tooltip": "^5.27.1",
|
"react-tooltip": "^5.27.1",
|
||||||
|
"react-zoom-pan-pinch": "^3.6.1",
|
||||||
"reactflow": "^11.11.4",
|
"reactflow": "^11.11.4",
|
||||||
"reagraph": "^4.19.2",
|
"reagraph": "^4.19.2",
|
||||||
"use-debounce": "^10.0.1"
|
"use-debounce": "^10.0.1"
|
||||||
|
@ -10542,6 +10543,20 @@
|
||||||
"react-dom": ">=16.13"
|
"react-dom": ">=16.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-zoom-pan-pinch": {
|
||||||
|
"version": "3.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.6.1.tgz",
|
||||||
|
"integrity": "sha512-SdPqdk7QDSV7u/WulkFOi+cnza8rEZ0XX4ZpeH7vx3UZEg7DoyuAy3MCmm+BWv/idPQL2Oe73VoC0EhfCN+sZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-dom": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reactflow": {
|
"node_modules/reactflow": {
|
||||||
"version": "11.11.4",
|
"version": "11.11.4",
|
||||||
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
|
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"react-tabs": "^6.0.2",
|
"react-tabs": "^6.0.2",
|
||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"react-tooltip": "^5.27.1",
|
"react-tooltip": "^5.27.1",
|
||||||
|
"react-zoom-pan-pinch": "^3.6.1",
|
||||||
"reactflow": "^11.11.4",
|
"reactflow": "^11.11.4",
|
||||||
"reagraph": "^4.19.2",
|
"reagraph": "^4.19.2",
|
||||||
"use-debounce": "^10.0.1"
|
"use-debounce": "^10.0.1"
|
||||||
|
|
1134
rsconcept/frontend/public/db_schema.svg
Normal file
1134
rsconcept/frontend/public/db_schema.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 122 KiB |
|
@ -3,10 +3,13 @@ import {
|
||||||
IconAdminOff,
|
IconAdminOff,
|
||||||
IconDarkTheme,
|
IconDarkTheme,
|
||||||
IconDatabase,
|
IconDatabase,
|
||||||
|
IconDBStructure,
|
||||||
IconHelp,
|
IconHelp,
|
||||||
IconHelpOff,
|
IconHelpOff,
|
||||||
|
IconImage,
|
||||||
IconLightTheme,
|
IconLightTheme,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
|
IconRESTapi,
|
||||||
IconUser
|
IconUser
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
import { CProps } from '@/components/props';
|
import { CProps } from '@/components/props';
|
||||||
|
@ -43,6 +46,21 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
logout(() => router.push(urls.admin, true));
|
logout(() => router.push(urls.admin, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gotoIcons(event: CProps.EventMouse) {
|
||||||
|
hideDropdown();
|
||||||
|
router.push(urls.icons, event.ctrlKey || event.metaKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoRestApi() {
|
||||||
|
hideDropdown();
|
||||||
|
router.push(urls.rest_api, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoDatabaseSchema(event: CProps.EventMouse) {
|
||||||
|
hideDropdown();
|
||||||
|
router.push(urls.database_schema, event.ctrlKey || event.metaKey);
|
||||||
|
}
|
||||||
|
|
||||||
function handleToggleDarkMode() {
|
function handleToggleDarkMode() {
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
toggleDarkMode();
|
toggleDarkMode();
|
||||||
|
@ -77,7 +95,34 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{user?.is_staff ? (
|
{user?.is_staff ? (
|
||||||
<DropdownButton text='База данных' icon={<IconDatabase size='1rem' />} onClick={gotoAdmin} />
|
<DropdownButton
|
||||||
|
text='REST API' // prettier: split-line
|
||||||
|
icon={<IconRESTapi size='1rem' />}
|
||||||
|
className='border-t'
|
||||||
|
onClick={gotoRestApi}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{user?.is_staff ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='База данных' // prettier: split-line
|
||||||
|
icon={<IconDatabase size='1rem' />}
|
||||||
|
onClick={gotoAdmin}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{user?.is_staff ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='Иконки' // prettier: split-line
|
||||||
|
icon={<IconImage size='1rem' />}
|
||||||
|
onClick={gotoIcons}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{user?.is_staff ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='Структура БД' // prettier: split-line
|
||||||
|
icon={<IconDBStructure size='1rem' />}
|
||||||
|
onClick={gotoDatabaseSchema}
|
||||||
|
className='border-b'
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Выйти...'
|
text='Выйти...'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createBrowserRouter } from 'react-router-dom';
|
import { createBrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import CreateItemPage from '@/pages/CreateItemPage';
|
import CreateItemPage from '@/pages/CreateItemPage';
|
||||||
|
import DatabaseSchemaPage from '@/pages/DatabaseSchemaPage';
|
||||||
import HomePage from '@/pages/HomePage';
|
import HomePage from '@/pages/HomePage';
|
||||||
import IconsPage from '@/pages/IconsPage';
|
import IconsPage from '@/pages/IconsPage';
|
||||||
import LibraryPage from '@/pages/LibraryPage';
|
import LibraryPage from '@/pages/LibraryPage';
|
||||||
|
@ -63,13 +64,17 @@ export const Router = createBrowserRouter([
|
||||||
path: `${routes.oss}/:id`,
|
path: `${routes.oss}/:id`,
|
||||||
element: <OssPage />
|
element: <OssPage />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: routes.manuals,
|
||||||
|
element: <ManualsPage />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `${routes.icons}`,
|
path: `${routes.icons}`,
|
||||||
element: <IconsPage />
|
element: <IconsPage />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.manuals,
|
path: `${routes.database_schema}`,
|
||||||
element: <ManualsPage />
|
element: <DatabaseSchemaPage />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ export const routes = {
|
||||||
help: 'manuals',
|
help: 'manuals',
|
||||||
rsforms: 'rsforms',
|
rsforms: 'rsforms',
|
||||||
oss: 'oss',
|
oss: 'oss',
|
||||||
icons: 'icons'
|
icons: 'icons',
|
||||||
|
database_schema: 'database-schema'
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SchemaProps {
|
interface SchemaProps {
|
||||||
|
@ -39,10 +40,13 @@ interface OssProps {
|
||||||
*/
|
*/
|
||||||
export const urls = {
|
export const urls = {
|
||||||
admin: `${buildConstants.backend}/admin`,
|
admin: `${buildConstants.backend}/admin`,
|
||||||
|
rest_api: `${buildConstants.backend}/`,
|
||||||
home: '/',
|
home: '/',
|
||||||
login: `/${routes.login}`,
|
login: `/${routes.login}`,
|
||||||
login_hint: (userName: string) => `/login?username=${userName}`,
|
login_hint: (userName: string) => `/login?username=${userName}`,
|
||||||
profile: `/${routes.profile}`,
|
profile: `/${routes.profile}`,
|
||||||
|
icons: `/${routes.icons}`,
|
||||||
|
database_schema: `/${routes.database_schema}`,
|
||||||
signup: `/${routes.signup}`,
|
signup: `/${routes.signup}`,
|
||||||
library: `/${routes.library}`,
|
library: `/${routes.library}`,
|
||||||
library_filter: (strategy: string) => `/library?filter=${strategy}`,
|
library_filter: (strategy: string) => `/library?filter=${strategy}`,
|
||||||
|
@ -51,7 +55,7 @@ export const urls = {
|
||||||
help_topic: (topic: string) => `/manuals?topic=${topic}`,
|
help_topic: (topic: string) => `/manuals?topic=${topic}`,
|
||||||
schema: (id: number | string, version?: number | string) =>
|
schema: (id: number | string, version?: number | string) =>
|
||||||
`/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''),
|
`/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''),
|
||||||
oss: (id: number | string) => `/oss/${id}`,
|
oss: (id: number | string, tab?: number) => `/oss/${id}` + (tab !== undefined ? `?tab=${tab}` : ''),
|
||||||
schema_props: ({ id, tab, version, active }: SchemaProps) => {
|
schema_props: ({ id, tab, version, active }: SchemaProps) => {
|
||||||
const versionStr = version !== undefined ? `v=${version}&` : '';
|
const versionStr = version !== undefined ? `v=${version}&` : '';
|
||||||
const activeStr = active !== undefined ? `&active=${active}` : '';
|
const activeStr = active !== undefined ? `&active=${active}` : '';
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
IInputCreatedResponse,
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
IOperationCreatedResponse,
|
IOperationCreatedResponse,
|
||||||
IOperationSchemaData,
|
IOperationSchemaData,
|
||||||
|
@ -19,26 +20,33 @@ export function getOssDetails(target: string, request: FrontPull<IOperationSchem
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchUpdatePositions(schema: string, request: FrontPush<IPositionsData>) {
|
export function patchUpdatePositions(oss: string, request: FrontPush<IPositionsData>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
endpoint: `/api/oss/${schema}/update-positions`,
|
endpoint: `/api/oss/${oss}/update-positions`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postCreateOperation(
|
export function postCreateOperation(
|
||||||
schema: string,
|
oss: string,
|
||||||
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
||||||
) {
|
) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/oss/${schema}/create-operation`,
|
endpoint: `/api/oss/${oss}/create-operation`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchDeleteOperation(schema: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
export function patchDeleteOperation(oss: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
endpoint: `/api/oss/${schema}/delete-operation`,
|
endpoint: `/api/oss/${oss}/delete-operation`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchCreateInput(oss: string, request: FrontExchange<ITargetOperation, IInputCreatedResponse>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/oss/${oss}/create-input`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,6 @@ export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu';
|
||||||
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
||||||
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
export { LuLightbulb as IconHelp } from 'react-icons/lu';
|
||||||
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu';
|
||||||
export { TbGridDots as IconGrid } from 'react-icons/tb';
|
|
||||||
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
export { RiPushpinFill as IconPin } from 'react-icons/ri';
|
||||||
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
export { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
||||||
export { BiCaretDown as IconSortDesc } from 'react-icons/bi';
|
export { BiCaretDown as IconSortDesc } from 'react-icons/bi';
|
||||||
|
@ -62,10 +61,12 @@ export { TbBriefcase as IconBusiness } from 'react-icons/tb';
|
||||||
export { VscLibrary as IconLibrary } from 'react-icons/vsc';
|
export { VscLibrary as IconLibrary } from 'react-icons/vsc';
|
||||||
export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
|
export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
|
||||||
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
||||||
export { FaRegObjectGroup as IconOSS } from 'react-icons/fa';
|
export { GiHoneycomb as IconOSS } from 'react-icons/gi';
|
||||||
export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
|
export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
|
||||||
export { LuArchive as IconArchive } from 'react-icons/lu';
|
export { LuArchive as IconArchive } from 'react-icons/lu';
|
||||||
export { LuDatabase as IconDatabase } from 'react-icons/lu';
|
export { LuDatabase as IconDatabase } from 'react-icons/lu';
|
||||||
|
export { LuView as IconDBStructure } from 'react-icons/lu';
|
||||||
|
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
|
||||||
export { LuImage as IconImage } from 'react-icons/lu';
|
export { LuImage as IconImage } from 'react-icons/lu';
|
||||||
export { TbColumns as IconList } from 'react-icons/tb';
|
export { TbColumns as IconList } from 'react-icons/tb';
|
||||||
export { ImStack as IconVersions } from 'react-icons/im';
|
export { ImStack as IconVersions } from 'react-icons/im';
|
||||||
|
@ -106,6 +107,8 @@ export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
||||||
export { LuNetwork as IconGenerateStructure } from 'react-icons/lu';
|
export { LuNetwork as IconGenerateStructure } from 'react-icons/lu';
|
||||||
export { LuBookCopy as IconInlineSynthesis } from 'react-icons/lu';
|
export { LuBookCopy as IconInlineSynthesis } from 'react-icons/lu';
|
||||||
export { LuWand2 as IconGenerateNames } from 'react-icons/lu';
|
export { LuWand2 as IconGenerateNames } from 'react-icons/lu';
|
||||||
|
export { GrConnect as IconConnect } from 'react-icons/gr';
|
||||||
|
export { BsPlay as IconExecute } from 'react-icons/bs';
|
||||||
|
|
||||||
// ======== Graph UI =======
|
// ======== Graph UI =======
|
||||||
export { BiCollapse as IconGraphCollapse } from 'react-icons/bi';
|
export { BiCollapse as IconGraphCollapse } from 'react-icons/bi';
|
||||||
|
@ -118,6 +121,11 @@ export { LuRotate3D as IconRotate3D } from 'react-icons/lu';
|
||||||
export { MdOutlineFitScreen as IconFitImage } from 'react-icons/md';
|
export { MdOutlineFitScreen as IconFitImage } from 'react-icons/md';
|
||||||
export { LuSparkles as IconClustering } from 'react-icons/lu';
|
export { LuSparkles as IconClustering } from 'react-icons/lu';
|
||||||
export { LuSparkle as IconClusteringOff } from 'react-icons/lu';
|
export { LuSparkle as IconClusteringOff } from 'react-icons/lu';
|
||||||
|
export { TbGridDots as IconGrid } from 'react-icons/tb';
|
||||||
|
export { FaSlash as IconLineStraight } from 'react-icons/fa6';
|
||||||
|
export { PiWaveSineLight as IconLineWave } from 'react-icons/pi';
|
||||||
|
export { LuCircleDashed as IconAnimation } from 'react-icons/lu';
|
||||||
|
export { LuCircle as IconAnimationOff } from 'react-icons/lu';
|
||||||
|
|
||||||
// ===== Custom elements ======
|
// ===== Custom elements ======
|
||||||
interface IconSVGProps {
|
interface IconSVGProps {
|
||||||
|
|
103
rsconcept/frontend/src/components/select/PickMultiOperation.tsx
Normal file
103
rsconcept/frontend/src/components/select/PickMultiOperation.tsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { IconRemove } from '@/components/Icons';
|
||||||
|
import SelectOperation from '@/components/select/SelectOperation';
|
||||||
|
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
|
||||||
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
import NoData from '@/components/ui/NoData';
|
||||||
|
import { IOperation, OperationID } from '@/models/oss';
|
||||||
|
|
||||||
|
interface PickMultiOperationProps {
|
||||||
|
rows?: number;
|
||||||
|
|
||||||
|
items: IOperation[];
|
||||||
|
selected: OperationID[];
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<IOperation>();
|
||||||
|
|
||||||
|
function PickMultiOperation({ rows, items, selected, setSelected }: PickMultiOperationProps) {
|
||||||
|
const selectedItems = useMemo(() => items.filter(item => selected.includes(item.id)), [items, selected]);
|
||||||
|
const nonSelectedItems = useMemo(() => items.filter(item => !selected.includes(item.id)), [items, selected]);
|
||||||
|
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(operation: OperationID) => setSelected(prev => prev.filter(item => item !== operation)),
|
||||||
|
[setSelected]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(operation?: IOperation) => {
|
||||||
|
if (operation) {
|
||||||
|
setLastSelected(operation);
|
||||||
|
setSelected(prev => [...prev, operation.id]);
|
||||||
|
setTimeout(() => setLastSelected(undefined), 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSelected]
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor('alias', {
|
||||||
|
id: 'alias',
|
||||||
|
header: 'Шифр',
|
||||||
|
size: 150,
|
||||||
|
minSize: 80,
|
||||||
|
maxSize: 150
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('title', {
|
||||||
|
id: 'title',
|
||||||
|
header: 'Название',
|
||||||
|
size: 1200,
|
||||||
|
minSize: 200,
|
||||||
|
maxSize: 1200,
|
||||||
|
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
|
cell: props => (
|
||||||
|
<MiniButton
|
||||||
|
noHover
|
||||||
|
title='Удалить'
|
||||||
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||||
|
onClick={() => handleDelete(props.row.original.id)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
],
|
||||||
|
[handleDelete]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-1 border-t border-x rounded-t-md clr-input'>
|
||||||
|
<SelectOperation
|
||||||
|
noBorder
|
||||||
|
items={nonSelectedItems} // prettier: split-line
|
||||||
|
value={lastSelected}
|
||||||
|
onSelectValue={handleSelect}
|
||||||
|
className='w-full'
|
||||||
|
/>
|
||||||
|
<DataTable
|
||||||
|
dense
|
||||||
|
noFooter
|
||||||
|
rows={rows}
|
||||||
|
contentHeight='1.3rem'
|
||||||
|
className='cc-scroll-y text-sm select-none border-y'
|
||||||
|
data={selectedItems}
|
||||||
|
columns={columns}
|
||||||
|
headPosition='0rem'
|
||||||
|
noDataComponent={
|
||||||
|
<NoData>
|
||||||
|
<p>Список пуст</p>
|
||||||
|
</NoData>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PickMultiOperation;
|
|
@ -15,7 +15,9 @@ interface SelectConstituentaProps extends CProps.Styling {
|
||||||
items?: IConstituenta[];
|
items?: IConstituenta[];
|
||||||
value?: IConstituenta;
|
value?: IConstituenta;
|
||||||
onSelectValue: (newValue?: IConstituenta) => void;
|
onSelectValue: (newValue?: IConstituenta) => void;
|
||||||
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectConstituenta({
|
function SelectConstituenta({
|
||||||
|
|
|
@ -13,7 +13,9 @@ interface SelectOperationProps extends CProps.Styling {
|
||||||
items?: IOperation[];
|
items?: IOperation[];
|
||||||
value?: IOperation;
|
value?: IOperation;
|
||||||
onSelectValue: (newValue?: IOperation) => void;
|
onSelectValue: (newValue?: IOperation) => void;
|
||||||
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectOperation({
|
function SelectOperation({
|
||||||
|
|
|
@ -13,8 +13,10 @@ import SelectSingle from '../ui/SelectSingle';
|
||||||
interface SelectUserProps extends CProps.Styling {
|
interface SelectUserProps extends CProps.Styling {
|
||||||
items?: IUserInfo[];
|
items?: IUserInfo[];
|
||||||
value?: UserID;
|
value?: UserID;
|
||||||
placeholder?: string;
|
|
||||||
onSelectValue: (newValue: UserID) => void;
|
onSelectValue: (newValue: UserID) => void;
|
||||||
|
|
||||||
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectUser({
|
function SelectUser({
|
||||||
|
|
|
@ -14,6 +14,9 @@ interface SelectVersionProps extends CProps.Styling {
|
||||||
items?: IVersionInfo[];
|
items?: IVersionInfo[];
|
||||||
value?: VersionID;
|
value?: VersionID;
|
||||||
onSelectValue: (newValue?: VersionID) => void;
|
onSelectValue: (newValue?: VersionID) => void;
|
||||||
|
|
||||||
|
placeholder?: string;
|
||||||
|
noBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
||||||
|
|
|
@ -13,11 +13,13 @@ import {
|
||||||
} from '@/backend/library';
|
} from '@/backend/library';
|
||||||
import { getRSFormDetails, postRSFormFromFile } from '@/backend/rsforms';
|
import { getRSFormDetails, postRSFormFromFile } from '@/backend/rsforms';
|
||||||
import { ErrorData } from '@/components/info/InfoError';
|
import { ErrorData } from '@/components/info/InfoError';
|
||||||
|
import useOssDetails from '@/hooks/useOssDetails';
|
||||||
import { FolderTree } from '@/models/FolderTree';
|
import { FolderTree } from '@/models/FolderTree';
|
||||||
import { ILibraryItem, LibraryItemID, LocationHead } from '@/models/library';
|
import { ILibraryItem, LibraryItemID, LocationHead } from '@/models/library';
|
||||||
import { ILibraryCreateData } from '@/models/library';
|
import { ILibraryCreateData } from '@/models/library';
|
||||||
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
|
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
|
||||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||||
|
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||||
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
|
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
|
||||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||||
import { contextOutsideScope } from '@/utils/labels';
|
import { contextOutsideScope } from '@/utils/labels';
|
||||||
|
@ -34,10 +36,17 @@ interface ILibraryContext {
|
||||||
loadingError: ErrorData;
|
loadingError: ErrorData;
|
||||||
setLoadingError: (error: ErrorData) => void;
|
setLoadingError: (error: ErrorData) => void;
|
||||||
|
|
||||||
|
globalOSS: IOperationSchema | undefined;
|
||||||
|
setGlobalID: (id: string | undefined) => void;
|
||||||
|
setGlobalOSS: (data: IOperationSchemaData) => void;
|
||||||
|
ossLoading: boolean;
|
||||||
|
ossError: ErrorData;
|
||||||
|
|
||||||
processing: boolean;
|
processing: boolean;
|
||||||
processingError: ErrorData;
|
processingError: ErrorData;
|
||||||
setProcessingError: (error: ErrorData) => void;
|
setProcessingError: (error: ErrorData) => void;
|
||||||
|
|
||||||
|
reloadOSS: (callback?: () => void) => void;
|
||||||
reloadItems: (callback?: () => void) => void;
|
reloadItems: (callback?: () => void) => void;
|
||||||
|
|
||||||
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
||||||
|
@ -75,6 +84,22 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||||
const [cachedTemplates, setCachedTemplates] = useState<IRSForm[]>([]);
|
const [cachedTemplates, setCachedTemplates] = useState<IRSForm[]>([]);
|
||||||
|
|
||||||
|
const [ossID, setGlobalID] = useState<string | undefined>(undefined);
|
||||||
|
const {
|
||||||
|
schema: globalOSS, // prettier: split lines
|
||||||
|
error: ossError,
|
||||||
|
setSchema: setGlobalOSS,
|
||||||
|
loading: ossLoading,
|
||||||
|
reload: reloadOssInternal
|
||||||
|
} = useOssDetails({ target: ossID });
|
||||||
|
|
||||||
|
const reloadOSS = useCallback(
|
||||||
|
(callback?: () => void) => {
|
||||||
|
reloadOssInternal(setProcessing, callback);
|
||||||
|
},
|
||||||
|
[reloadOssInternal]
|
||||||
|
);
|
||||||
|
|
||||||
const folders = useMemo(() => {
|
const folders = useMemo(() => {
|
||||||
const result = new FolderTree();
|
const result = new FolderTree();
|
||||||
result.addPath(LocationHead.USER, 0);
|
result.addPath(LocationHead.USER, 0);
|
||||||
|
@ -255,11 +280,17 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (globalOSS?.schemas.includes(target)) {
|
||||||
|
reloadOSS(() => {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[reloadItems, user]
|
[reloadItems, reloadOSS, user, globalOSS]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cloneItem = useCallback(
|
const cloneItem = useCallback(
|
||||||
|
@ -300,6 +331,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
processingError,
|
processingError,
|
||||||
setProcessingError,
|
setProcessingError,
|
||||||
|
|
||||||
|
globalOSS,
|
||||||
|
setGlobalID,
|
||||||
|
setGlobalOSS,
|
||||||
|
ossLoading,
|
||||||
|
ossError,
|
||||||
|
reloadOSS,
|
||||||
|
|
||||||
reloadItems,
|
reloadItems,
|
||||||
|
|
||||||
applyFilter,
|
applyFilter,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { DataCallback } from '@/backend/apiTransport';
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
import {
|
import {
|
||||||
|
@ -12,12 +12,18 @@ import {
|
||||||
patchSetOwner,
|
patchSetOwner,
|
||||||
postSubscribe
|
postSubscribe
|
||||||
} from '@/backend/library';
|
} from '@/backend/library';
|
||||||
import { patchDeleteOperation, patchUpdatePositions, postCreateOperation } from '@/backend/oss';
|
import { patchCreateInput, patchDeleteOperation, patchUpdatePositions, postCreateOperation } from '@/backend/oss';
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
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 { IOperation, IOperationCreateData, IOperationSchema, IPositionsData, ITargetOperation } from '@/models/oss';
|
import {
|
||||||
|
IOperation,
|
||||||
|
IOperationCreateData,
|
||||||
|
IOperationSchema,
|
||||||
|
IOperationSchemaData,
|
||||||
|
IPositionsData,
|
||||||
|
ITargetOperation
|
||||||
|
} from '@/models/oss';
|
||||||
import { UserID } from '@/models/user';
|
import { UserID } from '@/models/user';
|
||||||
import { contextOutsideScope } from '@/utils/labels';
|
import { contextOutsideScope } from '@/utils/labels';
|
||||||
|
|
||||||
|
@ -48,6 +54,7 @@ interface IOssContext {
|
||||||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
||||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
||||||
|
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssContext = createContext<IOssContext | null>(null);
|
const OssContext = createContext<IOssContext | null>(null);
|
||||||
|
@ -66,13 +73,8 @@ interface OssStateProps {
|
||||||
|
|
||||||
export const OssState = ({ itemID, children }: OssStateProps) => {
|
export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
const library = useLibrary();
|
const library = useLibrary();
|
||||||
|
const schema = library.globalOSS;
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const {
|
|
||||||
schema, // prettier: split lines
|
|
||||||
error: errorLoading,
|
|
||||||
setSchema,
|
|
||||||
loading
|
|
||||||
} = useOssDetails({ target: itemID });
|
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||||
|
|
||||||
|
@ -90,6 +92,12 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [user, schema, toggleTracking]);
|
}, [user, schema, toggleTracking]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (schema?.id !== Number(itemID)) {
|
||||||
|
library.setGlobalID(itemID);
|
||||||
|
}
|
||||||
|
}, [itemID, schema, library]);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -102,13 +110,14 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setSchema(Object.assign(schema, newData));
|
const fullData: IOperationSchemaData = Object.assign(schema, newData);
|
||||||
|
library.setGlobalOSS(fullData);
|
||||||
library.localUpdateItem(newData);
|
library.localUpdateItem(newData);
|
||||||
if (callback) callback(newData);
|
if (callback) callback(newData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, setSchema, schema, library]
|
[itemID, schema, library]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subscribe = useCallback(
|
const subscribe = useCallback(
|
||||||
|
@ -133,7 +142,7 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, schema, user]
|
[itemID, user, schema]
|
||||||
);
|
);
|
||||||
|
|
||||||
const unsubscribe = useCallback(
|
const unsubscribe = useCallback(
|
||||||
|
@ -278,13 +287,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setSchema(newData.oss);
|
library.setGlobalOSS(newData.oss);
|
||||||
library.localUpdateTimestamp(newData.oss.id);
|
library.localUpdateTimestamp(newData.oss.id);
|
||||||
if (callback) callback(newData.new_operation);
|
if (callback) callback(newData.new_operation);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, library, setSchema]
|
[itemID, library]
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteOperation = useCallback(
|
const deleteOperation = useCallback(
|
||||||
|
@ -296,13 +305,32 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setSchema(newData);
|
library.setGlobalOSS(newData);
|
||||||
library.localUpdateTimestamp(newData.id);
|
library.localUpdateTimestamp(newData.id);
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, library, setSchema]
|
[itemID, library]
|
||||||
|
);
|
||||||
|
|
||||||
|
const createInput = useCallback(
|
||||||
|
(data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => {
|
||||||
|
setProcessingError(undefined);
|
||||||
|
patchCreateInput(itemID, {
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: setProcessingError,
|
||||||
|
onSuccess: newData => {
|
||||||
|
library.setGlobalOSS(newData.oss);
|
||||||
|
library.reloadItems(() => {
|
||||||
|
if (callback) callback(newData.new_schema);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[itemID, library]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -310,8 +338,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
value={{
|
value={{
|
||||||
schema,
|
schema,
|
||||||
itemID,
|
itemID,
|
||||||
loading,
|
loading: library.ossLoading,
|
||||||
errorLoading,
|
errorLoading: library.ossError,
|
||||||
processing,
|
processing,
|
||||||
processingError,
|
processingError,
|
||||||
isOwned,
|
isOwned,
|
||||||
|
@ -327,7 +355,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
|
|
||||||
savePositions,
|
savePositions,
|
||||||
createOperation,
|
createOperation,
|
||||||
deleteOperation
|
deleteOperation,
|
||||||
|
createInput
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import SelectOperation from '@/components/select/SelectOperation';
|
|
||||||
import FlexColumn from '@/components/ui/FlexColumn';
|
import FlexColumn from '@/components/ui/FlexColumn';
|
||||||
import Label from '@/components/ui/Label';
|
import Label from '@/components/ui/Label';
|
||||||
import TextArea from '@/components/ui/TextArea';
|
import TextArea from '@/components/ui/TextArea';
|
||||||
import TextInput from '@/components/ui/TextInput';
|
import TextInput from '@/components/ui/TextInput';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { IOperation, IOperationSchema, OperationID } from '@/models/oss';
|
import { IOperationSchema, OperationID } from '@/models/oss';
|
||||||
import { limits, patterns } from '@/utils/constants';
|
import { limits, patterns } from '@/utils/constants';
|
||||||
|
|
||||||
|
import PickMultiOperation from '../../components/select/PickMultiOperation';
|
||||||
|
|
||||||
interface TabSynthesisOperationProps {
|
interface TabSynthesisOperationProps {
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
alias: string;
|
alias: string;
|
||||||
|
@ -34,22 +31,6 @@ function TabSynthesisOperation({
|
||||||
inputs,
|
inputs,
|
||||||
setInputs
|
setInputs
|
||||||
}: TabSynthesisOperationProps) {
|
}: TabSynthesisOperationProps) {
|
||||||
const [left, setLeft] = useState<IOperation | undefined>(undefined);
|
|
||||||
const [right, setRight] = useState<IOperation | undefined>(undefined);
|
|
||||||
|
|
||||||
console.log(inputs);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const inputs: OperationID[] = [];
|
|
||||||
if (left) {
|
|
||||||
inputs.push(left.id);
|
|
||||||
}
|
|
||||||
if (right) {
|
|
||||||
inputs.push(right.id);
|
|
||||||
}
|
|
||||||
setInputs(inputs);
|
|
||||||
}, [setInputs, left, right]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimateFade className='cc-column'>
|
<AnimateFade className='cc-column'>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -79,16 +60,10 @@ function TabSynthesisOperation({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-between'>
|
|
||||||
<FlexColumn>
|
<FlexColumn>
|
||||||
<Label text='Аргумент 1' />
|
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
|
||||||
<SelectOperation items={oss.items} value={left} onSelectValue={setLeft} />
|
<PickMultiOperation items={oss.items} selected={inputs} setSelected={setInputs} rows={6} />
|
||||||
</FlexColumn>
|
</FlexColumn>
|
||||||
<FlexColumn>
|
|
||||||
<Label text='Аргумент 2' className='text-right' />
|
|
||||||
<SelectOperation items={oss.items} value={right} onSelectValue={setRight} />
|
|
||||||
</FlexColumn>
|
|
||||||
</div>
|
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
@import 'styling/setup.css';
|
@import 'styling/setup.css';
|
||||||
@import 'styling/styles.css';
|
|
||||||
@import 'styling/imports.css';
|
|
||||||
@import 'styling/overrides.css';
|
@import 'styling/overrides.css';
|
||||||
|
@import 'styling/styles.css';
|
||||||
|
|
|
@ -40,6 +40,7 @@ export interface OssNodeInternal {
|
||||||
label: string;
|
label: string;
|
||||||
operation: IOperation;
|
operation: IOperation;
|
||||||
};
|
};
|
||||||
|
dragging: boolean;
|
||||||
xPos: number;
|
xPos: number;
|
||||||
yPos: number;
|
yPos: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Graph } from './Graph';
|
import { Graph } from './Graph';
|
||||||
import { ILibraryItemData, LibraryItemID } from './library';
|
import { ILibraryItem, ILibraryItemData, LibraryItemID } from './library';
|
||||||
import { ConstituentaID } from './rsform';
|
import { ConstituentaID } from './rsform';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,3 +139,11 @@ export interface IOperationCreatedResponse {
|
||||||
new_operation: IOperation;
|
new_operation: IOperation;
|
||||||
oss: IOperationSchemaData;
|
oss: IOperationSchemaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IRSForm} for Input {@link IOperation}.
|
||||||
|
*/
|
||||||
|
export interface IInputCreatedResponse {
|
||||||
|
new_schema: ILibraryItem;
|
||||||
|
oss: IOperationSchemaData;
|
||||||
|
}
|
||||||
|
|
31
rsconcept/frontend/src/pages/DatabaseSchemaPage.tsx
Normal file
31
rsconcept/frontend/src/pages/DatabaseSchemaPage.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useLayoutEffect, useMemo } from 'react';
|
||||||
|
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
|
||||||
|
|
||||||
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
import { resources } from '@/utils/constants';
|
||||||
|
|
||||||
|
function DatabaseSchemaPage() {
|
||||||
|
const { calculateHeight, setNoFooter } = useConceptOptions();
|
||||||
|
|
||||||
|
const panelHeight = useMemo(() => calculateHeight('0px'), [calculateHeight]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setNoFooter(true);
|
||||||
|
return () => setNoFooter(false);
|
||||||
|
}, [setNoFooter]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimateFade className='flex justify-center overflow-hidden' style={{ maxHeight: panelHeight }}>
|
||||||
|
<TransformWrapper>
|
||||||
|
<TransformComponent>
|
||||||
|
<img alt='Схема базы данных' src={resources.db_schema} className='w-fit h-fit' />
|
||||||
|
</TransformComponent>
|
||||||
|
</TransformWrapper>
|
||||||
|
</AnimateFade>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DatabaseSchemaPage;
|
|
@ -4,13 +4,12 @@
|
||||||
import * as icons from '@/components/Icons';
|
import * as icons from '@/components/Icons';
|
||||||
|
|
||||||
export function IconsPage() {
|
export function IconsPage() {
|
||||||
|
const iconsList = Object.keys(icons).filter(key => key.startsWith('Icon'));
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center px-6 py-3'>
|
<div className='flex flex-col items-center px-6 py-3'>
|
||||||
<h1 className='mb-6'>Список иконок</h1>
|
<h1 className='mb-6'>Всего иконок: {iconsList.length}</h1>
|
||||||
<div className='grid grid-cols-4'>
|
<div className='grid grid-cols-4'>
|
||||||
{Object.keys(icons)
|
{iconsList.map((key, index) => (
|
||||||
.filter(key => key.startsWith('Icon'))
|
|
||||||
.map((key, index) => (
|
|
||||||
<div key={`icons_list_${index}`} className='flex flex-col items-center px-3 pb-6'>
|
<div key={`icons_list_${index}`} className='flex flex-col items-center px-3 pb-6'>
|
||||||
<p>{icons[key]({ size: '2rem' })}</p>
|
<p>{icons[key]({ size: '2rem' })}</p>
|
||||||
<p>{key}</p>
|
<p>{key}</p>
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
|
||||||
import { storage } from '@/utils/constants';
|
|
||||||
|
|
||||||
import OssFlow from './OssFlow';
|
import OssFlow from './OssFlow';
|
||||||
|
|
||||||
interface EditorOssGraphProps {
|
interface EditorOssGraphProps {
|
||||||
|
@ -13,11 +10,9 @@ interface EditorOssGraphProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
||||||
const [showGrid, setShowGrid] = useLocalStorage<boolean>(storage.ossShowGrid, false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<OssFlow isModified={isModified} setIsModified={setIsModified} showGrid={showGrid} setShowGrid={setShowGrid} />
|
<OssFlow isModified={isModified} setIsModified={setIsModified} />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ function InputNode(node: OssNodeInternal) {
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
noHover
|
noHover
|
||||||
title='Связанная КС'
|
title='Связанная КС'
|
||||||
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOpenSchema();
|
handleOpenSchema();
|
||||||
}}
|
}}
|
||||||
|
@ -34,7 +35,7 @@ function InputNode(node: OssNodeInternal) {
|
||||||
</Overlay>
|
</Overlay>
|
||||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||||
{node.data.label}
|
{node.data.label}
|
||||||
{controller.showTooltip ? (
|
{controller.showTooltip && !node.dragging ? (
|
||||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { IconDestroy, IconEdit2, IconNewItem, IconRSForm } from '@/components/Icons';
|
import { IconConnect, IconDestroy, IconEdit2, IconExecute, IconNewItem, IconRSForm } from '@/components/Icons';
|
||||||
import Dropdown from '@/components/ui/Dropdown';
|
import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import useClickedOutside from '@/hooks/useClickedOutside';
|
import useClickedOutside from '@/hooks/useClickedOutside';
|
||||||
|
@ -22,9 +22,10 @@ export interface ContextMenuData {
|
||||||
interface NodeContextMenuProps extends ContextMenuData {
|
interface NodeContextMenuProps extends ContextMenuData {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
onDelete: (target: OperationID) => void;
|
onDelete: (target: OperationID) => void;
|
||||||
|
onCreateInput: (target: OperationID) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: NodeContextMenuProps) {
|
function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete, onCreateInput }: NodeContextMenuProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
@ -57,11 +58,21 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node
|
||||||
onDelete(operation.id);
|
onDelete(operation.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCreateSchema = () => {
|
||||||
|
handleHide();
|
||||||
|
onCreateInput(operation.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRunSynthesis = () => {
|
||||||
|
toast.error('Not implemented');
|
||||||
|
handleHide();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
||||||
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Свойства операции'
|
text='Редактировать'
|
||||||
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')}
|
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')}
|
||||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
|
@ -83,16 +94,25 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node
|
||||||
title='Создать пустую схему для загрузки'
|
title='Создать пустую схему для загрузки'
|
||||||
icon={<IconNewItem size='1rem' className='icon-green' />}
|
icon={<IconNewItem size='1rem' className='icon-green' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
|
onClick={handleCreateSchema}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{controller.isMutable && !operation.result && operation.operation_type === OperationType.INPUT ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='Загрузить схему'
|
||||||
|
title='Выбрать схему для загрузки'
|
||||||
|
icon={<IconConnect size='1rem' className='icon-primary' />}
|
||||||
|
disabled={controller.isProcessing}
|
||||||
onClick={handleEditSchema}
|
onClick={handleEditSchema}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{controller.isMutable && operation.operation_type === OperationType.INPUT ? (
|
{controller.isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Привязать схему'
|
text='Выполнить синтез'
|
||||||
title='Выбрать схему для загрузки'
|
title='Выполнить операцию и получить синтезированную КС'
|
||||||
icon={<IconRSForm size='1rem' className='icon-primary' />}
|
icon={<IconExecute size='1rem' className='icon-green' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
onClick={handleEditSchema}
|
onClick={handleRunSynthesis}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ function OperationNode(node: OssNodeInternal) {
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
noHover
|
noHover
|
||||||
title='Связанная КС'
|
title='Связанная КС'
|
||||||
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOpenSchema();
|
handleOpenSchema();
|
||||||
}}
|
}}
|
||||||
|
@ -36,7 +37,9 @@ function OperationNode(node: OssNodeInternal) {
|
||||||
|
|
||||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||||
{node.data.label}
|
{node.data.label}
|
||||||
|
{controller.showTooltip && !node.dragging ? (
|
||||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ProOptions,
|
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
|
@ -23,9 +22,10 @@ import Overlay from '@/components/ui/Overlay';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
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 useLocalStorage from '@/hooks/useLocalStorage';
|
||||||
import { OssNode } from '@/models/miscellaneous';
|
import { OssNode } from '@/models/miscellaneous';
|
||||||
import { OperationID } from '@/models/oss';
|
import { OperationID } from '@/models/oss';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER, storage } from '@/utils/constants';
|
||||||
import { errors } from '@/utils/labels';
|
import { errors } from '@/utils/labels';
|
||||||
|
|
||||||
import { useOssEdit } from '../OssEditContext';
|
import { useOssEdit } from '../OssEditContext';
|
||||||
|
@ -37,16 +37,18 @@ import ToolbarOssGraph from './ToolbarOssGraph';
|
||||||
interface OssFlowProps {
|
interface OssFlowProps {
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
showGrid: boolean;
|
|
||||||
setShowGrid: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowProps) {
|
function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
const { calculateHeight, colors } = useConceptOptions();
|
const { calculateHeight, colors } = useConceptOptions();
|
||||||
const model = useOSS();
|
const model = useOSS();
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
const flow = useReactFlow();
|
const flow = useReactFlow();
|
||||||
|
|
||||||
|
const [showGrid, setShowGrid] = useLocalStorage<boolean>(storage.ossShowGrid, false);
|
||||||
|
const [edgeAnimate, setEdgeAnimate] = useLocalStorage<boolean>(storage.ossEdgeAnimate, false);
|
||||||
|
const [edgeStraight, setEdgeStraight] = useLocalStorage<boolean>(storage.ossEdgeStraight, false);
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
const [toggleReset, setToggleReset] = useState(false);
|
const [toggleReset, setToggleReset] = useState(false);
|
||||||
|
@ -81,6 +83,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
id: String(index),
|
id: String(index),
|
||||||
source: String(argument.argument),
|
source: String(argument.argument),
|
||||||
target: String(argument.operation),
|
target: String(argument.operation),
|
||||||
|
type: edgeStraight ? 'straight' : 'simplebezier',
|
||||||
|
animated: edgeAnimate,
|
||||||
targetHandle:
|
targetHandle:
|
||||||
model.schema!.operationByID.get(argument.argument)!.position_x >
|
model.schema!.operationByID.get(argument.argument)!.position_x >
|
||||||
model.schema!.operationByID.get(argument.operation)!.position_x
|
model.schema!.operationByID.get(argument.operation)!.position_x
|
||||||
|
@ -92,7 +96,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
}, PARAMETER.graphRefreshDelay);
|
}, PARAMETER.graphRefreshDelay);
|
||||||
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset]);
|
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
|
||||||
|
|
||||||
const getPositions = useCallback(
|
const getPositions = useCallback(
|
||||||
() =>
|
() =>
|
||||||
|
@ -137,6 +141,13 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
[controller, getPositions]
|
[controller, getPositions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCreateInput = useCallback(
|
||||||
|
(target: OperationID) => {
|
||||||
|
controller.createInput(target, getPositions());
|
||||||
|
},
|
||||||
|
[controller, getPositions]
|
||||||
|
);
|
||||||
|
|
||||||
const handleFitView = useCallback(() => {
|
const handleFitView = useCallback(() => {
|
||||||
flow.fitView({ duration: PARAMETER.zoomDuration });
|
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||||
}, [flow]);
|
}, [flow]);
|
||||||
|
@ -180,7 +191,6 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: CProps.EventMouse, node: OssNode) => {
|
(event: CProps.EventMouse, node: OssNode) => {
|
||||||
console.log(node);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -224,7 +234,6 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const proOptions: ProOptions = useMemo(() => ({ hideAttribution: true }), []);
|
|
||||||
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
|
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
|
||||||
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||||
|
|
||||||
|
@ -244,7 +253,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
onNodesChange={handleNodesChange}
|
onNodesChange={handleNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
fitView
|
fitView
|
||||||
proOptions={proOptions}
|
proOptions={{ hideAttribution: true }}
|
||||||
nodeTypes={OssNodeTypes}
|
nodeTypes={OssNodeTypes}
|
||||||
maxZoom={2}
|
maxZoom={2}
|
||||||
minZoom={0.75}
|
minZoom={0.75}
|
||||||
|
@ -257,17 +266,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
{showGrid ? <Background gap={10} /> : null}
|
{showGrid ? <Background gap={10} /> : null}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
),
|
),
|
||||||
[
|
[nodes, edges, handleNodesChange, handleContextMenu, handleClickCanvas, onEdgesChange, OssNodeTypes, showGrid]
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
proOptions,
|
|
||||||
handleNodesChange,
|
|
||||||
handleContextMenu,
|
|
||||||
handleClickCanvas,
|
|
||||||
onEdgesChange,
|
|
||||||
OssNodeTypes,
|
|
||||||
showGrid
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -276,6 +275,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
<ToolbarOssGraph
|
<ToolbarOssGraph
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
showGrid={showGrid}
|
showGrid={showGrid}
|
||||||
|
edgeAnimate={edgeAnimate}
|
||||||
|
edgeStraight={edgeStraight}
|
||||||
onFitView={handleFitView}
|
onFitView={handleFitView}
|
||||||
onCreate={handleCreateOperation}
|
onCreate={handleCreateOperation}
|
||||||
onDelete={handleDeleteSelected}
|
onDelete={handleDeleteSelected}
|
||||||
|
@ -283,10 +284,17 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
||||||
onSavePositions={handleSavePositions}
|
onSavePositions={handleSavePositions}
|
||||||
onSaveImage={handleSaveImage}
|
onSaveImage={handleSaveImage}
|
||||||
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
||||||
|
toggleEdgeAnimate={() => setEdgeAnimate(prev => !prev)}
|
||||||
|
toggleEdgeStraight={() => setEdgeStraight(prev => !prev)}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
{menuProps ? (
|
{menuProps ? (
|
||||||
<NodeContextMenu onHide={handleContextMenuHide} onDelete={handleDeleteOperation} {...menuProps} />
|
<NodeContextMenu
|
||||||
|
onHide={handleContextMenuHide}
|
||||||
|
onDelete={handleDeleteOperation}
|
||||||
|
onCreateInput={handleCreateInput}
|
||||||
|
{...menuProps}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||||
{graph}
|
{graph}
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { IconDestroy, IconFitImage, IconGrid, IconImage, IconNewItem, IconReset, IconSave } from '@/components/Icons';
|
import {
|
||||||
|
IconAnimation,
|
||||||
|
IconAnimationOff,
|
||||||
|
IconDestroy,
|
||||||
|
IconFitImage,
|
||||||
|
IconGrid,
|
||||||
|
IconImage,
|
||||||
|
IconLineStraight,
|
||||||
|
IconLineWave,
|
||||||
|
IconNewItem,
|
||||||
|
IconReset,
|
||||||
|
IconSave
|
||||||
|
} from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
@ -12,6 +24,8 @@ import { useOssEdit } from '../OssEditContext';
|
||||||
interface ToolbarOssGraphProps {
|
interface ToolbarOssGraphProps {
|
||||||
isModified: boolean;
|
isModified: boolean;
|
||||||
showGrid: boolean;
|
showGrid: boolean;
|
||||||
|
edgeAnimate: boolean;
|
||||||
|
edgeStraight: boolean;
|
||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onFitView: () => void;
|
onFitView: () => void;
|
||||||
|
@ -19,39 +33,30 @@ interface ToolbarOssGraphProps {
|
||||||
onSavePositions: () => void;
|
onSavePositions: () => void;
|
||||||
onResetPositions: () => void;
|
onResetPositions: () => void;
|
||||||
toggleShowGrid: () => void;
|
toggleShowGrid: () => void;
|
||||||
|
toggleEdgeAnimate: () => void;
|
||||||
|
toggleEdgeStraight: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolbarOssGraph({
|
function ToolbarOssGraph({
|
||||||
isModified,
|
isModified,
|
||||||
showGrid,
|
showGrid,
|
||||||
|
edgeAnimate,
|
||||||
|
edgeStraight,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onFitView,
|
onFitView,
|
||||||
onSaveImage,
|
onSaveImage,
|
||||||
onSavePositions,
|
onSavePositions,
|
||||||
onResetPositions,
|
onResetPositions,
|
||||||
toggleShowGrid
|
toggleShowGrid,
|
||||||
|
toggleEdgeAnimate,
|
||||||
|
toggleEdgeStraight
|
||||||
}: ToolbarOssGraphProps) {
|
}: ToolbarOssGraphProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className='flex flex-col items-center'>
|
||||||
<div className='cc-icons'>
|
<div className='cc-icons'>
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
|
||||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
|
||||||
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
|
||||||
disabled={controller.isProcessing || !isModified}
|
|
||||||
onClick={onSavePositions}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
|
||||||
title='Сбросить изменения'
|
|
||||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
|
||||||
disabled={!isModified}
|
|
||||||
onClick={onResetPositions}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||||
title='Сбросить вид'
|
title='Сбросить вид'
|
||||||
|
@ -68,22 +73,28 @@ function ToolbarOssGraph({
|
||||||
}
|
}
|
||||||
onClick={toggleShowGrid}
|
onClick={toggleShowGrid}
|
||||||
/>
|
/>
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Новая операция'
|
title={edgeStraight ? 'Связи: прямые' : 'Связи: безье'}
|
||||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
icon={
|
||||||
disabled={controller.isProcessing}
|
edgeStraight ? (
|
||||||
onClick={onCreate}
|
<IconLineStraight size='1.25rem' className='icon-primary' />
|
||||||
|
) : (
|
||||||
|
<IconLineWave size='1.25rem' className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleEdgeStraight}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
{controller.isMutable ? (
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Удалить выбранную'
|
title={edgeAnimate ? 'Анимация: вкл' : 'Анимация: выкл'}
|
||||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
icon={
|
||||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
edgeAnimate ? (
|
||||||
onClick={onDelete}
|
<IconAnimation size='1.25rem' className='icon-primary' />
|
||||||
|
) : (
|
||||||
|
<IconAnimationOff size='1.25rem' className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleEdgeAnimate}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
||||||
title='Сохранить изображение'
|
title='Сохранить изображение'
|
||||||
|
@ -95,6 +106,36 @@ function ToolbarOssGraph({
|
||||||
offset={4}
|
offset={4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{controller.isMutable ? (
|
||||||
|
<div className='cc-icons'>
|
||||||
|
{' '}
|
||||||
|
<MiniButton
|
||||||
|
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||||
|
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
||||||
|
disabled={controller.isProcessing || !isModified}
|
||||||
|
onClick={onSavePositions}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Сбросить изменения'
|
||||||
|
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||||
|
disabled={!isModified}
|
||||||
|
onClick={onResetPositions}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Новая операция'
|
||||||
|
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||||
|
disabled={controller.isProcessing}
|
||||||
|
onClick={onCreate}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Удалить выбранную'
|
||||||
|
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||||
|
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
||||||
|
onClick={onDelete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ export interface IOssEditContext {
|
||||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||||
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
||||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
|
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||||
|
@ -210,6 +211,16 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
[model]
|
[model]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createInput = useCallback(
|
||||||
|
(target: OperationID, positions: IOperationPosition[]) => {
|
||||||
|
model.createInput({ target: target, positions: positions }, new_schema => {
|
||||||
|
toast.success(information.newLibraryItem);
|
||||||
|
router.push(urls.schema(new_schema.id));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[model, router]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OssEditContext.Provider
|
<OssEditContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -234,7 +245,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
openOperationSchema,
|
openOperationSchema,
|
||||||
savePositions,
|
savePositions,
|
||||||
promptCreateOperation,
|
promptCreateOperation,
|
||||||
deleteOperation
|
deleteOperation,
|
||||||
|
createInput
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{model.schema ? (
|
{model.schema ? (
|
||||||
|
|
|
@ -33,7 +33,7 @@ export enum OssTabID {
|
||||||
function OssTabs() {
|
function OssTabs() {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const query = useQueryStrings();
|
const query = useQueryStrings();
|
||||||
const activeTab = (Number(query.get('tab')) ?? OssTabID.CARD) as OssTabID;
|
const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH;
|
||||||
|
|
||||||
const { calculateHeight } = useConceptOptions();
|
const { calculateHeight } = useConceptOptions();
|
||||||
const { schema, loading, errorLoading } = useOSS();
|
const { schema, loading, errorLoading } = useOSS();
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
IconLibrary,
|
IconLibrary,
|
||||||
IconMenu,
|
IconMenu,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
IconOSS,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
IconReader,
|
IconReader,
|
||||||
IconReplace,
|
IconReplace,
|
||||||
|
@ -29,6 +30,7 @@ import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
import { useRSForm } from '@/context/RSFormContext';
|
import { useRSForm } from '@/context/RSFormContext';
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
|
@ -36,6 +38,7 @@ import { AccessPolicy } from '@/models/library';
|
||||||
import { UserLevel } from '@/models/user';
|
import { UserLevel } from '@/models/user';
|
||||||
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
|
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { OssTabID } from '../OssPage/OssTabs';
|
||||||
import { useRSEdit } from './RSEditContext';
|
import { useRSEdit } from './RSEditContext';
|
||||||
|
|
||||||
interface MenuRSTabsProps {
|
interface MenuRSTabsProps {
|
||||||
|
@ -47,6 +50,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const model = useRSForm();
|
const model = useRSForm();
|
||||||
|
const library = useLibrary();
|
||||||
|
|
||||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||||
|
|
||||||
|
@ -181,6 +185,13 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
onClick={handleCreateNew}
|
onClick={handleCreateNew}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{library.globalOSS ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='Перейти к ОСС'
|
||||||
|
icon={<IconOSS size='1rem' className='icon-primary' />}
|
||||||
|
onClick={() => router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH))}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Библиотека'
|
text='Библиотека'
|
||||||
icon={<IconLibrary size='1rem' className='icon-primary' />}
|
icon={<IconLibrary size='1rem' className='icon-primary' />}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { ConstituentaID, IConstituenta, IConstituentaMeta } from '@/models/rsfor
|
||||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||||
import { information, labelVersion, prompts } from '@/utils/labels';
|
import { information, labelVersion, prompts } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { OssTabID } from '../OssPage/OssTabs';
|
||||||
import EditorConstituenta from './EditorConstituenta';
|
import EditorConstituenta from './EditorConstituenta';
|
||||||
import EditorRSForm from './EditorRSFormCard';
|
import EditorRSForm from './EditorRSFormCard';
|
||||||
import EditorRSList from './EditorRSList';
|
import EditorRSList from './EditorRSList';
|
||||||
|
@ -39,13 +40,13 @@ export enum RSTabID {
|
||||||
function RSTabs() {
|
function RSTabs() {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const query = useQueryStrings();
|
const query = useQueryStrings();
|
||||||
const activeTab = (Number(query.get('tab')) ?? RSTabID.CARD) as RSTabID;
|
const activeTab = query.get('tab') ? (Number(query.get('tab')) as RSTabID) : RSTabID.CARD;
|
||||||
const version = query.get('v') ? Number(query.get('v')) : undefined;
|
const version = query.get('v') ? Number(query.get('v')) : undefined;
|
||||||
const cstQuery = query.get('active');
|
const cstQuery = query.get('active');
|
||||||
|
|
||||||
const { setNoFooter, calculateHeight } = useConceptOptions();
|
const { setNoFooter, calculateHeight } = useConceptOptions();
|
||||||
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
||||||
const { destroyItem } = useLibrary();
|
const library = useLibrary();
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(false);
|
const [isModified, setIsModified] = useState(false);
|
||||||
useBlockNavigation(isModified);
|
useBlockNavigation(isModified);
|
||||||
|
@ -176,11 +177,16 @@ function RSTabs() {
|
||||||
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
destroyItem(schema.id, () => {
|
const backToOSS = library.globalOSS?.schemas.includes(schema.id);
|
||||||
|
library.destroyItem(schema.id, () => {
|
||||||
toast.success(information.itemDestroyed);
|
toast.success(information.itemDestroyed);
|
||||||
|
if (backToOSS) {
|
||||||
|
router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH));
|
||||||
|
} else {
|
||||||
router.push(urls.library);
|
router.push(urls.library);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [schema, destroyItem, router]);
|
}, [schema, library, router]);
|
||||||
|
|
||||||
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* Module: Override imported components CSS styling.
|
* Module: Override imported components CSS styling.
|
||||||
*/
|
*/
|
||||||
|
@import './constants.css';
|
||||||
|
@import './imports.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Import overrides */
|
/* Import overrides */
|
||||||
|
|
|
@ -54,7 +54,8 @@ export const patterns = {
|
||||||
export const resources = {
|
export const resources = {
|
||||||
graph_font: '/DejaVu.ttf',
|
graph_font: '/DejaVu.ttf',
|
||||||
privacy_policy: '/privacy.pdf',
|
privacy_policy: '/privacy.pdf',
|
||||||
logo: '/logo_full.svg'
|
logo: '/logo_full.svg',
|
||||||
|
db_schema: '/db_schema.svg'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,6 +115,8 @@ export const storage = {
|
||||||
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
||||||
|
|
||||||
ossShowGrid: 'oss.show_grid',
|
ossShowGrid: 'oss.show_grid',
|
||||||
|
ossEdgeStraight: 'oss.edge_straight',
|
||||||
|
ossEdgeAnimate: 'oss.edge_animate',
|
||||||
|
|
||||||
cstFilterMatch: 'cst.filter.match',
|
cstFilterMatch: 'cst.filter.match',
|
||||||
cstFilterGraph: 'cst.filter.graph'
|
cstFilterGraph: 'cst.filter.graph'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user