From 54ca6a5279bb350ed2c20d3eea502fccb4e46f90 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 28 Jul 2024 00:37:33 +0300 Subject: [PATCH] F: Implement RSForm to Operation dependency --- .../backend/apps/oss/serializers/__init__.py | 6 +- .../apps/oss/serializers/data_access.py | 2 +- .../backend/apps/oss/serializers/responses.py | 8 +++ .../backend/apps/oss/tests/s_views/t_oss.py | 56 ++++++++++----- rsconcept/backend/apps/oss/views/oss.py | 69 +++++++++++++++++-- rsconcept/backend/shared/messages.py | 8 +++ rsconcept/frontend/src/app/urls.ts | 2 +- rsconcept/frontend/src/backend/oss.ts | 20 ++++-- .../frontend/src/context/LibraryContext.tsx | 22 +++++- rsconcept/frontend/src/context/OssContext.tsx | 25 ++++++- rsconcept/frontend/src/models/oss.ts | 10 ++- .../OssPage/EditorOssGraph/InputNode.tsx | 1 + .../EditorOssGraph/NodeContextMenu.tsx | 10 ++- .../OssPage/EditorOssGraph/OperationNode.tsx | 1 + .../pages/OssPage/EditorOssGraph/OssFlow.tsx | 15 +++- .../src/pages/OssPage/OssEditContext.tsx | 14 +++- .../frontend/src/pages/OssPage/OssTabs.tsx | 2 +- .../src/pages/RSFormPage/MenuRSTabs.tsx | 11 +++ .../frontend/src/pages/RSFormPage/RSTabs.tsx | 14 ++-- 19 files changed, 244 insertions(+), 52 deletions(-) diff --git a/rsconcept/backend/apps/oss/serializers/__init__.py b/rsconcept/backend/apps/oss/serializers/__init__.py index a1e9c339..e32b2334 100644 --- a/rsconcept/backend/apps/oss/serializers/__init__.py +++ b/rsconcept/backend/apps/oss/serializers/__init__.py @@ -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 diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 37ca8f11..8d66c11a 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -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( diff --git a/rsconcept/backend/apps/oss/serializers/responses.py b/rsconcept/backend/apps/oss/serializers/responses.py index c0cf5b31..4fd0be31 100644 --- a/rsconcept/backend/apps/oss/serializers/responses.py +++ b/rsconcept/backend/apps/oss/serializers/responses.py @@ -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() diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py index 8fca5af2..81bb564b 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -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) diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index 27b271fc..43710069 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -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 + } + ) diff --git a/rsconcept/backend/shared/messages.py b/rsconcept/backend/shared/messages.py index 4ef915bd..3ace8a71 100644 --- a/rsconcept/backend/shared/messages.py +++ b/rsconcept/backend/shared/messages.py @@ -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}' diff --git a/rsconcept/frontend/src/app/urls.ts b/rsconcept/frontend/src/app/urls.ts index a0e74527..c7fc284d 100644 --- a/rsconcept/frontend/src/app/urls.ts +++ b/rsconcept/frontend/src/app/urls.ts @@ -52,7 +52,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}` : ''; diff --git a/rsconcept/frontend/src/backend/oss.ts b/rsconcept/frontend/src/backend/oss.ts index 8b319958..f92cae12 100644 --- a/rsconcept/frontend/src/backend/oss.ts +++ b/rsconcept/frontend/src/backend/oss.ts @@ -3,6 +3,7 @@ */ import { + IInputCreatedResponse, IOperationCreateData, IOperationCreatedResponse, IOperationSchemaData, @@ -19,26 +20,33 @@ export function getOssDetails(target: string, request: FrontPull) { +export function patchUpdatePositions(oss: string, request: FrontPush) { AxiosPatch({ - endpoint: `/api/oss/${schema}/update-positions`, + endpoint: `/api/oss/${oss}/update-positions`, request: request }); } export function postCreateOperation( - schema: string, + oss: string, request: FrontExchange ) { AxiosPost({ - endpoint: `/api/oss/${schema}/create-operation`, + endpoint: `/api/oss/${oss}/create-operation`, request: request }); } -export function patchDeleteOperation(schema: string, request: FrontExchange) { +export function patchDeleteOperation(oss: string, request: FrontExchange) { AxiosPatch({ - endpoint: `/api/oss/${schema}/delete-operation`, + endpoint: `/api/oss/${oss}/delete-operation`, + request: request + }); +} + +export function patchCreateInput(oss: string, request: FrontExchange) { + AxiosPatch({ + endpoint: `/api/oss/${oss}/create-input`, request: request }); } diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx index da669f51..360cb8a2 100644 --- a/rsconcept/frontend/src/context/LibraryContext.tsx +++ b/rsconcept/frontend/src/context/LibraryContext.tsx @@ -46,6 +46,7 @@ interface ILibraryContext { processingError: ErrorData; setProcessingError: (error: ErrorData) => void; + reloadOSS: (callback?: () => void) => void; reloadItems: (callback?: () => void) => void; applyFilter: (params: ILibraryFilter) => ILibraryItem[]; @@ -88,9 +89,17 @@ export const LibraryState = ({ children }: LibraryStateProps) => { schema: globalOSS, // prettier: split lines error: ossError, setSchema: setGlobalOSS, - loading: ossLoading + 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); @@ -271,11 +280,17 @@ export const LibraryState = ({ children }: LibraryStateProps) => { 1 ); } - if (callback) callback(); + if (globalOSS?.schemas.includes(target)) { + reloadOSS(() => { + if (callback) callback(); + }); + } else { + if (callback) callback(); + } }) }); }, - [reloadItems, user] + [reloadItems, reloadOSS, user, globalOSS] ); const cloneItem = useCallback( @@ -321,6 +336,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setGlobalOSS, ossLoading, ossError, + reloadOSS, reloadItems, diff --git a/rsconcept/frontend/src/context/OssContext.tsx b/rsconcept/frontend/src/context/OssContext.tsx index 69903708..c21ed575 100644 --- a/rsconcept/frontend/src/context/OssContext.tsx +++ b/rsconcept/frontend/src/context/OssContext.tsx @@ -12,7 +12,7 @@ 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 { AccessPolicy, ILibraryItem } from '@/models/library'; import { ILibraryUpdateData } from '@/models/library'; @@ -54,6 +54,7 @@ interface IOssContext { savePositions: (data: IPositionsData, callback?: () => void) => void; createOperation: (data: IOperationCreateData, callback?: DataCallback) => void; deleteOperation: (data: ITargetOperation, callback?: () => void) => void; + createInput: (data: ITargetOperation, callback?: DataCallback) => void; } const OssContext = createContext(null); @@ -313,6 +314,25 @@ export const OssState = ({ itemID, children }: OssStateProps) => { [itemID, library] ); + const createInput = useCallback( + (data: ITargetOperation, callback?: DataCallback) => { + 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 ( { savePositions, createOperation, - deleteOperation + deleteOperation, + createInput }} > {children} diff --git a/rsconcept/frontend/src/models/oss.ts b/rsconcept/frontend/src/models/oss.ts index 7d921256..a00edaa2 100644 --- a/rsconcept/frontend/src/models/oss.ts +++ b/rsconcept/frontend/src/models/oss.ts @@ -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; +} diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx index 6a6bfad7..33fb9c1c 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx @@ -26,6 +26,7 @@ function InputNode(node: OssNodeInternal) { icon={} noHover title='Связанная КС' + hideTitle={!controller.showTooltip} onClick={() => { handleOpenSchema(); }} diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx index deb55615..91c93f92 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx @@ -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,6 +58,11 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node onDelete(operation.id); }; + const handleCreateSchema = () => { + handleHide(); + onCreateInput(operation.id); + }; + return (
= window.innerWidth - PARAMETER.ossContextMenuWidth}> @@ -83,7 +89,7 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node title='Создать пустую схему для загрузки' icon={} disabled={controller.isProcessing} - onClick={handleEditSchema} + onClick={handleCreateSchema} /> ) : null} {controller.isMutable && operation.operation_type === OperationType.INPUT ? ( diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx index dbe1a076..642ecf3c 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx @@ -27,6 +27,7 @@ function OperationNode(node: OssNodeInternal) { icon={} noHover title='Связанная КС' + hideTitle={!controller.showTooltip} onClick={() => { handleOpenSchema(); }} diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx index 0b18fd12..d2571b5d 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx @@ -141,6 +141,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { [controller, getPositions] ); + const handleCreateInput = useCallback( + (target: OperationID) => { + controller.createInput(target, getPositions()); + }, + [controller, getPositions] + ); + const handleFitView = useCallback(() => { flow.fitView({ duration: PARAMETER.zoomDuration }); }, [flow]); @@ -184,7 +191,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { const handleContextMenu = useCallback( (event: CProps.EventMouse, node: OssNode) => { - console.log(node); event.preventDefault(); event.stopPropagation(); @@ -283,7 +289,12 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { /> {menuProps ? ( - + ) : null}
{graph} diff --git a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx index c78e9732..a36e8270 100644 --- a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx +++ b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx @@ -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(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 ( {model.schema ? ( diff --git a/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx b/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx index e3ecb535..5aa06d42 100644 --- a/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx +++ b/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx @@ -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 = (Number(query.get('tab')) ?? OssTabID.GRAPH) as OssTabID; const { calculateHeight } = useConceptOptions(); const { schema, loading, errorLoading } = useOSS(); diff --git a/rsconcept/frontend/src/pages/RSFormPage/MenuRSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/MenuRSTabs.tsx index 1c349e62..a8f19d31 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/MenuRSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/MenuRSTabs.tsx @@ -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 ? ( + } + onClick={() => router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH))} + /> + ) : null} } diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index 0a04c0cb..76e6768f 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -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'; @@ -45,7 +46,7 @@ function RSTabs() { 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); - router.push(urls.library); + 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]);