F: Implement RSForm to Operation dependency
Some checks failed
Frontend CI / build (22.x) (push) Waiting to run
Backend CI / build (3.12) (push) Has been cancelled

This commit is contained in:
Ivan 2024-07-28 00:37:50 +03:00
parent f254acac7f
commit ec3936cc4c
20 changed files with 267 additions and 59 deletions

View File

@ -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

View File

@ -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(

View File

@ -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()

View File

@ -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)

View File

@ -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
}
)

View File

@ -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}'

View File

@ -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}` : '';

View File

@ -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
});
}

View File

@ -61,7 +61,7 @@ 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';
@ -105,6 +105,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';

View File

@ -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 (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,

View File

@ -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<IOperation>) => void;
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
}
const OssContext = createContext<IOssContext | null>(null);
@ -313,6 +314,25 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
[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 (
<OssContext.Provider
value={{
@ -335,7 +355,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
savePositions,
createOperation,
deleteOperation
deleteOperation,
createInput
}}
>
{children}

View File

@ -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;
}

View File

@ -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();
}}

View File

@ -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}

View File

@ -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();
}}

View File

@ -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) {
/>
</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}

View File

@ -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 ? (

View File

@ -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();

View File

@ -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' />}

View File

@ -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);
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]);