'use client'; import { createContext, useContext, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { urls } from '@/app/urls'; import { useAuth } from '@/backend/auth/useAuth'; import { useDeleteItem } from '@/backend/library/useDeleteItem'; import { useInputUpdate } from '@/backend/oss/useInputUpdate'; import { useOperationCreate } from '@/backend/oss/useOperationCreate'; import { useOperationDelete } from '@/backend/oss/useOperationDelete'; import { useOperationUpdate } from '@/backend/oss/useOperationUpdate'; import { useOssSuspense } from '@/backend/oss/useOSS'; import { useRelocateConstituents } from '@/backend/oss/useRelocateConstituents'; import { useUpdatePositions } from '@/backend/oss/useUpdatePositions'; import { ILibraryItemEditor, LibraryItemID } from '@/models/library'; import { calculateInsertPosition } from '@/models/miscellaneousAPI'; import { IOperationPosition, IOperationSchema, OperationID, OperationType } from '@/models/oss'; import { UserRole } from '@/models/user'; import { useDialogsStore } from '@/stores/dialogs'; import { usePreferencesStore } from '@/stores/preferences'; import { useRoleStore } from '@/stores/role'; import { PARAMETER } from '@/utils/constants'; import { information, prompts } from '@/utils/labels'; import { RSTabID } from '../RSFormPage/RSEditContext'; export enum OssTabID { CARD = 0, GRAPH = 1 } export interface ICreateOperationPrompt { defaultX: number; defaultY: number; inputs: OperationID[]; positions: IOperationPosition[]; callback: (newID: OperationID) => void; } export interface IOssEditContext extends ILibraryItemEditor { schema: IOperationSchema; selected: OperationID[]; isOwned: boolean; isMutable: boolean; isAttachedToOSS: boolean; showTooltip: boolean; setShowTooltip: (newValue: boolean) => void; navigateTab: (tab: OssTabID) => void; navigateOperationSchema: (target: OperationID) => void; deleteSchema: () => void; setSelected: React.Dispatch>; canDelete: (target: OperationID) => boolean; promptCreateOperation: (props: ICreateOperationPrompt) => void; promptDeleteOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void; } const OssEditContext = createContext(null); export const useOssEdit = () => { const context = useContext(OssEditContext); if (context === null) { throw new Error('useOssEdit has to be used within '); } return context; }; interface OssEditStateProps { itemID: LibraryItemID; } export const OssEditState = ({ itemID, children }: React.PropsWithChildren) => { const router = useConceptNavigation(); const { user } = useAuth(); const adminMode = usePreferencesStore(state => state.adminMode); const role = useRoleStore(state => state.role); const adjustRole = useRoleStore(state => state.adjustRole); const { schema } = useOssSuspense({ itemID: itemID }); const isOwned = user?.id === schema.owner || false; const isMutable = role > UserRole.READER && !schema.read_only; const [showTooltip, setShowTooltip] = useState(true); const [selected, setSelected] = useState([]); const showEditInput = useDialogsStore(state => state.showChangeInputSchema); const showEditOperation = useDialogsStore(state => state.showEditOperation); const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation); const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents); const showCreateOperation = useDialogsStore(state => state.showCreateOperation); const { deleteItem } = useDeleteItem(); const { updatePositions } = useUpdatePositions(); const { operationCreate } = useOperationCreate(); const { operationDelete } = useOperationDelete(); const { operationUpdate } = useOperationUpdate(); const { relocateConstituents } = useRelocateConstituents(); const { inputUpdate } = useInputUpdate(); useEffect( () => adjustRole({ isOwner: isOwned, isEditor: (user && schema.editors.includes(user?.id)) ?? false, isStaff: user?.is_staff ?? false, adminMode: adminMode }), [schema, adjustRole, isOwned, user, adminMode] ); function navigateTab(tab: OssTabID) { if (!schema) { return; } const url = urls.oss_props({ id: schema.id, tab: tab }); router.push(url); } function navigateOperationSchema(target: OperationID) { const node = schema.operationByID.get(target); if (!node?.result) { return; } router.push(urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST })); } function deleteSchema() { if (!schema || !window.confirm(prompts.deleteOSS)) { return; } deleteItem(schema.id, () => { toast.success(information.itemDestroyed); router.push(urls.library); }); } function promptCreateOperation({ defaultX, defaultY, inputs, positions, callback }: ICreateOperationPrompt) { showCreateOperation({ oss: schema, onCreate: data => { const target = calculateInsertPosition(schema, data.item_data.operation_type, data.arguments ?? [], positions, { x: defaultX, y: defaultY }); data.positions = positions; data.item_data.position_x = target.x; data.item_data.position_y = target.y; operationCreate({ itemID: schema.id, data }, operation => { toast.success(information.newOperation(operation.alias)); if (callback) { setTimeout(() => callback(operation.id), PARAMETER.refreshTimeout); } }); }, initialInputs: inputs }); } function canDelete(target: OperationID) { const operation = schema.operationByID.get(target); if (!operation) { return false; } if (operation.operation_type === OperationType.INPUT) { return true; } return schema.graph.expandOutputs([target]).length === 0; } function promptEditOperation(target: OperationID, positions: IOperationPosition[]) { const operation = schema.operationByID.get(target); if (!operation) { return; } showEditOperation({ oss: schema, target: operation, onSubmit: data => { data.positions = positions; operationUpdate({ itemID: schema.id, data }, () => toast.success(information.changesSaved)); } }); } function promptDeleteOperation(target: OperationID, positions: IOperationPosition[]) { const operation = schema.operationByID.get(target); if (!operation) { return; } showDeleteOperation({ target: operation, onSubmit: (targetID, keepConstituents, deleteSchema) => { operationDelete( { itemID: schema.id, data: { target: targetID, positions: positions, keep_constituents: keepConstituents, delete_schema: deleteSchema } }, () => toast.success(information.operationDestroyed) ); } }); } function promptEditInput(target: OperationID, positions: IOperationPosition[]) { const operation = schema.operationByID.get(target); if (!operation) { return; } showEditInput({ oss: schema, target: operation, onSubmit: (target, newInput) => { inputUpdate( { itemID: schema.id, data: { target: target, positions: positions, input: newInput ?? null } }, () => toast.success(information.changesSaved) ); } }); } function promptRelocateConstituents(target: OperationID | undefined, positions: IOperationPosition[]) { const operation = target ? schema.operationByID.get(target) : undefined; showRelocateConstituents({ oss: schema, initialTarget: operation, onSubmit: data => { if ( positions.every(item => { const operation = schema.operationByID.get(item.id)!; return operation.position_x === item.position_x && operation.position_y === item.position_y; }) ) { relocateConstituents({ itemID: schema.id, data }, () => toast.success(information.changesSaved)); } else { updatePositions( { itemID: schema.id, // positions: positions }, () => relocateConstituents({ itemID: schema.id, data }, () => toast.success(information.changesSaved)) ); } } }); } return ( {children} ); };