F: Implement RSForm to Operation dependency
This commit is contained in:
parent
4899860a05
commit
54ca6a5279
|
@ -4,8 +4,8 @@ from .basics import OperationPositionSerializer, PositionsSerializer, Substituti
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
ArgumentSerializer,
|
ArgumentSerializer,
|
||||||
OperationCreateSerializer,
|
OperationCreateSerializer,
|
||||||
OperationDeleteSerializer,
|
|
||||||
OperationSchemaSerializer,
|
OperationSchemaSerializer,
|
||||||
OperationSerializer
|
OperationSerializer,
|
||||||
|
OperationTargetSerializer
|
||||||
)
|
)
|
||||||
from .responses import NewOperationResponse
|
from .responses import NewOperationResponse, NewSchemaResponse
|
||||||
|
|
|
@ -53,7 +53,7 @@ class OperationCreateSerializer(serializers.Serializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OperationDeleteSerializer(serializers.Serializer):
|
class OperationTargetSerializer(serializers.Serializer):
|
||||||
''' Serializer: Delete operation. '''
|
''' Serializer: Delete operation. '''
|
||||||
target = PKField(many=False, queryset=Operation.objects.all())
|
target = PKField(many=False, queryset=Operation.objects.all())
|
||||||
positions = serializers.ListField(
|
positions = serializers.ListField(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from apps.library.serializers import LibraryItemSerializer
|
||||||
|
|
||||||
from .data_access import OperationSchemaSerializer, OperationSerializer
|
from .data_access import OperationSchemaSerializer, OperationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,3 +10,9 @@ class NewOperationResponse(serializers.Serializer):
|
||||||
''' Serializer: Create operation response. '''
|
''' Serializer: Create operation response. '''
|
||||||
new_operation = OperationSerializer()
|
new_operation = OperationSerializer()
|
||||||
oss = OperationSchemaSerializer()
|
oss = OperationSchemaSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
class NewSchemaResponse(serializers.Serializer):
|
||||||
|
''' Serializer: Create RSForm for input operation response. '''
|
||||||
|
new_schema = LibraryItemSerializer()
|
||||||
|
oss = OperationSchemaSerializer()
|
||||||
|
|
|
@ -127,8 +127,6 @@ class TestOssViewset(EndpointTester):
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||||
def test_create_operation(self):
|
def test_create_operation(self):
|
||||||
|
|
||||||
|
|
||||||
self.populateData()
|
self.populateData()
|
||||||
self.executeBadData(item=self.owned_id)
|
self.executeBadData(item=self.owned_id)
|
||||||
|
|
||||||
|
@ -231,23 +229,6 @@ class TestOssViewset(EndpointTester):
|
||||||
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||||
self.assertEqual(schema.location, self.owned.model.location)
|
self.assertEqual(schema.location, self.owned.model.location)
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
|
||||||
def test_create_operation_result(self):
|
|
||||||
self.populateData()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'item_data': {
|
|
||||||
'alias': 'Test4',
|
|
||||||
'operation_type': OperationType.INPUT,
|
|
||||||
'result': self.ks1.model.pk
|
|
||||||
},
|
|
||||||
'positions': [],
|
|
||||||
}
|
|
||||||
response = self.executeCreated(data=data, item=self.owned_id)
|
|
||||||
self.owned.refresh_from_db()
|
|
||||||
new_operation = response.data['new_operation']
|
|
||||||
self.assertEqual(new_operation['result'], self.ks1.model.pk)
|
|
||||||
|
|
||||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||||
def test_delete_operation(self):
|
def test_delete_operation(self):
|
||||||
self.executeNotFound(item=self.invalid_id)
|
self.executeNotFound(item=self.invalid_id)
|
||||||
|
@ -269,3 +250,40 @@ class TestOssViewset(EndpointTester):
|
||||||
self.login()
|
self.login()
|
||||||
response = self.executeOK(data=data)
|
response = self.executeOK(data=data)
|
||||||
self.assertEqual(len(response.data['items']), 2)
|
self.assertEqual(len(response.data['items']), 2)
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
|
||||||
|
def test_create_input(self):
|
||||||
|
self.populateData()
|
||||||
|
self.executeBadData(item=self.owned_id)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'positions': []
|
||||||
|
}
|
||||||
|
self.executeBadData(data=data)
|
||||||
|
|
||||||
|
data['target'] = self.operation1.pk
|
||||||
|
self.toggle_admin(True)
|
||||||
|
self.executeBadData(data=data, item=self.unowned_id)
|
||||||
|
self.logout()
|
||||||
|
self.executeForbidden(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.login()
|
||||||
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.operation1.result = None
|
||||||
|
self.operation1.comment = 'TestComment'
|
||||||
|
self.operation1.title = 'TestTitle'
|
||||||
|
self.operation1.sync_text = False
|
||||||
|
self.operation1.save()
|
||||||
|
response = self.executeOK(data=data)
|
||||||
|
self.operation1.refresh_from_db()
|
||||||
|
|
||||||
|
new_schema = response.data['new_schema']
|
||||||
|
self.assertEqual(self.operation1.sync_text, True)
|
||||||
|
self.assertEqual(new_schema['id'], self.operation1.result.pk)
|
||||||
|
self.assertEqual(new_schema['alias'], self.operation1.alias)
|
||||||
|
self.assertEqual(new_schema['title'], self.operation1.title)
|
||||||
|
self.assertEqual(new_schema['comment'], self.operation1.comment)
|
||||||
|
|
||||||
|
data['target'] = self.operation3.pk
|
||||||
|
self.executeBadData(data=data)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import cast
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import generics
|
from rest_framework import generics, serializers
|
||||||
from rest_framework import status as c
|
from rest_framework import status as c
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -12,6 +12,7 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from apps.library.models import LibraryItem, LibraryItemType
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
from apps.library.serializers import LibraryItemSerializer
|
from apps.library.serializers import LibraryItemSerializer
|
||||||
|
from shared import messages as msg
|
||||||
from shared import permissions
|
from shared import permissions
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
@ -33,7 +34,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
if self.action in [
|
if self.action in [
|
||||||
'create_operation',
|
'create_operation',
|
||||||
'delete_operation',
|
'delete_operation',
|
||||||
'update_positions'
|
'update_positions',
|
||||||
|
'create_input'
|
||||||
]:
|
]:
|
||||||
permission_list = [permissions.ItemEditor]
|
permission_list = [permissions.ItemEditor]
|
||||||
elif self.action in ['details']:
|
elif self.action in ['details']:
|
||||||
|
@ -117,19 +119,18 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
oss.add_argument(operation=new_operation, argument=argument)
|
oss.add_argument(operation=new_operation, argument=argument)
|
||||||
|
|
||||||
oss.refresh_from_db()
|
oss.refresh_from_db()
|
||||||
response = Response(
|
return Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data={
|
data={
|
||||||
'new_operation': s.OperationSerializer(new_operation).data,
|
'new_operation': s.OperationSerializer(new_operation).data,
|
||||||
'oss': s.OperationSchemaSerializer(oss.model).data
|
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return response
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='delete operation',
|
summary='delete operation',
|
||||||
tags=['OSS'],
|
tags=['OSS'],
|
||||||
request=s.OperationDeleteSerializer,
|
request=s.OperationTargetSerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
@ -140,7 +141,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||||
def delete_operation(self, request: Request, pk):
|
def delete_operation(self, request: Request, pk):
|
||||||
''' Endpoint: Delete operation. '''
|
''' Endpoint: Delete operation. '''
|
||||||
serializer = s.OperationDeleteSerializer(
|
serializer = s.OperationTargetSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'oss': self.get_object()}
|
context={'oss': self.get_object()}
|
||||||
)
|
)
|
||||||
|
@ -156,3 +157,59 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.OperationSchemaSerializer(oss.model).data
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='create input schema for target operation',
|
||||||
|
tags=['OSS'],
|
||||||
|
request=s.OperationTargetSerializer(),
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: s.NewSchemaResponse,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['patch'], url_path='create-input')
|
||||||
|
def create_input(self, request: Request, pk):
|
||||||
|
''' Create new input RSForm. '''
|
||||||
|
serializer = s.OperationTargetSerializer(
|
||||||
|
data=request.data,
|
||||||
|
context={'oss': self.get_object()}
|
||||||
|
)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||||
|
if operation.operation_type != m.OperationType.INPUT:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'target': msg.operationNotInput(operation.alias)
|
||||||
|
})
|
||||||
|
if operation.result is not None:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'target': msg.operationResultNotEmpty(operation.alias)
|
||||||
|
})
|
||||||
|
|
||||||
|
oss = m.OperationSchema(self.get_object())
|
||||||
|
with transaction.atomic():
|
||||||
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
|
schema = LibraryItem.objects.create(
|
||||||
|
item_type=LibraryItemType.RSFORM,
|
||||||
|
owner=oss.model.owner,
|
||||||
|
alias=operation.alias,
|
||||||
|
title=operation.title,
|
||||||
|
comment=operation.comment,
|
||||||
|
visible=False,
|
||||||
|
access_policy=oss.model.access_policy,
|
||||||
|
location=oss.model.location
|
||||||
|
)
|
||||||
|
operation.result = schema
|
||||||
|
operation.sync_text = True
|
||||||
|
operation.save()
|
||||||
|
|
||||||
|
oss.refresh_from_db()
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data={
|
||||||
|
'new_schema': LibraryItemSerializer(schema).data,
|
||||||
|
'oss': s.OperationSchemaSerializer(oss.model).data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -18,6 +18,14 @@ def schemaNotOwned():
|
||||||
return 'Нет доступа к схеме'
|
return 'Нет доступа к схеме'
|
||||||
|
|
||||||
|
|
||||||
|
def operationNotInput(title: str):
|
||||||
|
return f'Операция не является Загрузкой: {title}'
|
||||||
|
|
||||||
|
|
||||||
|
def operationResultNotEmpty(title: str):
|
||||||
|
return f'Результат операции не пуст: {title}'
|
||||||
|
|
||||||
|
|
||||||
def renameTrivial(name: str):
|
def renameTrivial(name: str):
|
||||||
return f'Имя должно отличаться от текущего: {name}'
|
return f'Имя должно отличаться от текущего: {name}'
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const urls = {
|
||||||
help_topic: (topic: string) => `/manuals?topic=${topic}`,
|
help_topic: (topic: string) => `/manuals?topic=${topic}`,
|
||||||
schema: (id: number | string, version?: number | string) =>
|
schema: (id: number | string, version?: number | string) =>
|
||||||
`/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''),
|
`/rsforms/${id}` + (version !== undefined ? `?v=${version}` : ''),
|
||||||
oss: (id: number | string) => `/oss/${id}`,
|
oss: (id: number | string, tab?: number) => `/oss/${id}` + (tab !== undefined ? `?tab=${tab}` : ''),
|
||||||
schema_props: ({ id, tab, version, active }: SchemaProps) => {
|
schema_props: ({ id, tab, version, active }: SchemaProps) => {
|
||||||
const versionStr = version !== undefined ? `v=${version}&` : '';
|
const versionStr = version !== undefined ? `v=${version}&` : '';
|
||||||
const activeStr = active !== undefined ? `&active=${active}` : '';
|
const activeStr = active !== undefined ? `&active=${active}` : '';
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
IInputCreatedResponse,
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
IOperationCreatedResponse,
|
IOperationCreatedResponse,
|
||||||
IOperationSchemaData,
|
IOperationSchemaData,
|
||||||
|
@ -19,26 +20,33 @@ export function getOssDetails(target: string, request: FrontPull<IOperationSchem
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchUpdatePositions(schema: string, request: FrontPush<IPositionsData>) {
|
export function patchUpdatePositions(oss: string, request: FrontPush<IPositionsData>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
endpoint: `/api/oss/${schema}/update-positions`,
|
endpoint: `/api/oss/${oss}/update-positions`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postCreateOperation(
|
export function postCreateOperation(
|
||||||
schema: string,
|
oss: string,
|
||||||
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
request: FrontExchange<IOperationCreateData, IOperationCreatedResponse>
|
||||||
) {
|
) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/oss/${schema}/create-operation`,
|
endpoint: `/api/oss/${oss}/create-operation`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchDeleteOperation(schema: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
export function patchDeleteOperation(oss: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
endpoint: `/api/oss/${schema}/delete-operation`,
|
endpoint: `/api/oss/${oss}/delete-operation`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchCreateInput(oss: string, request: FrontExchange<ITargetOperation, IInputCreatedResponse>) {
|
||||||
|
AxiosPatch({
|
||||||
|
endpoint: `/api/oss/${oss}/create-input`,
|
||||||
request: request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ interface ILibraryContext {
|
||||||
processingError: ErrorData;
|
processingError: ErrorData;
|
||||||
setProcessingError: (error: ErrorData) => void;
|
setProcessingError: (error: ErrorData) => void;
|
||||||
|
|
||||||
|
reloadOSS: (callback?: () => void) => void;
|
||||||
reloadItems: (callback?: () => void) => void;
|
reloadItems: (callback?: () => void) => void;
|
||||||
|
|
||||||
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
||||||
|
@ -88,9 +89,17 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
schema: globalOSS, // prettier: split lines
|
schema: globalOSS, // prettier: split lines
|
||||||
error: ossError,
|
error: ossError,
|
||||||
setSchema: setGlobalOSS,
|
setSchema: setGlobalOSS,
|
||||||
loading: ossLoading
|
loading: ossLoading,
|
||||||
|
reload: reloadOssInternal
|
||||||
} = useOssDetails({ target: ossID });
|
} = useOssDetails({ target: ossID });
|
||||||
|
|
||||||
|
const reloadOSS = useCallback(
|
||||||
|
(callback?: () => void) => {
|
||||||
|
reloadOssInternal(setProcessing, callback);
|
||||||
|
},
|
||||||
|
[reloadOssInternal]
|
||||||
|
);
|
||||||
|
|
||||||
const folders = useMemo(() => {
|
const folders = useMemo(() => {
|
||||||
const result = new FolderTree();
|
const result = new FolderTree();
|
||||||
result.addPath(LocationHead.USER, 0);
|
result.addPath(LocationHead.USER, 0);
|
||||||
|
@ -271,11 +280,17 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
1
|
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(
|
const cloneItem = useCallback(
|
||||||
|
@ -321,6 +336,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
setGlobalOSS,
|
setGlobalOSS,
|
||||||
ossLoading,
|
ossLoading,
|
||||||
ossError,
|
ossError,
|
||||||
|
reloadOSS,
|
||||||
|
|
||||||
reloadItems,
|
reloadItems,
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
patchSetOwner,
|
patchSetOwner,
|
||||||
postSubscribe
|
postSubscribe
|
||||||
} from '@/backend/library';
|
} from '@/backend/library';
|
||||||
import { patchDeleteOperation, patchUpdatePositions, postCreateOperation } from '@/backend/oss';
|
import { patchCreateInput, patchDeleteOperation, patchUpdatePositions, postCreateOperation } from '@/backend/oss';
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
import { AccessPolicy, ILibraryItem } from '@/models/library';
|
||||||
import { ILibraryUpdateData } from '@/models/library';
|
import { ILibraryUpdateData } from '@/models/library';
|
||||||
|
@ -54,6 +54,7 @@ interface IOssContext {
|
||||||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void;
|
||||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
||||||
|
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssContext = createContext<IOssContext | null>(null);
|
const OssContext = createContext<IOssContext | null>(null);
|
||||||
|
@ -313,6 +314,25 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
[itemID, library]
|
[itemID, library]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createInput = useCallback(
|
||||||
|
(data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => {
|
||||||
|
setProcessingError(undefined);
|
||||||
|
patchCreateInput(itemID, {
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: setProcessingError,
|
||||||
|
onSuccess: newData => {
|
||||||
|
library.setGlobalOSS(newData.oss);
|
||||||
|
library.reloadItems(() => {
|
||||||
|
if (callback) callback(newData.new_schema);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[itemID, library]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OssContext.Provider
|
<OssContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -335,7 +355,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
|
|
||||||
savePositions,
|
savePositions,
|
||||||
createOperation,
|
createOperation,
|
||||||
deleteOperation
|
deleteOperation,
|
||||||
|
createInput
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Graph } from './Graph';
|
import { Graph } from './Graph';
|
||||||
import { ILibraryItemData, LibraryItemID } from './library';
|
import { ILibraryItem, ILibraryItemData, LibraryItemID } from './library';
|
||||||
import { ConstituentaID } from './rsform';
|
import { ConstituentaID } from './rsform';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,3 +139,11 @@ export interface IOperationCreatedResponse {
|
||||||
new_operation: IOperation;
|
new_operation: IOperation;
|
||||||
oss: IOperationSchemaData;
|
oss: IOperationSchemaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data response when creating {@link IRSForm} for Input {@link IOperation}.
|
||||||
|
*/
|
||||||
|
export interface IInputCreatedResponse {
|
||||||
|
new_schema: ILibraryItem;
|
||||||
|
oss: IOperationSchemaData;
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ function InputNode(node: OssNodeInternal) {
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
noHover
|
noHover
|
||||||
title='Связанная КС'
|
title='Связанная КС'
|
||||||
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOpenSchema();
|
handleOpenSchema();
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -22,9 +22,10 @@ export interface ContextMenuData {
|
||||||
interface NodeContextMenuProps extends ContextMenuData {
|
interface NodeContextMenuProps extends ContextMenuData {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
onDelete: (target: OperationID) => void;
|
onDelete: (target: OperationID) => void;
|
||||||
|
onCreateInput: (target: OperationID) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: NodeContextMenuProps) {
|
function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete, onCreateInput }: NodeContextMenuProps) {
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
@ -57,6 +58,11 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node
|
||||||
onDelete(operation.id);
|
onDelete(operation.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCreateSchema = () => {
|
||||||
|
handleHide();
|
||||||
|
onCreateInput(operation.id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
||||||
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
||||||
|
@ -83,7 +89,7 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: Node
|
||||||
title='Создать пустую схему для загрузки'
|
title='Создать пустую схему для загрузки'
|
||||||
icon={<IconNewItem size='1rem' className='icon-green' />}
|
icon={<IconNewItem size='1rem' className='icon-green' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
onClick={handleEditSchema}
|
onClick={handleCreateSchema}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{controller.isMutable && operation.operation_type === OperationType.INPUT ? (
|
{controller.isMutable && operation.operation_type === OperationType.INPUT ? (
|
||||||
|
|
|
@ -27,6 +27,7 @@ function OperationNode(node: OssNodeInternal) {
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
noHover
|
noHover
|
||||||
title='Связанная КС'
|
title='Связанная КС'
|
||||||
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOpenSchema();
|
handleOpenSchema();
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -141,6 +141,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
[controller, getPositions]
|
[controller, getPositions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCreateInput = useCallback(
|
||||||
|
(target: OperationID) => {
|
||||||
|
controller.createInput(target, getPositions());
|
||||||
|
},
|
||||||
|
[controller, getPositions]
|
||||||
|
);
|
||||||
|
|
||||||
const handleFitView = useCallback(() => {
|
const handleFitView = useCallback(() => {
|
||||||
flow.fitView({ duration: PARAMETER.zoomDuration });
|
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||||
}, [flow]);
|
}, [flow]);
|
||||||
|
@ -184,7 +191,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: CProps.EventMouse, node: OssNode) => {
|
(event: CProps.EventMouse, node: OssNode) => {
|
||||||
console.log(node);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -283,7 +289,12 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
{menuProps ? (
|
{menuProps ? (
|
||||||
<NodeContextMenu onHide={handleContextMenuHide} onDelete={handleDeleteOperation} {...menuProps} />
|
<NodeContextMenu
|
||||||
|
onHide={handleContextMenuHide}
|
||||||
|
onDelete={handleDeleteOperation}
|
||||||
|
onCreateInput={handleCreateInput}
|
||||||
|
{...menuProps}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||||
{graph}
|
{graph}
|
||||||
|
|
|
@ -44,6 +44,7 @@ export interface IOssEditContext {
|
||||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||||
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void;
|
||||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
|
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||||
|
@ -210,6 +211,16 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
[model]
|
[model]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createInput = useCallback(
|
||||||
|
(target: OperationID, positions: IOperationPosition[]) => {
|
||||||
|
model.createInput({ target: target, positions: positions }, new_schema => {
|
||||||
|
toast.success(information.newLibraryItem);
|
||||||
|
router.push(urls.schema(new_schema.id));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[model, router]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OssEditContext.Provider
|
<OssEditContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -234,7 +245,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
openOperationSchema,
|
openOperationSchema,
|
||||||
savePositions,
|
savePositions,
|
||||||
promptCreateOperation,
|
promptCreateOperation,
|
||||||
deleteOperation
|
deleteOperation,
|
||||||
|
createInput
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{model.schema ? (
|
{model.schema ? (
|
||||||
|
|
|
@ -33,7 +33,7 @@ export enum OssTabID {
|
||||||
function OssTabs() {
|
function OssTabs() {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const query = useQueryStrings();
|
const query = useQueryStrings();
|
||||||
const activeTab = (Number(query.get('tab')) ?? OssTabID.CARD) as OssTabID;
|
const activeTab = (Number(query.get('tab')) ?? OssTabID.GRAPH) as OssTabID;
|
||||||
|
|
||||||
const { calculateHeight } = useConceptOptions();
|
const { calculateHeight } = useConceptOptions();
|
||||||
const { schema, loading, errorLoading } = useOSS();
|
const { schema, loading, errorLoading } = useOSS();
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
IconLibrary,
|
IconLibrary,
|
||||||
IconMenu,
|
IconMenu,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
IconOSS,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
IconReader,
|
IconReader,
|
||||||
IconReplace,
|
IconReplace,
|
||||||
|
@ -29,6 +30,7 @@ import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
import { useRSForm } from '@/context/RSFormContext';
|
import { useRSForm } from '@/context/RSFormContext';
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
|
@ -36,6 +38,7 @@ import { AccessPolicy } from '@/models/library';
|
||||||
import { UserLevel } from '@/models/user';
|
import { UserLevel } from '@/models/user';
|
||||||
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
|
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { OssTabID } from '../OssPage/OssTabs';
|
||||||
import { useRSEdit } from './RSEditContext';
|
import { useRSEdit } from './RSEditContext';
|
||||||
|
|
||||||
interface MenuRSTabsProps {
|
interface MenuRSTabsProps {
|
||||||
|
@ -47,6 +50,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const model = useRSForm();
|
const model = useRSForm();
|
||||||
|
const library = useLibrary();
|
||||||
|
|
||||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||||
|
|
||||||
|
@ -181,6 +185,13 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
onClick={handleCreateNew}
|
onClick={handleCreateNew}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{library.globalOSS ? (
|
||||||
|
<DropdownButton
|
||||||
|
text='Перейти к ОСС'
|
||||||
|
icon={<IconOSS size='1rem' className='icon-primary' />}
|
||||||
|
onClick={() => router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH))}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Библиотека'
|
text='Библиотека'
|
||||||
icon={<IconLibrary size='1rem' className='icon-primary' />}
|
icon={<IconLibrary size='1rem' className='icon-primary' />}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { ConstituentaID, IConstituenta, IConstituentaMeta } from '@/models/rsfor
|
||||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||||
import { information, labelVersion, prompts } from '@/utils/labels';
|
import { information, labelVersion, prompts } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { OssTabID } from '../OssPage/OssTabs';
|
||||||
import EditorConstituenta from './EditorConstituenta';
|
import EditorConstituenta from './EditorConstituenta';
|
||||||
import EditorRSForm from './EditorRSFormCard';
|
import EditorRSForm from './EditorRSFormCard';
|
||||||
import EditorRSList from './EditorRSList';
|
import EditorRSList from './EditorRSList';
|
||||||
|
@ -45,7 +46,7 @@ function RSTabs() {
|
||||||
|
|
||||||
const { setNoFooter, calculateHeight } = useConceptOptions();
|
const { setNoFooter, calculateHeight } = useConceptOptions();
|
||||||
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
||||||
const { destroyItem } = useLibrary();
|
const library = useLibrary();
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(false);
|
const [isModified, setIsModified] = useState(false);
|
||||||
useBlockNavigation(isModified);
|
useBlockNavigation(isModified);
|
||||||
|
@ -176,11 +177,16 @@ function RSTabs() {
|
||||||
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
destroyItem(schema.id, () => {
|
const backToOSS = library.globalOSS?.schemas.includes(schema.id);
|
||||||
|
library.destroyItem(schema.id, () => {
|
||||||
toast.success(information.itemDestroyed);
|
toast.success(information.itemDestroyed);
|
||||||
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]);
|
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user