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",
|
||||
"SIDELIST",
|
||||
"signup",
|
||||
"simplebezier",
|
||||
"Slng",
|
||||
"SMALLPR",
|
||||
"Stylesheet",
|
||||
|
|
|
@ -39,6 +39,7 @@ This readme file is used mostly to document project dependencies and conventions
|
|||
- react-error-boundary
|
||||
- react-pdf
|
||||
- react-tooltip
|
||||
- react-zoom-pan-pinch
|
||||
- reactflow
|
||||
- js-file-download
|
||||
- use-debounce
|
||||
|
@ -143,7 +144,8 @@ This readme file is used mostly to document project dependencies and conventions
|
|||
|
||||
- 🚀 F: major feature implementation
|
||||
- 💄 D: UI design
|
||||
- 🚑 B: bug fix
|
||||
- 🔥 B: bug fix
|
||||
- 🚑 M: Minor fixes
|
||||
- 🔧 R: refactoring and code improvement
|
||||
- 📝 I: documentation
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ from .basics import OperationPositionSerializer, PositionsSerializer, Substituti
|
|||
from .data_access import (
|
||||
ArgumentSerializer,
|
||||
OperationCreateSerializer,
|
||||
OperationDeleteSerializer,
|
||||
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. '''
|
||||
target = PKField(many=False, queryset=Operation.objects.all())
|
||||
positions = serializers.ListField(
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.library.serializers import LibraryItemSerializer
|
||||
|
||||
from .data_access import OperationSchemaSerializer, OperationSerializer
|
||||
|
||||
|
||||
|
@ -8,3 +10,9 @@ class NewOperationResponse(serializers.Serializer):
|
|||
''' Serializer: Create operation response. '''
|
||||
new_operation = OperationSerializer()
|
||||
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')
|
||||
def test_create_operation(self):
|
||||
|
||||
|
||||
self.populateData()
|
||||
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.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')
|
||||
def test_delete_operation(self):
|
||||
self.executeNotFound(item=self.invalid_id)
|
||||
|
@ -269,3 +250,40 @@ class TestOssViewset(EndpointTester):
|
|||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
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 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 viewsets
|
||||
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.serializers import LibraryItemSerializer
|
||||
from shared import messages as msg
|
||||
from shared import permissions
|
||||
|
||||
from .. import models as m
|
||||
|
@ -33,7 +34,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
if self.action in [
|
||||
'create_operation',
|
||||
'delete_operation',
|
||||
'update_positions'
|
||||
'update_positions',
|
||||
'create_input'
|
||||
]:
|
||||
permission_list = [permissions.ItemEditor]
|
||||
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.refresh_from_db()
|
||||
response = Response(
|
||||
return Response(
|
||||
status=c.HTTP_201_CREATED,
|
||||
data={
|
||||
'new_operation': s.OperationSerializer(new_operation).data,
|
||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||
}
|
||||
)
|
||||
return response
|
||||
|
||||
@extend_schema(
|
||||
summary='delete operation',
|
||||
tags=['OSS'],
|
||||
request=s.OperationDeleteSerializer,
|
||||
request=s.OperationTargetSerializer,
|
||||
responses={
|
||||
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
||||
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')
|
||||
def delete_operation(self, request: Request, pk):
|
||||
''' Endpoint: Delete operation. '''
|
||||
serializer = s.OperationDeleteSerializer(
|
||||
serializer = s.OperationTargetSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
)
|
||||
|
@ -156,3 +157,59 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
status=c.HTTP_200_OK,
|
||||
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 'Нет доступа к схеме'
|
||||
|
||||
|
||||
def operationNotInput(title: str):
|
||||
return f'Операция не является Загрузкой: {title}'
|
||||
|
||||
|
||||
def operationResultNotEmpty(title: str):
|
||||
return f'Результат операции не пуст: {title}'
|
||||
|
||||
|
||||
def renameTrivial(name: str):
|
||||
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-toastify": "^10.0.5",
|
||||
"react-tooltip": "^5.27.1",
|
||||
"react-zoom-pan-pinch": "^3.6.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"reagraph": "^4.19.2",
|
||||
"use-debounce": "^10.0.1"
|
||||
|
@ -10542,6 +10543,20 @@
|
|||
"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": {
|
||||
"version": "11.11.4",
|
||||
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"react-tabs": "^6.0.2",
|
||||
"react-toastify": "^10.0.5",
|
||||
"react-tooltip": "^5.27.1",
|
||||
"react-zoom-pan-pinch": "^3.6.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"reagraph": "^4.19.2",
|
||||
"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,
|
||||
IconDarkTheme,
|
||||
IconDatabase,
|
||||
IconDBStructure,
|
||||
IconHelp,
|
||||
IconHelpOff,
|
||||
IconImage,
|
||||
IconLightTheme,
|
||||
IconLogout,
|
||||
IconRESTapi,
|
||||
IconUser
|
||||
} from '@/components/Icons';
|
||||
import { CProps } from '@/components/props';
|
||||
|
@ -43,6 +46,21 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
|||
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() {
|
||||
hideDropdown();
|
||||
toggleDarkMode();
|
||||
|
@ -77,7 +95,34 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
|||
/>
|
||||
) : null}
|
||||
{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}
|
||||
<DropdownButton
|
||||
text='Выйти...'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createBrowserRouter } from 'react-router-dom';
|
||||
|
||||
import CreateItemPage from '@/pages/CreateItemPage';
|
||||
import DatabaseSchemaPage from '@/pages/DatabaseSchemaPage';
|
||||
import HomePage from '@/pages/HomePage';
|
||||
import IconsPage from '@/pages/IconsPage';
|
||||
import LibraryPage from '@/pages/LibraryPage';
|
||||
|
@ -63,13 +64,17 @@ export const Router = createBrowserRouter([
|
|||
path: `${routes.oss}/:id`,
|
||||
element: <OssPage />
|
||||
},
|
||||
{
|
||||
path: routes.manuals,
|
||||
element: <ManualsPage />
|
||||
},
|
||||
{
|
||||
path: `${routes.icons}`,
|
||||
element: <IconsPage />
|
||||
},
|
||||
{
|
||||
path: routes.manuals,
|
||||
element: <ManualsPage />
|
||||
path: `${routes.database_schema}`,
|
||||
element: <DatabaseSchemaPage />
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ export const routes = {
|
|||
help: 'manuals',
|
||||
rsforms: 'rsforms',
|
||||
oss: 'oss',
|
||||
icons: 'icons'
|
||||
icons: 'icons',
|
||||
database_schema: 'database-schema'
|
||||
};
|
||||
|
||||
interface SchemaProps {
|
||||
|
@ -39,10 +40,13 @@ interface OssProps {
|
|||
*/
|
||||
export const urls = {
|
||||
admin: `${buildConstants.backend}/admin`,
|
||||
rest_api: `${buildConstants.backend}/`,
|
||||
home: '/',
|
||||
login: `/${routes.login}`,
|
||||
login_hint: (userName: string) => `/login?username=${userName}`,
|
||||
profile: `/${routes.profile}`,
|
||||
icons: `/${routes.icons}`,
|
||||
database_schema: `/${routes.database_schema}`,
|
||||
signup: `/${routes.signup}`,
|
||||
library: `/${routes.library}`,
|
||||
library_filter: (strategy: string) => `/library?filter=${strategy}`,
|
||||
|
@ -51,7 +55,7 @@ export const urls = {
|
|||
help_topic: (topic: string) => `/manuals?topic=${topic}`,
|
||||
schema: (id: number | string, version?: number | string) =>
|
||||
`/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) => {
|
||||
const versionStr = version !== undefined ? `v=${version}&` : '';
|
||||
const activeStr = active !== undefined ? `&active=${active}` : '';
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
IInputCreatedResponse,
|
||||
IOperationCreateData,
|
||||
IOperationCreatedResponse,
|
||||
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({
|
||||
endpoint: `/api/oss/${schema}/update-positions`,
|
||||
endpoint: `/api/oss/${oss}/update-positions`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postCreateOperation(
|
||||
schema: string,
|
||||
oss: string,
|
||||
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
||||
) {
|
||||
AxiosPost({
|
||||
endpoint: `/api/oss/${schema}/create-operation`,
|
||||
endpoint: `/api/oss/${oss}/create-operation`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function patchDeleteOperation(schema: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||
export function patchDeleteOperation(oss: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu';
|
|||
export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu';
|
||||
export { LuLightbulb as IconHelp } 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 { RiUnpinLine as IconUnpin } from 'react-icons/ri';
|
||||
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 { IoLibrary as IconLibrary2 } from 'react-icons/io5';
|
||||
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 { LuArchive as IconArchive } 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 { TbColumns as IconList } from 'react-icons/tb';
|
||||
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 { LuBookCopy as IconInlineSynthesis } 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 =======
|
||||
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 { LuSparkles as IconClustering } 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 ======
|
||||
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[];
|
||||
value?: IConstituenta;
|
||||
onSelectValue: (newValue?: IConstituenta) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
||||
function SelectConstituenta({
|
||||
|
|
|
@ -13,7 +13,9 @@ interface SelectOperationProps extends CProps.Styling {
|
|||
items?: IOperation[];
|
||||
value?: IOperation;
|
||||
onSelectValue: (newValue?: IOperation) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
||||
function SelectOperation({
|
||||
|
|
|
@ -13,8 +13,10 @@ import SelectSingle from '../ui/SelectSingle';
|
|||
interface SelectUserProps extends CProps.Styling {
|
||||
items?: IUserInfo[];
|
||||
value?: UserID;
|
||||
placeholder?: string;
|
||||
onSelectValue: (newValue: UserID) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
||||
function SelectUser({
|
||||
|
|
|
@ -14,6 +14,9 @@ interface SelectVersionProps extends CProps.Styling {
|
|||
items?: IVersionInfo[];
|
||||
value?: VersionID;
|
||||
onSelectValue: (newValue?: VersionID) => void;
|
||||
|
||||
placeholder?: string;
|
||||
noBorder?: boolean;
|
||||
}
|
||||
|
||||
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
|
||||
|
|
|
@ -13,11 +13,13 @@ import {
|
|||
} from '@/backend/library';
|
||||
import { getRSFormDetails, postRSFormFromFile } from '@/backend/rsforms';
|
||||
import { ErrorData } from '@/components/info/InfoError';
|
||||
import useOssDetails from '@/hooks/useOssDetails';
|
||||
import { FolderTree } from '@/models/FolderTree';
|
||||
import { ILibraryItem, LibraryItemID, LocationHead } from '@/models/library';
|
||||
import { ILibraryCreateData } from '@/models/library';
|
||||
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
@ -34,10 +36,17 @@ interface ILibraryContext {
|
|||
loadingError: ErrorData;
|
||||
setLoadingError: (error: ErrorData) => void;
|
||||
|
||||
globalOSS: IOperationSchema | undefined;
|
||||
setGlobalID: (id: string | undefined) => void;
|
||||
setGlobalOSS: (data: IOperationSchemaData) => void;
|
||||
ossLoading: boolean;
|
||||
ossError: ErrorData;
|
||||
|
||||
processing: boolean;
|
||||
processingError: ErrorData;
|
||||
setProcessingError: (error: ErrorData) => void;
|
||||
|
||||
reloadOSS: (callback?: () => void) => void;
|
||||
reloadItems: (callback?: () => void) => void;
|
||||
|
||||
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
||||
|
@ -75,6 +84,22 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||
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 result = new FolderTree();
|
||||
result.addPath(LocationHead.USER, 0);
|
||||
|
@ -255,11 +280,17 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
1
|
||||
);
|
||||
}
|
||||
if (globalOSS?.schemas.includes(target)) {
|
||||
reloadOSS(() => {
|
||||
if (callback) callback();
|
||||
});
|
||||
} else {
|
||||
if (callback) callback();
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
[reloadItems, user]
|
||||
[reloadItems, reloadOSS, user, globalOSS]
|
||||
);
|
||||
|
||||
const cloneItem = useCallback(
|
||||
|
@ -300,6 +331,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
processingError,
|
||||
setProcessingError,
|
||||
|
||||
globalOSS,
|
||||
setGlobalID,
|
||||
setGlobalOSS,
|
||||
ossLoading,
|
||||
ossError,
|
||||
reloadOSS,
|
||||
|
||||
reloadItems,
|
||||
|
||||
applyFilter,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'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 {
|
||||
|
@ -12,12 +12,18 @@ import {
|
|||
patchSetOwner,
|
||||
postSubscribe
|
||||
} 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 useOssDetails from '@/hooks/useOssDetails';
|
||||
import { AccessPolicy, ILibraryItem } 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 { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
|
@ -48,6 +54,7 @@ interface IOssContext {
|
|||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
||||
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
||||
}
|
||||
|
||||
const OssContext = createContext<IOssContext | null>(null);
|
||||
|
@ -66,13 +73,8 @@ interface OssStateProps {
|
|||
|
||||
export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||
const library = useLibrary();
|
||||
const schema = library.globalOSS;
|
||||
const { user } = useAuth();
|
||||
const {
|
||||
schema, // prettier: split lines
|
||||
error: errorLoading,
|
||||
setSchema,
|
||||
loading
|
||||
} = useOssDetails({ target: itemID });
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||
|
||||
|
@ -90,6 +92,12 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [user, schema, toggleTracking]);
|
||||
|
||||
useEffect(() => {
|
||||
if (schema?.id !== Number(itemID)) {
|
||||
library.setGlobalID(itemID);
|
||||
}
|
||||
}, [itemID, schema, library]);
|
||||
|
||||
const update = useCallback(
|
||||
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
||||
if (!schema) {
|
||||
|
@ -102,13 +110,14 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
setSchema(Object.assign(schema, newData));
|
||||
const fullData: IOperationSchemaData = Object.assign(schema, newData);
|
||||
library.setGlobalOSS(fullData);
|
||||
library.localUpdateItem(newData);
|
||||
if (callback) callback(newData);
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, setSchema, schema, library]
|
||||
[itemID, schema, library]
|
||||
);
|
||||
|
||||
const subscribe = useCallback(
|
||||
|
@ -133,7 +142,7 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, user]
|
||||
[itemID, user, schema]
|
||||
);
|
||||
|
||||
const unsubscribe = useCallback(
|
||||
|
@ -278,13 +287,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
setSchema(newData.oss);
|
||||
library.setGlobalOSS(newData.oss);
|
||||
library.localUpdateTimestamp(newData.oss.id);
|
||||
if (callback) callback(newData.new_operation);
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library, setSchema]
|
||||
[itemID, library]
|
||||
);
|
||||
|
||||
const deleteOperation = useCallback(
|
||||
|
@ -296,13 +305,32 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
library.setGlobalOSS(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
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 (
|
||||
|
@ -310,8 +338,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
value={{
|
||||
schema,
|
||||
itemID,
|
||||
loading,
|
||||
errorLoading,
|
||||
loading: library.ossLoading,
|
||||
errorLoading: library.ossError,
|
||||
processing,
|
||||
processingError,
|
||||
isOwned,
|
||||
|
@ -327,7 +355,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
|
||||
savePositions,
|
||||
createOperation,
|
||||
deleteOperation
|
||||
deleteOperation,
|
||||
createInput
|
||||
}}
|
||||
>
|
||||
{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 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 { IOperationSchema, OperationID } from '@/models/oss';
|
||||
import { limits, patterns } from '@/utils/constants';
|
||||
|
||||
import PickMultiOperation from '../../components/select/PickMultiOperation';
|
||||
|
||||
interface TabSynthesisOperationProps {
|
||||
oss: IOperationSchema;
|
||||
alias: string;
|
||||
|
@ -34,22 +31,6 @@ function TabSynthesisOperation({
|
|||
inputs,
|
||||
setInputs
|
||||
}: 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 (
|
||||
<AnimateFade className='cc-column'>
|
||||
<TextInput
|
||||
|
@ -79,16 +60,10 @@ function TabSynthesisOperation({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between'>
|
||||
<FlexColumn>
|
||||
<Label text='Аргумент 1' />
|
||||
<SelectOperation items={oss.items} value={left} onSelectValue={setLeft} />
|
||||
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
|
||||
<PickMultiOperation items={oss.items} selected={inputs} setSelected={setInputs} rows={6} />
|
||||
</FlexColumn>
|
||||
<FlexColumn>
|
||||
<Label text='Аргумент 2' className='text-right' />
|
||||
<SelectOperation items={oss.items} value={right} onSelectValue={setRight} />
|
||||
</FlexColumn>
|
||||
</div>
|
||||
</AnimateFade>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
@import 'styling/setup.css';
|
||||
@import 'styling/styles.css';
|
||||
@import 'styling/imports.css';
|
||||
@import 'styling/overrides.css';
|
||||
@import 'styling/styles.css';
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface OssNodeInternal {
|
|||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
dragging: boolean;
|
||||
xPos: number;
|
||||
yPos: number;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { Graph } from './Graph';
|
||||
import { ILibraryItemData, LibraryItemID } from './library';
|
||||
import { ILibraryItem, ILibraryItemData, LibraryItemID } from './library';
|
||||
import { ConstituentaID } from './rsform';
|
||||
|
||||
/**
|
||||
|
@ -139,3 +139,11 @@ export interface IOperationCreatedResponse {
|
|||
new_operation: IOperation;
|
||||
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';
|
||||
|
||||
export function IconsPage() {
|
||||
const iconsList = Object.keys(icons).filter(key => key.startsWith('Icon'));
|
||||
return (
|
||||
<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'>
|
||||
{Object.keys(icons)
|
||||
.filter(key => key.startsWith('Icon'))
|
||||
.map((key, index) => (
|
||||
{iconsList.map((key, index) => (
|
||||
<div key={`icons_list_${index}`} className='flex flex-col items-center px-3 pb-6'>
|
||||
<p>{icons[key]({ size: '2rem' })}</p>
|
||||
<p>{key}</p>
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { storage } from '@/utils/constants';
|
||||
|
||||
import OssFlow from './OssFlow';
|
||||
|
||||
interface EditorOssGraphProps {
|
||||
|
@ -13,11 +10,9 @@ interface EditorOssGraphProps {
|
|||
}
|
||||
|
||||
function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) {
|
||||
const [showGrid, setShowGrid] = useLocalStorage<boolean>(storage.ossShowGrid, false);
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<OssFlow isModified={isModified} setIsModified={setIsModified} showGrid={showGrid} setShowGrid={setShowGrid} />
|
||||
<OssFlow isModified={isModified} setIsModified={setIsModified} />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ function InputNode(node: OssNodeInternal) {
|
|||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||
noHover
|
||||
title='Связанная КС'
|
||||
hideTitle={!controller.showTooltip}
|
||||
onClick={() => {
|
||||
handleOpenSchema();
|
||||
}}
|
||||
|
@ -34,7 +35,7 @@ function InputNode(node: OssNodeInternal) {
|
|||
</Overlay>
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||
{node.data.label}
|
||||
{controller.showTooltip ? (
|
||||
{controller.showTooltip && !node.dragging ? (
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
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 DropdownButton from '@/components/ui/DropdownButton';
|
||||
import useClickedOutside from '@/hooks/useClickedOutside';
|
||||
|
@ -22,9 +22,10 @@ export interface ContextMenuData {
|
|||
interface NodeContextMenuProps extends ContextMenuData {
|
||||
onHide: () => 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 [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
@ -57,11 +58,21 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node
|
|||
onDelete(operation.id);
|
||||
};
|
||||
|
||||
const handleCreateSchema = () => {
|
||||
handleHide();
|
||||
onCreateInput(operation.id);
|
||||
};
|
||||
|
||||
const handleRunSynthesis = () => {
|
||||
toast.error('Not implemented');
|
||||
handleHide();
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
||||
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
||||
<DropdownButton
|
||||
text='Свойства операции'
|
||||
text='Редактировать'
|
||||
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')}
|
||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||
disabled={controller.isProcessing}
|
||||
|
@ -83,16 +94,25 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node
|
|||
title='Создать пустую схему для загрузки'
|
||||
icon={<IconNewItem size='1rem' className='icon-green' />}
|
||||
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}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable && operation.operation_type === OperationType.INPUT ? (
|
||||
{controller.isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? (
|
||||
<DropdownButton
|
||||
text='Привязать схему'
|
||||
title='Выбрать схему для загрузки'
|
||||
icon={<IconRSForm size='1rem' className='icon-primary' />}
|
||||
text='Выполнить синтез'
|
||||
title='Выполнить операцию и получить синтезированную КС'
|
||||
icon={<IconExecute size='1rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleEditSchema}
|
||||
onClick={handleRunSynthesis}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ function OperationNode(node: OssNodeInternal) {
|
|||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||
noHover
|
||||
title='Связанная КС'
|
||||
hideTitle={!controller.showTooltip}
|
||||
onClick={() => {
|
||||
handleOpenSchema();
|
||||
}}
|
||||
|
@ -36,7 +37,9 @@ function OperationNode(node: OssNodeInternal) {
|
|||
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||
{node.data.label}
|
||||
{controller.showTooltip && !node.dragging ? (
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
Node,
|
||||
NodeChange,
|
||||
NodeTypes,
|
||||
ProOptions,
|
||||
ReactFlow,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
|
@ -23,9 +22,10 @@ import Overlay from '@/components/ui/Overlay';
|
|||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { OssNode } from '@/models/miscellaneous';
|
||||
import { OperationID } from '@/models/oss';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { PARAMETER, storage } from '@/utils/constants';
|
||||
import { errors } from '@/utils/labels';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
|
@ -37,16 +37,18 @@ import ToolbarOssGraph from './ToolbarOssGraph';
|
|||
interface OssFlowProps {
|
||||
isModified: 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 model = useOSS();
|
||||
const controller = useOssEdit();
|
||||
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 [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
@ -81,6 +83,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
id: String(index),
|
||||
source: String(argument.argument),
|
||||
target: String(argument.operation),
|
||||
type: edgeStraight ? 'straight' : 'simplebezier',
|
||||
animated: edgeAnimate,
|
||||
targetHandle:
|
||||
model.schema!.operationByID.get(argument.argument)!.position_x >
|
||||
model.schema!.operationByID.get(argument.operation)!.position_x
|
||||
|
@ -92,7 +96,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
setTimeout(() => {
|
||||
setIsModified(false);
|
||||
}, PARAMETER.graphRefreshDelay);
|
||||
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset]);
|
||||
}, [model.schema, setNodes, setEdges, setIsModified, toggleReset, edgeStraight, edgeAnimate]);
|
||||
|
||||
const getPositions = useCallback(
|
||||
() =>
|
||||
|
@ -137,6 +141,13 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
[controller, getPositions]
|
||||
);
|
||||
|
||||
const handleCreateInput = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.createInput(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
|
||||
const handleFitView = useCallback(() => {
|
||||
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||
}, [flow]);
|
||||
|
@ -180,7 +191,6 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
|
||||
const handleContextMenu = useCallback(
|
||||
(event: CProps.EventMouse, node: OssNode) => {
|
||||
console.log(node);
|
||||
event.preventDefault();
|
||||
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 canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||
|
||||
|
@ -244,7 +253,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
fitView
|
||||
proOptions={proOptions}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
nodeTypes={OssNodeTypes}
|
||||
maxZoom={2}
|
||||
minZoom={0.75}
|
||||
|
@ -257,17 +266,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
{showGrid ? <Background gap={10} /> : null}
|
||||
</ReactFlow>
|
||||
),
|
||||
[
|
||||
nodes,
|
||||
edges,
|
||||
proOptions,
|
||||
handleNodesChange,
|
||||
handleContextMenu,
|
||||
handleClickCanvas,
|
||||
onEdgesChange,
|
||||
OssNodeTypes,
|
||||
showGrid
|
||||
]
|
||||
[nodes, edges, handleNodesChange, handleContextMenu, handleClickCanvas, onEdgesChange, OssNodeTypes, showGrid]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -276,6 +275,8 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
<ToolbarOssGraph
|
||||
isModified={isModified}
|
||||
showGrid={showGrid}
|
||||
edgeAnimate={edgeAnimate}
|
||||
edgeStraight={edgeStraight}
|
||||
onFitView={handleFitView}
|
||||
onCreate={handleCreateOperation}
|
||||
onDelete={handleDeleteSelected}
|
||||
|
@ -283,10 +284,17 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
onSavePositions={handleSavePositions}
|
||||
onSaveImage={handleSaveImage}
|
||||
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
||||
toggleEdgeAnimate={() => setEdgeAnimate(prev => !prev)}
|
||||
toggleEdgeStraight={() => setEdgeStraight(prev => !prev)}
|
||||
/>
|
||||
</Overlay>
|
||||
{menuProps ? (
|
||||
<NodeContextMenu onHide={handleContextMenuHide} onDelete={handleDeleteOperation} {...menuProps} />
|
||||
<NodeContextMenu
|
||||
onHide={handleContextMenuHide}
|
||||
onDelete={handleDeleteOperation}
|
||||
onCreateInput={handleCreateInput}
|
||||
{...menuProps}
|
||||
/>
|
||||
) : null}
|
||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||
{graph}
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
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 MiniButton from '@/components/ui/MiniButton';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
@ -12,6 +24,8 @@ import { useOssEdit } from '../OssEditContext';
|
|||
interface ToolbarOssGraphProps {
|
||||
isModified: boolean;
|
||||
showGrid: boolean;
|
||||
edgeAnimate: boolean;
|
||||
edgeStraight: boolean;
|
||||
onCreate: () => void;
|
||||
onDelete: () => void;
|
||||
onFitView: () => void;
|
||||
|
@ -19,39 +33,30 @@ interface ToolbarOssGraphProps {
|
|||
onSavePositions: () => void;
|
||||
onResetPositions: () => void;
|
||||
toggleShowGrid: () => void;
|
||||
toggleEdgeAnimate: () => void;
|
||||
toggleEdgeStraight: () => void;
|
||||
}
|
||||
|
||||
function ToolbarOssGraph({
|
||||
isModified,
|
||||
showGrid,
|
||||
edgeAnimate,
|
||||
edgeStraight,
|
||||
onCreate,
|
||||
onDelete,
|
||||
onFitView,
|
||||
onSaveImage,
|
||||
onSavePositions,
|
||||
onResetPositions,
|
||||
toggleShowGrid
|
||||
toggleShowGrid,
|
||||
toggleEdgeAnimate,
|
||||
toggleEdgeStraight
|
||||
}: ToolbarOssGraphProps) {
|
||||
const controller = useOssEdit();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center'>
|
||||
<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
|
||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||
title='Сбросить вид'
|
||||
|
@ -68,22 +73,28 @@ function ToolbarOssGraph({
|
|||
}
|
||||
onClick={toggleShowGrid}
|
||||
/>
|
||||
{controller.isMutable ? (
|
||||
<MiniButton
|
||||
title='Новая операция'
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={onCreate}
|
||||
title={edgeStraight ? 'Связи: прямые' : 'Связи: безье'}
|
||||
icon={
|
||||
edgeStraight ? (
|
||||
<IconLineStraight size='1.25rem' className='icon-primary' />
|
||||
) : (
|
||||
<IconLineWave size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleEdgeStraight}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable ? (
|
||||
<MiniButton
|
||||
title='Удалить выбранную'
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
||||
onClick={onDelete}
|
||||
title={edgeAnimate ? 'Анимация: вкл' : 'Анимация: выкл'}
|
||||
icon={
|
||||
edgeAnimate ? (
|
||||
<IconAnimation size='1.25rem' className='icon-primary' />
|
||||
) : (
|
||||
<IconAnimationOff size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleEdgeAnimate}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
||||
title='Сохранить изображение'
|
||||
|
@ -95,6 +106,36 @@ function ToolbarOssGraph({
|
|||
offset={4}
|
||||
/>
|
||||
</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;
|
||||
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
}
|
||||
|
||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||
|
@ -210,6 +211,16 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
[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 (
|
||||
<OssEditContext.Provider
|
||||
value={{
|
||||
|
@ -234,7 +245,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
openOperationSchema,
|
||||
savePositions,
|
||||
promptCreateOperation,
|
||||
deleteOperation
|
||||
deleteOperation,
|
||||
createInput
|
||||
}}
|
||||
>
|
||||
{model.schema ? (
|
||||
|
|
|
@ -33,7 +33,7 @@ export enum OssTabID {
|
|||
function OssTabs() {
|
||||
const router = useConceptNavigation();
|
||||
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 { schema, loading, errorLoading } = useOSS();
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
IconLibrary,
|
||||
IconMenu,
|
||||
IconNewItem,
|
||||
IconOSS,
|
||||
IconOwner,
|
||||
IconReader,
|
||||
IconReplace,
|
||||
|
@ -29,6 +30,7 @@ import Dropdown from '@/components/ui/Dropdown';
|
|||
import DropdownButton from '@/components/ui/DropdownButton';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
|
@ -36,6 +38,7 @@ import { AccessPolicy } from '@/models/library';
|
|||
import { UserLevel } from '@/models/user';
|
||||
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
|
||||
|
||||
import { OssTabID } from '../OssPage/OssTabs';
|
||||
import { useRSEdit } from './RSEditContext';
|
||||
|
||||
interface MenuRSTabsProps {
|
||||
|
@ -47,6 +50,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
|||
const router = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
const model = useRSForm();
|
||||
const library = useLibrary();
|
||||
|
||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||
|
||||
|
@ -181,6 +185,13 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
|||
onClick={handleCreateNew}
|
||||
/>
|
||||
) : null}
|
||||
{library.globalOSS ? (
|
||||
<DropdownButton
|
||||
text='Перейти к ОСС'
|
||||
icon={<IconOSS size='1rem' className='icon-primary' />}
|
||||
onClick={() => router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH))}
|
||||
/>
|
||||
) : null}
|
||||
<DropdownButton
|
||||
text='Библиотека'
|
||||
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 { information, labelVersion, prompts } from '@/utils/labels';
|
||||
|
||||
import { OssTabID } from '../OssPage/OssTabs';
|
||||
import EditorConstituenta from './EditorConstituenta';
|
||||
import EditorRSForm from './EditorRSFormCard';
|
||||
import EditorRSList from './EditorRSList';
|
||||
|
@ -39,13 +40,13 @@ export enum RSTabID {
|
|||
function RSTabs() {
|
||||
const router = useConceptNavigation();
|
||||
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 cstQuery = query.get('active');
|
||||
|
||||
const { setNoFooter, calculateHeight } = useConceptOptions();
|
||||
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
||||
const { destroyItem } = useLibrary();
|
||||
const library = useLibrary();
|
||||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
useBlockNavigation(isModified);
|
||||
|
@ -176,11 +177,16 @@ function RSTabs() {
|
|||
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
||||
return;
|
||||
}
|
||||
destroyItem(schema.id, () => {
|
||||
const backToOSS = library.globalOSS?.schemas.includes(schema.id);
|
||||
library.destroyItem(schema.id, () => {
|
||||
toast.success(information.itemDestroyed);
|
||||
if (backToOSS) {
|
||||
router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH));
|
||||
} else {
|
||||
router.push(urls.library);
|
||||
}
|
||||
});
|
||||
}, [schema, destroyItem, router]);
|
||||
}, [schema, library, router]);
|
||||
|
||||
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/**
|
||||
* Module: Override imported components CSS styling.
|
||||
*/
|
||||
@import './constants.css';
|
||||
@import './imports.css';
|
||||
|
||||
:root {
|
||||
/* Import overrides */
|
||||
|
|
|
@ -54,7 +54,8 @@ export const patterns = {
|
|||
export const resources = {
|
||||
graph_font: '/DejaVu.ttf',
|
||||
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',
|
||||
|
||||
ossShowGrid: 'oss.show_grid',
|
||||
ossEdgeStraight: 'oss.edge_straight',
|
||||
ossEdgeAnimate: 'oss.edge_animate',
|
||||
|
||||
cstFilterMatch: 'cst.filter.match',
|
||||
cstFilterGraph: 'cst.filter.graph'
|
||||
|
|
Loading…
Reference in New Issue
Block a user