F: Implement input schema change UI

This commit is contained in:
Ivan 2024-07-28 21:29:46 +03:00
parent 01c0eb201e
commit 336b61957b
16 changed files with 352 additions and 33 deletions

View File

@ -6,6 +6,7 @@ from .data_access import (
OperationCreateSerializer, OperationCreateSerializer,
OperationSchemaSerializer, OperationSchemaSerializer,
OperationSerializer, OperationSerializer,
OperationTargetSerializer OperationTargetSerializer,
SetOperationInputSerializer
) )
from .responses import NewOperationResponse, NewSchemaResponse from .responses import NewOperationResponse, NewSchemaResponse

View File

@ -5,7 +5,7 @@ from django.db.models import F
from rest_framework import serializers from rest_framework import serializers
from rest_framework.serializers import PrimaryKeyRelatedField as PKField from rest_framework.serializers import PrimaryKeyRelatedField as PKField
from apps.library.models import LibraryItem from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemDetailsSerializer from apps.library.serializers import LibraryItemDetailsSerializer
from shared import messages as msg from shared import messages as msg
@ -66,9 +66,37 @@ class OperationTargetSerializer(serializers.Serializer):
operation = cast(Operation, attrs['target']) operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss: if oss and operation.oss != oss:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{operation.id}': msg.operationNotOwned(oss.title) 'target': msg.operationNotInOSS(oss.title)
})
return attrs
class SetOperationInputSerializer(serializers.Serializer):
''' Serializer: Set input schema for operation. '''
target = PKField(many=False, queryset=Operation.objects.all())
input = PKField(
many=False,
queryset=LibraryItem.objects.filter(item_type=LibraryItemType.RSFORM),
allow_null=True,
default=None
)
sync_text = serializers.BooleanField(default=False, required=False)
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
)
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss:
raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title)
})
if operation.operation_type != OperationType.INPUT:
raise serializers.ValidationError({
'target': msg.operationNotInput(operation.alias)
}) })
self.instance = operation
return attrs return attrs

View File

@ -2,7 +2,7 @@
from rest_framework import status from rest_framework import status
from apps.library.models import AccessPolicy, LibraryItem, LibraryItemType, LocationHead from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType, LocationHead
from apps.oss.models import Operation, OperationSchema, OperationType from apps.oss.models import Operation, OperationSchema, OperationType
from apps.rsform.models import RSForm from apps.rsform.models import RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -208,6 +208,7 @@ 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_schema(self): def test_create_operation_schema(self):
self.populateData() self.populateData()
Editor.add(self.owned.model, self.user2)
data = { data = {
'item_data': { 'item_data': {
'alias': 'Test4', 'alias': 'Test4',
@ -228,6 +229,7 @@ class TestOssViewset(EndpointTester):
self.assertEqual(schema.visible, False) self.assertEqual(schema.visible, False)
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)
self.assertIn(self.user2, schema.editors())
@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):
@ -287,3 +289,58 @@ class TestOssViewset(EndpointTester):
data['target'] = self.operation3.pk data['target'] = self.operation3.pk
self.executeBadData(data=data) self.executeBadData(data=data)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_null(self):
self.populateData()
self.executeBadData(item=self.owned_id)
data = {
'sync_text': True,
'positions': []
}
self.executeBadData(data=data)
data['target'] = self.operation1.pk
data['input'] = None
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()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.sync_text, True)
self.assertEqual(self.operation1.result, None)
data['input'] = self.ks1.model.pk
self.ks1.model.alias = 'Test42'
self.ks1.model.title = 'Test421'
self.ks1.model.comment = 'TestComment42'
self.ks1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.sync_text, True)
self.assertEqual(self.operation1.result, self.ks1.model)
self.assertEqual(self.operation1.alias, self.ks1.model.alias)
self.assertEqual(self.operation1.title, self.ks1.model.title)
self.assertEqual(self.operation1.comment, self.ks1.model.comment)
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
def test_set_input_change_schema(self):
self.populateData()
self.operation2.result = None
data = {
'sync_text': True,
'positions': [],
'target': self.operation1.pk,
'input': self.ks2.model.pk
}
response = self.executeOK(data=data, item=self.owned_id)
self.operation2.refresh_from_db()
self.assertEqual(self.operation2.sync_text, True)
self.assertEqual(self.operation2.result, self.ks2.model)

View File

@ -10,7 +10,7 @@ from rest_framework.decorators import action
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType from apps.library.models import Editor, LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer from apps.library.serializers import LibraryItemSerializer
from shared import messages as msg from shared import messages as msg
from shared import permissions from shared import permissions
@ -35,7 +35,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'create_operation', 'create_operation',
'delete_operation', 'delete_operation',
'update_positions', 'update_positions',
'create_input' 'create_input',
'set_input'
]: ]:
permission_list = [permissions.ItemEditor] permission_list = [permissions.ItemEditor]
elif self.action in ['details']: elif self.action in ['details']:
@ -112,6 +113,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
access_policy=oss.model.access_policy, access_policy=oss.model.access_policy,
location=oss.model.location location=oss.model.location
) )
Editor.set(schema, oss.model.editors())
data['result'] = schema data['result'] = schema
new_operation = oss.create_operation(**data) new_operation = oss.create_operation(**data)
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data: if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
@ -201,6 +203,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
access_policy=oss.model.access_policy, access_policy=oss.model.access_policy,
location=oss.model.location location=oss.model.location
) )
Editor.set(schema, oss.model.editors())
operation.result = schema operation.result = schema
operation.sync_text = True operation.sync_text = True
operation.save() operation.save()
@ -213,3 +216,44 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'oss': s.OperationSchemaSerializer(oss.model).data 'oss': s.OperationSchemaSerializer(oss.model).data
} }
) )
@extend_schema(
summary='set input schema for target operation',
tags=['OSS'],
request=s.SetOperationInputSerializer(),
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='set-input')
def set_input(self, request: Request, pk):
''' Set input schema for target operation. '''
serializer = s.SetOperationInputSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
result = serializer.validated_data['input']
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
operation.result = result
operation.sync_text = serializer.validated_data['sync_text']
if result is not None and operation.sync_text:
operation.title = result.title
operation.comment = result.comment
operation.alias = result.alias
operation.save()
# update arguments
oss.refresh_from_db()
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)

View File

@ -212,7 +212,7 @@ class CstTargetSerializer(serializers.Serializer):
cst = cast(Constituenta, attrs['target']) cst = cast(Constituenta, attrs['target'])
if schema and cst.schema != schema: if schema and cst.schema != schema:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.id}': msg.constituentaNotOwned(schema.title) f'{cst.id}': msg.constituentaNotInRSform(schema.title)
}) })
if cst.cst_type not in [CstType.FUNCTION, CstType.STRUCTURED, CstType.TERM]: if cst.cst_type not in [CstType.FUNCTION, CstType.STRUCTURED, CstType.TERM]:
raise serializers.ValidationError({ raise serializers.ValidationError({
@ -234,7 +234,7 @@ class CstRenameSerializer(serializers.Serializer):
cst = cast(Constituenta, attrs['target']) cst = cast(Constituenta, attrs['target'])
if cst.schema != schema: if cst.schema != schema:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.id}': msg.constituentaNotOwned(schema.title) f'{cst.id}': msg.constituentaNotInRSform(schema.title)
}) })
new_alias = self.initial_data['alias'] new_alias = self.initial_data['alias']
if cst.alias == new_alias: if cst.alias == new_alias:
@ -260,7 +260,7 @@ class CstListSerializer(serializers.Serializer):
for item in attrs['items']: for item in attrs['items']:
if item.schema != schema: if item.schema != schema:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{item.id}': msg.constituentaNotOwned(schema.title) f'{item.id}': msg.constituentaNotInRSform(schema.title)
}) })
return attrs return attrs
@ -300,11 +300,11 @@ class CstSubstituteSerializer(serializers.Serializer):
}) })
if original_cst.schema != schema: if original_cst.schema != schema:
raise serializers.ValidationError({ raise serializers.ValidationError({
'original': msg.constituentaNotOwned(schema.title) 'original': msg.constituentaNotInRSform(schema.title)
}) })
if substitution_cst.schema != schema: if substitution_cst.schema != schema:
raise serializers.ValidationError({ raise serializers.ValidationError({
'substitution': msg.constituentaNotOwned(schema.title) 'substitution': msg.constituentaNotInRSform(schema.title)
}) })
deleted.add(original_cst.pk) deleted.add(original_cst.pk)
return attrs return attrs
@ -325,14 +325,14 @@ class InlineSynthesisSerializer(serializers.Serializer):
schema_out = cast(LibraryItem, attrs['receiver']) schema_out = cast(LibraryItem, attrs['receiver'])
if user.is_anonymous or (schema_out.owner != user and not user.is_staff): if user.is_anonymous or (schema_out.owner != user and not user.is_staff):
raise PermissionDenied({ raise PermissionDenied({
'message': msg.schemaNotOwned(), 'message': msg.schemaForbidden(),
'object_id': schema_in.id 'object_id': schema_in.id
}) })
constituents = cast(list[Constituenta], attrs['items']) constituents = cast(list[Constituenta], attrs['items'])
for cst in constituents: for cst in constituents:
if cst.schema != schema_in: if cst.schema != schema_in:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.id}': msg.constituentaNotOwned(schema_in.title) f'{cst.id}': msg.constituentaNotInRSform(schema_in.title)
}) })
deleted = set() deleted = set()
for item in attrs['substitutions']: for item in attrs['substitutions']:
@ -345,7 +345,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
}) })
if substitution_cst.schema != schema_out: if substitution_cst.schema != schema_out:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{substitution_cst.id}': msg.constituentaNotOwned(schema_out.title) f'{substitution_cst.id}': msg.constituentaNotInRSform(schema_out.title)
}) })
else: else:
if substitution_cst not in constituents: if substitution_cst not in constituents:
@ -354,7 +354,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
}) })
if original_cst.schema != schema_out: if original_cst.schema != schema_out:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.constituentaNotOwned(schema_out.title) f'{original_cst.id}': msg.constituentaNotInRSform(schema_out.title)
}) })
if original_cst.pk in deleted: if original_cst.pk in deleted:
raise serializers.ValidationError({ raise serializers.ValidationError({

View File

@ -2,11 +2,11 @@
# pylint: skip-file # pylint: skip-file
def constituentaNotOwned(title: str): def constituentaNotInRSform(title: str):
return f'Конституента не принадлежит схеме: {title}' return f'Конституента не принадлежит схеме: {title}'
def operationNotOwned(title: str): def operationNotInOSS(title: str):
return f'Операция не принадлежит схеме: {title}' return f'Операция не принадлежит схеме: {title}'
@ -14,7 +14,7 @@ def substitutionNotInList():
return 'Отождествляемая конституента отсутствует в списке' return 'Отождествляемая конституента отсутствует в списке'
def schemaNotOwned(): def schemaForbidden():
return 'Нет доступа к схеме' return 'Нет доступа к схеме'

View File

@ -7,6 +7,7 @@ import {
IOperationCreateData, IOperationCreateData,
IOperationCreatedResponse, IOperationCreatedResponse,
IOperationSchemaData, IOperationSchemaData,
IOperationSetInputData,
IPositionsData, IPositionsData,
ITargetOperation ITargetOperation
} from '@/models/oss'; } from '@/models/oss';
@ -50,3 +51,10 @@ export function patchCreateInput(oss: string, request: FrontExchange<ITargetOper
request: request request: request
}); });
} }
export function patchSetInput(oss: string, request: FrontExchange<IOperationSetInputData, IOperationSchemaData>) {
AxiosPatch({
endpoint: `/api/oss/${oss}/set-input`,
request: request
});
}

View File

@ -12,7 +12,13 @@ import {
patchSetOwner, patchSetOwner,
postSubscribe postSubscribe
} from '@/backend/library'; } from '@/backend/library';
import { patchCreateInput, patchDeleteOperation, patchUpdatePositions, postCreateOperation } from '@/backend/oss'; import {
patchCreateInput,
patchDeleteOperation,
patchSetInput,
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';
@ -21,6 +27,7 @@ import {
IOperationCreateData, IOperationCreateData,
IOperationSchema, IOperationSchema,
IOperationSchemaData, IOperationSchemaData,
IOperationSetInputData,
IPositionsData, IPositionsData,
ITargetOperation ITargetOperation
} from '@/models/oss'; } from '@/models/oss';
@ -55,6 +62,7 @@ interface IOssContext {
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; createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
setInput: (data: IOperationSetInputData, callback?: () => void) => void;
} }
const OssContext = createContext<IOssContext | null>(null); const OssContext = createContext<IOssContext | null>(null);
@ -333,6 +341,27 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
[itemID, library] [itemID, library]
); );
const setInput = useCallback(
(data: IOperationSetInputData, callback?: () => void) => {
if (!schema) {
return;
}
setProcessingError(undefined);
patchSetInput(itemID, {
data: data,
showError: true,
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: newData => {
library.setGlobalOSS(newData);
library.localUpdateTimestamp(newData.id);
if (callback) callback();
}
});
},
[itemID, schema, library]
);
return ( return (
<OssContext.Provider <OssContext.Provider
value={{ value={{
@ -356,7 +385,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
savePositions, savePositions,
createOperation, createOperation,
deleteOperation, deleteOperation,
createInput createInput,
setInput
}} }}
> >
{children} {children}

View File

@ -157,7 +157,11 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
onSuccess: newData => { onSuccess: newData => {
setSchema(Object.assign(schema, newData)); setSchema(Object.assign(schema, newData));
library.localUpdateItem(newData); library.localUpdateItem(newData);
if (library.globalOSS?.schemas.includes(newData.id)) {
library.reloadOSS(() => {
if (callback) callback(newData); if (callback) callback(newData);
});
} else if (callback) callback(newData);
} }
}); });
}, },

View File

@ -0,0 +1,79 @@
'use client';
import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react';
import { IconReset } from '@/components/Icons';
import PickSchema from '@/components/select/PickSchema';
import Checkbox from '@/components/ui/Checkbox';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import Modal, { ModalProps } from '@/components/ui/Modal';
import { ILibraryItem, LibraryItemID } from '@/models/library';
import { IOperation, IOperationSchema } from '@/models/oss';
interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
oss: IOperationSchema;
target: IOperation;
onSubmit: (newSchema: LibraryItemID | undefined, syncText: boolean) => void;
}
function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) {
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined);
const [syncText, setSyncText] = useState(target.sync_text);
const baseFilter = useCallback(
(item: ILibraryItem) => !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result,
[oss, selected, target]
);
const isValid = useMemo(() => target.result !== selected, [target, selected]);
const handleSelectLocation = useCallback((newValue: LibraryItemID) => {
setSelected(newValue);
}, []);
function handleSubmit() {
onSubmit(selected, syncText);
}
return (
<Modal
overflowVisible
header='Выбор концептуальной схемы'
submitText='Подтвердить выбор'
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')}
>
<div className='flex justify-between gap-3 items-center'>
<div className='flex gap-3'>
<Label text='Загружаемая концептуальная схема' />
<MiniButton
title='Сбросить выбор схемы'
noHover
noPadding
icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => setSelected(undefined)}
disabled={selected == undefined}
/>
</div>
</div>
<PickSchema
value={selected} // prettier: split-line
onSelectValue={handleSelectLocation}
rows={8}
baseFilter={baseFilter}
/>
<Checkbox
value={syncText}
setValue={setSyncText}
label='Синхронизировать текст'
titleHtml='Загрузить текстовые поля<br/> из концептуальной схемы'
/>
</Modal>
);
}
export default DlgChangeInputSchema;

View File

@ -80,7 +80,7 @@ function TabInputOperation({
value={syncText} value={syncText}
setValue={setSyncText} setValue={setSyncText}
label='Синхронизировать текст' label='Синхронизировать текст'
title='Брать текст из концептуальной схемы' titleHtml='Загрузить текстовые поля<br/> из концептуальной схемы'
/> />
</FlexColumn> </FlexColumn>

View File

@ -69,6 +69,14 @@ export interface IOperationCreateData extends IPositionsData {
create_schema: boolean; create_schema: boolean;
} }
/**
* Represents {@link IOperation} data, used in setInput process.
*/
export interface IOperationSetInputData extends ITargetOperation {
sync_text: boolean;
input: LibraryItemID | null;
}
/** /**
* Represents {@link IOperation} Argument. * Represents {@link IOperation} Argument.
*/ */

View File

@ -23,9 +23,18 @@ interface NodeContextMenuProps extends ContextMenuData {
onHide: () => void; onHide: () => void;
onDelete: (target: OperationID) => void; onDelete: (target: OperationID) => void;
onCreateInput: (target: OperationID) => void; onCreateInput: (target: OperationID) => void;
onEditSchema: (target: OperationID) => void;
} }
function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete, onCreateInput }: NodeContextMenuProps) { function NodeContextMenu({
operation,
cursorX,
cursorY,
onHide,
onDelete,
onCreateInput,
onEditSchema
}: 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);
@ -44,8 +53,8 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete, onCrea
}; };
const handleEditSchema = () => { const handleEditSchema = () => {
toast.error('Not implemented');
handleHide(); handleHide();
onEditSchema(operation.id);
}; };
const handleEditOperation = () => { const handleEditOperation = () => {
@ -97,9 +106,9 @@ function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete, onCrea
onClick={handleCreateSchema} onClick={handleCreateSchema}
/> />
) : null} ) : null}
{controller.isMutable && !operation.result && operation.operation_type === OperationType.INPUT ? ( {controller.isMutable && operation.operation_type === OperationType.INPUT ? (
<DropdownButton <DropdownButton
text='Загрузить схему' text={!operation.result ? 'Загрузить схему' : 'Изменить схему'}
title='Выбрать схему для загрузки' title='Выбрать схему для загрузки'
icon={<IconConnect size='1rem' className='icon-primary' />} icon={<IconConnect size='1rem' className='icon-primary' />}
disabled={controller.isProcessing} disabled={controller.isProcessing}

View File

@ -28,9 +28,7 @@ function OperationNode(node: OssNodeInternal) {
noHover noHover
title='Связанная КС' title='Связанная КС'
hideTitle={!controller.showTooltip} hideTitle={!controller.showTooltip}
onClick={() => { onClick={handleOpenSchema}
handleOpenSchema();
}}
disabled={!hasFile} disabled={!hasFile}
/> />
</Overlay> </Overlay>

View File

@ -148,6 +148,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
[controller, getPositions] [controller, getPositions]
); );
const handleEditSchema = useCallback(
(target: OperationID) => {
controller.promptEditInput(target, getPositions());
},
[controller, getPositions]
);
const handleFitView = useCallback(() => { const handleFitView = useCallback(() => {
flow.fitView({ duration: PARAMETER.zoomDuration }); flow.fitView({ duration: PARAMETER.zoomDuration });
}, [flow]); }, [flow]);
@ -293,6 +300,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
onHide={handleContextMenuHide} onHide={handleContextMenuHide}
onDelete={handleDeleteOperation} onDelete={handleDeleteOperation}
onCreateInput={handleCreateInput} onCreateInput={handleCreateInput}
onEditSchema={handleEditSchema}
{...menuProps} {...menuProps}
/> />
) : null} ) : null}

View File

@ -10,12 +10,19 @@ import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext'; import { useOSS } from '@/context/OssContext';
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
import DlgChangeLocation from '@/dialogs/DlgChangeLocation'; import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
import DlgCreateOperation from '@/dialogs/DlgCreateOperation'; import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
import DlgEditEditors from '@/dialogs/DlgEditEditors'; import DlgEditEditors from '@/dialogs/DlgEditEditors';
import { AccessPolicy } from '@/models/library'; import { AccessPolicy, LibraryItemID } from '@/models/library';
import { Position2D } from '@/models/miscellaneous'; import { Position2D } from '@/models/miscellaneous';
import { IOperationCreateData, IOperationPosition, IOperationSchema, OperationID } from '@/models/oss'; import {
IOperationCreateData,
IOperationPosition,
IOperationSchema,
IOperationSetInputData,
OperationID
} from '@/models/oss';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserLevel } from '@/models/user';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
@ -45,6 +52,7 @@ export interface IOssEditContext {
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; createInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
} }
const OssEditContext = createContext<IOssEditContext | null>(null); const OssEditContext = createContext<IOssEditContext | null>(null);
@ -79,10 +87,16 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const [showEditEditors, setShowEditEditors] = useState(false); const [showEditEditors, setShowEditEditors] = useState(false);
const [showEditLocation, setShowEditLocation] = useState(false); const [showEditLocation, setShowEditLocation] = useState(false);
const [showEditInput, setShowEditInput] = useState(false);
const [showCreateOperation, setShowCreateOperation] = useState(false); const [showCreateOperation, setShowCreateOperation] = useState(false);
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 }); const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
const [positions, setPositions] = useState<IOperationPosition[]>([]); const [positions, setPositions] = useState<IOperationPosition[]>([]);
const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined);
const targetOperation = useMemo(
() => (targetOperationID ? model.schema?.operationByID.get(targetOperationID) : undefined),
[model, targetOperationID]
);
useLayoutEffect( useLayoutEffect(
() => () =>
@ -221,6 +235,28 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
[model, router] [model, router]
); );
const promptEditInput = useCallback((target: OperationID, positions: IOperationPosition[]) => {
setPositions(positions);
setTargetOperationID(target);
setShowEditInput(true);
}, []);
const setTargetInput = useCallback(
(newInput: LibraryItemID | undefined, syncText: boolean) => {
if (!targetOperationID) {
return;
}
const data: IOperationSetInputData = {
target: targetOperationID,
positions: positions,
sync_text: syncText,
input: newInput ?? null
};
model.setInput(data, () => toast.success(information.changesSaved));
},
[model, targetOperationID, positions]
);
return ( return (
<OssEditContext.Provider <OssEditContext.Provider
value={{ value={{
@ -246,7 +282,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
savePositions, savePositions,
promptCreateOperation, promptCreateOperation,
deleteOperation, deleteOperation,
createInput createInput,
promptEditInput
}} }}
> >
{model.schema ? ( {model.schema ? (
@ -274,6 +311,14 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
onCreate={handleCreateOperation} onCreate={handleCreateOperation}
/> />
) : null} ) : null}
{showEditInput ? (
<DlgChangeInputSchema
hideWindow={() => setShowEditInput(false)}
oss={model.schema}
target={targetOperation!}
onSubmit={setTargetInput}
/>
) : null}
</AnimatePresence> </AnimatePresence>
) : null} ) : null}