From 519b5f6634cb7a39b0acd0a413f6d82eb969c3a9 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Sun, 26 Jan 2025 22:24:34 +0300 Subject: [PATCH] F: Implement react-query pt4 --- .../frontend/src/app/GlobalOssContext.tsx | 63 -- .../frontend/src/app/GlobalProviders.tsx | 3 - rsconcept/frontend/src/backend/library/api.ts | 10 +- .../library/useIsProcessingLibrary.tsx | 6 +- .../src/backend/library/useLibraryItem.tsx | 23 + rsconcept/frontend/src/backend/oss/api.ts | 2 +- rsconcept/frontend/src/backend/oss/useOSS.tsx | 6 +- .../src/backend/oss/useUpdatePositions.tsx | 2 +- rsconcept/frontend/src/backend/rsform/api.ts | 2 +- .../frontend/src/backend/rsform/useRSForm.tsx | 4 +- .../src/dialogs/DlgCloneLibraryItem.tsx | 2 +- .../DlgEditVersions/DlgEditVersions.tsx | 48 +- rsconcept/frontend/src/models/library.ts | 11 +- rsconcept/frontend/src/pages/HomePage.tsx | 2 +- .../pages/ManualsPage/items/ui/HelpRSMenu.tsx | 4 - .../OssPage/EditorOssCard/EditorOssCard.tsx | 22 +- .../pages/OssPage/EditorOssCard/FormOSS.tsx | 12 +- .../OssPage/EditorOssGraph/EditorOssGraph.tsx | 9 +- .../EditorOssGraph/NodeContextMenu.tsx | 21 +- .../pages/OssPage/EditorOssGraph/OssFlow.tsx | 79 ++- .../EditorOssGraph/ToolbarOssGraph.tsx | 22 +- .../src/pages/OssPage/MenuOssTabs.tsx | 18 +- .../src/pages/OssPage/OssEditContext.tsx | 447 ++++++-------- .../frontend/src/pages/OssPage/OssPage.tsx | 60 +- .../frontend/src/pages/OssPage/OssTabs.tsx | 153 ++--- .../EditorConstituenta/ControlsOverlay.tsx | 55 -- .../EditorConstituenta/EditorConstituenta.tsx | 61 +- .../EditorConstituenta/EditorControls.tsx | 84 +++ .../EditorConstituenta/FormConstituenta.tsx | 145 ++--- .../ToolbarConstituenta.tsx | 45 +- .../EditorRSExpression/EditorRSExpression.tsx | 4 +- .../EditorRSFormCard/EditorLibraryItem.tsx | 105 ++-- .../EditorRSFormCard/EditorRSFormCard.tsx | 22 +- .../EditorRSFormCard/FormRSForm.tsx | 23 +- .../EditorRSFormCard/ToolbarItemAccess.tsx | 21 +- .../EditorRSFormCard/ToolbarRSFormCard.tsx | 23 +- .../EditorRSFormCard/ToolbarVersioning.tsx | 55 +- .../RSFormPage/EditorRSList/EditorRSList.tsx | 12 +- .../RSFormPage/EditorRSList/ToolbarRSList.tsx | 16 +- .../EditorTermGraph/EditorTermGraph.tsx | 10 +- .../RSFormPage/EditorTermGraph/TGFlow.tsx | 26 +- .../EditorTermGraph/ToolbarTermGraph.tsx | 21 +- .../RSFormPage/EditorTermGraph/ViewHidden.tsx | 8 +- .../src/pages/RSFormPage/MenuRSTabs.tsx | 187 ++++-- .../src/pages/RSFormPage/RSEditContext.tsx | 560 ++++-------------- .../src/pages/RSFormPage/RSFormPage.tsx | 72 ++- .../frontend/src/pages/RSFormPage/RSTabs.tsx | 274 ++------- .../ViewConstituents/ViewConstituents.tsx | 11 +- rsconcept/frontend/src/stores/modification.ts | 11 + rsconcept/frontend/src/utils/utils.ts | 23 +- 50 files changed, 1317 insertions(+), 1588 deletions(-) delete mode 100644 rsconcept/frontend/src/app/GlobalOssContext.tsx create mode 100644 rsconcept/frontend/src/backend/library/useLibraryItem.tsx delete mode 100644 rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ControlsOverlay.tsx create mode 100644 rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorControls.tsx create mode 100644 rsconcept/frontend/src/stores/modification.ts diff --git a/rsconcept/frontend/src/app/GlobalOssContext.tsx b/rsconcept/frontend/src/app/GlobalOssContext.tsx deleted file mode 100644 index 30a40e87..00000000 --- a/rsconcept/frontend/src/app/GlobalOssContext.tsx +++ /dev/null @@ -1,63 +0,0 @@ -'use client'; - -import { createContext, useCallback, useContext, useState } from 'react'; - -import { useOss, useOssInvalidate, useOssUpdate } from '@/backend/oss/useOSS'; -import { ErrorData } from '@/components/info/InfoError'; -import { LibraryItemID } from '@/models/library'; -import { IOperationSchema, IOperationSchemaData } from '@/models/oss'; -import { contextOutsideScope } from '@/utils/labels'; - -interface IGlobalOssContext { - schema: IOperationSchema | undefined; - setID: (id: LibraryItemID | undefined) => void; - setData: (data: IOperationSchemaData) => void; - loading: boolean; - loadingError: ErrorData; - - invalidate: () => Promise; - invalidateItem: (target: LibraryItemID) => void; - partialUpdate: (data: Partial) => void; -} - -const GlobalOssContext = createContext(null); -export const useGlobalOss = (): IGlobalOssContext => { - const context = useContext(GlobalOssContext); - if (context === null) { - throw new Error(contextOutsideScope('useGlobalOss', 'GlobalOssState')); - } - return context; -}; - -export const GlobalOssState = ({ children }: React.PropsWithChildren) => { - const [ossID, setID] = useState(undefined); - const { schema: schema, error: loadingError, isLoading: loading } = useOss({ itemID: ossID }); - const { update, partialUpdate } = useOssUpdate({ itemID: ossID }); - const { invalidate } = useOssInvalidate({ itemID: ossID }); - - const invalidateItem = useCallback( - (target: LibraryItemID) => { - if (schema?.schemas.includes(target)) { - invalidate().catch(console.error); - } - }, - [invalidate, schema] - ); - - return ( - - {children} - - ); -}; diff --git a/rsconcept/frontend/src/app/GlobalProviders.tsx b/rsconcept/frontend/src/app/GlobalProviders.tsx index 9785495a..21f05748 100644 --- a/rsconcept/frontend/src/app/GlobalProviders.tsx +++ b/rsconcept/frontend/src/app/GlobalProviders.tsx @@ -6,7 +6,6 @@ import { ErrorBoundary } from 'react-error-boundary'; import { IntlProvider } from 'react-intl'; import { queryClient } from '@/backend/queryClient'; -import { GlobalOssState } from '@/app/GlobalOssContext'; import ErrorFallback from './ErrorFallback'; @@ -31,12 +30,10 @@ function GlobalProviders({ children }: React.PropsWithChildren) { > - {children} - ); diff --git a/rsconcept/frontend/src/backend/library/api.ts b/rsconcept/frontend/src/backend/library/api.ts index 1afc069c..79f5b8d2 100644 --- a/rsconcept/frontend/src/backend/library/api.ts +++ b/rsconcept/frontend/src/backend/library/api.ts @@ -2,10 +2,13 @@ import { queryOptions } from '@tanstack/react-query'; import { axiosInstance } from '@/backend/axiosInstance'; import { DELAYS } from '@/backend/configuration'; -import { AccessPolicy, ILibraryItem, IVersionData, LibraryItemID, VersionID } from '@/models/library'; +import { AccessPolicy, ILibraryItem, IVersionData, LibraryItemID, LibraryItemType, VersionID } from '@/models/library'; import { ConstituentaID, IRSFormData } from '@/models/rsform'; import { UserID } from '@/models/user'; +import { ossApi } from '../oss/api'; +import { rsformsApi } from '../rsform/api'; + /** * Represents update data for renaming Location. */ @@ -67,6 +70,11 @@ export const libraryApi = { }) .then(response => response.data) }), + getItemQueryOptions: ({ itemID, itemType }: { itemID: LibraryItemID; itemType: LibraryItemType }) => { + return itemType === LibraryItemType.RSFORM + ? rsformsApi.getRSFormQueryOptions({ itemID }) + : ossApi.getOssQueryOptions({ itemID }); + }, getTemplatesQueryOptions: () => queryOptions({ queryKey: [libraryApi.baseKey, 'templates'], diff --git a/rsconcept/frontend/src/backend/library/useIsProcessingLibrary.tsx b/rsconcept/frontend/src/backend/library/useIsProcessingLibrary.tsx index c278c6ea..d906cc9b 100644 --- a/rsconcept/frontend/src/backend/library/useIsProcessingLibrary.tsx +++ b/rsconcept/frontend/src/backend/library/useIsProcessingLibrary.tsx @@ -1,8 +1,12 @@ import { useIsMutating } from '@tanstack/react-query'; +import { ossApi } from '../oss/api'; +import { rsformsApi } from '../rsform/api'; import { libraryApi } from './api'; export const useIsProcessingLibrary = () => { const countMutations = useIsMutating({ mutationKey: [libraryApi.baseKey] }); - return countMutations !== 0; + const countOss = useIsMutating({ mutationKey: [ossApi.baseKey] }); + const countRSForm = useIsMutating({ mutationKey: [rsformsApi.baseKey] }); + return countMutations + countOss + countRSForm !== 0; }; diff --git a/rsconcept/frontend/src/backend/library/useLibraryItem.tsx b/rsconcept/frontend/src/backend/library/useLibraryItem.tsx new file mode 100644 index 00000000..340a973d --- /dev/null +++ b/rsconcept/frontend/src/backend/library/useLibraryItem.tsx @@ -0,0 +1,23 @@ +import { useQuery } from '@tanstack/react-query'; + +import { ILibraryItemVersioned, LibraryItemID, LibraryItemType } from '@/models/library'; + +import { ossApi } from '../oss/api'; +import { rsformsApi } from '../rsform/api'; + +export function useLibraryItem({ itemID, itemType }: { itemID: LibraryItemID; itemType: LibraryItemType }) { + const { data: rsForm } = useQuery({ + ...rsformsApi.getRSFormQueryOptions({ itemID }), + enabled: itemType === LibraryItemType.RSFORM + }); + const { data: oss } = useQuery({ + ...ossApi.getOssQueryOptions({ itemID }), + enabled: itemType === LibraryItemType.OSS + }); + return { + item: + itemType === LibraryItemType.RSFORM + ? (rsForm as ILibraryItemVersioned | undefined) + : (oss as ILibraryItemVersioned | undefined) + }; +} diff --git a/rsconcept/frontend/src/backend/oss/api.ts b/rsconcept/frontend/src/backend/oss/api.ts index 4a4d4e8a..7e2820fb 100644 --- a/rsconcept/frontend/src/backend/oss/api.ts +++ b/rsconcept/frontend/src/backend/oss/api.ts @@ -92,7 +92,7 @@ export interface ICstRelocateDTO { } export const ossApi = { - baseKey: 'library', + baseKey: 'oss', getOssQueryOptions: ({ itemID }: { itemID?: LibraryItemID }) => { return queryOptions({ diff --git a/rsconcept/frontend/src/backend/oss/useOSS.tsx b/rsconcept/frontend/src/backend/oss/useOSS.tsx index 6a99dad4..4458e82e 100644 --- a/rsconcept/frontend/src/backend/oss/useOSS.tsx +++ b/rsconcept/frontend/src/backend/oss/useOSS.tsx @@ -1,10 +1,10 @@ import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; +import { useLibrary, useLibrarySuspense } from '@/backend/library/useLibrary'; import { LibraryItemID } from '@/models/library'; import { IOperationSchema, IOperationSchemaData } from '@/models/oss'; import { OssLoader } from '@/models/OssLoader'; -import { useLibrary, useLibrarySuspense } from '@/backend/library/useLibrary'; import { ossApi } from './api'; export function useOss({ itemID }: { itemID?: LibraryItemID }) { @@ -17,12 +17,12 @@ export function useOss({ itemID }: { itemID?: LibraryItemID }) { return { schema: schema, isLoading: isLoading || libraryLoading, error: error }; } -export function useOssSuspense({ itemID }: { itemID?: LibraryItemID }) { +export function useOssSuspense({ itemID }: { itemID: LibraryItemID }) { const { items: libraryItems } = useLibrarySuspense(); const { data } = useSuspenseQuery({ ...ossApi.getOssQueryOptions({ itemID }) }); - const schema = data ? new OssLoader(data, libraryItems).produceOSS() : undefined; + const schema = new OssLoader(data!, libraryItems).produceOSS(); return { schema }; } diff --git a/rsconcept/frontend/src/backend/oss/useUpdatePositions.tsx b/rsconcept/frontend/src/backend/oss/useUpdatePositions.tsx index 655d7a68..22cdbe4d 100644 --- a/rsconcept/frontend/src/backend/oss/useUpdatePositions.tsx +++ b/rsconcept/frontend/src/backend/oss/useUpdatePositions.tsx @@ -14,7 +14,7 @@ export const useUpdatePositions = () => { onSuccess: (_, variables) => updateTimestamp(variables.itemID) }); return { - cstDelete: ( + updatePositions: ( data: { itemID: LibraryItemID; // positions: IOperationPosition[]; diff --git a/rsconcept/frontend/src/backend/rsform/api.ts b/rsconcept/frontend/src/backend/rsform/api.ts index c4391a62..35f1993b 100644 --- a/rsconcept/frontend/src/backend/rsform/api.ts +++ b/rsconcept/frontend/src/backend/rsform/api.ts @@ -107,7 +107,7 @@ export interface ICheckConstituentaDTO { } export const rsformsApi = { - baseKey: 'library', + baseKey: 'rsform', getRSFormQueryOptions: ({ itemID, version }: { itemID?: LibraryItemID; version?: VersionID }) => { return queryOptions({ diff --git a/rsconcept/frontend/src/backend/rsform/useRSForm.tsx b/rsconcept/frontend/src/backend/rsform/useRSForm.tsx index 66050a4a..86334a5f 100644 --- a/rsconcept/frontend/src/backend/rsform/useRSForm.tsx +++ b/rsconcept/frontend/src/backend/rsform/useRSForm.tsx @@ -15,11 +15,11 @@ export function useRSForm({ itemID, version }: { itemID?: LibraryItemID; version return { schema, isLoading, error }; } -export function useRSFormSuspense({ itemID, version }: { itemID?: LibraryItemID; version?: VersionID }) { +export function useRSFormSuspense({ itemID, version }: { itemID: LibraryItemID; version?: VersionID }) { const { data } = useSuspenseQuery({ ...rsformsApi.getRSFormQueryOptions({ itemID, version }) }); - const schema = data ? new RSFormLoader(data).produceRSForm() : undefined; + const schema = new RSFormLoader(data!).produceRSForm(); return { schema }; } diff --git a/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx b/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx index cb9ae85d..d5633be0 100644 --- a/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx +++ b/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import { 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 { IRSFormCloneDTO } from '@/backend/library/api'; @@ -18,7 +19,6 @@ import MiniButton from '@/components/ui/MiniButton'; import Modal from '@/components/ui/Modal'; import TextArea from '@/components/ui/TextArea'; import TextInput from '@/components/ui/TextInput'; -import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library'; import { cloneTitle, combineLocation, validateLocation } from '@/models/libraryAPI'; import { ConstituentaID } from '@/models/rsform'; diff --git a/rsconcept/frontend/src/dialogs/DlgEditVersions/DlgEditVersions.tsx b/rsconcept/frontend/src/dialogs/DlgEditVersions/DlgEditVersions.tsx index 1c920962..660fa82f 100644 --- a/rsconcept/frontend/src/dialogs/DlgEditVersions/DlgEditVersions.tsx +++ b/rsconcept/frontend/src/dialogs/DlgEditVersions/DlgEditVersions.tsx @@ -1,34 +1,51 @@ 'use client'; import { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; +import { urls } from '@/app/urls'; +import { useIsProcessingLibrary } from '@/backend/library/useIsProcessingLibrary'; +import { useVersionDelete } from '@/backend/library/useVersionDelete'; +import { useVersionUpdate } from '@/backend/library/useVersionUpdate'; import { IconReset, IconSave } from '@/components/Icons'; import MiniButton from '@/components/ui/MiniButton'; import Modal from '@/components/ui/Modal'; import TextArea from '@/components/ui/TextArea'; import TextInput from '@/components/ui/TextInput'; -import { IVersionData, IVersionInfo, VersionID } from '@/models/library'; +import { ILibraryItemVersioned, IVersionData, IVersionInfo, VersionID } from '@/models/library'; import { useDialogsStore } from '@/stores/dialogs'; +import { information } from '@/utils/labels'; import TableVersions from './TableVersions'; export interface DlgEditVersionsProps { - versions: IVersionInfo[]; - onDelete: (versionID: VersionID) => void; - onUpdate: (versionID: VersionID, data: IVersionData) => void; + item: ILibraryItemVersioned; } function DlgEditVersions() { - const { versions, onDelete, onUpdate } = useDialogsStore(state => state.props as DlgEditVersionsProps); - const [selected, setSelected] = useState(undefined); - const processing = false; // TODO: fix processing hook and versions update + const { item } = useDialogsStore(state => state.props as DlgEditVersionsProps); + const router = useConceptNavigation(); + const processing = useIsProcessingLibrary(); + const { versionDelete } = useVersionDelete(); + const { versionUpdate } = useVersionUpdate(); + const [selected, setSelected] = useState(undefined); const [version, setVersion] = useState(''); const [description, setDescription] = useState(''); - const isValid = selected && versions.every(ver => ver.id === selected.id || ver.version != version); + const isValid = selected && item.versions.every(ver => ver.id === selected.id || ver.version != version); const isModified = selected && (selected.version != version || selected.description != description); + function handleDeleteVersion(versionID: VersionID) { + versionDelete({ itemID: item.id, versionID: versionID }, () => { + toast.success(information.versionDestroyed); + if (versionID === versionID) { + router.push(urls.schema(item.id)); + } + }); + } + function handleUpdate() { if (!isModified || !selected || processing || !isValid) { return; @@ -37,7 +54,14 @@ function DlgEditVersions() { version: version, description: description }; - onUpdate(selected.id, data); + versionUpdate( + { + itemID: item.id, // + versionID: selected.id, + data: data + }, + () => toast.success(information.changesSaved) + ); } function handleReset() { @@ -57,9 +81,9 @@ function DlgEditVersions() { setSelected(versions.find(ver => ver.id === versionID))} + items={item.versions} + onDelete={handleDeleteVersion} + onSelect={versionID => setSelected(item.versions.find(ver => ver.id === versionID))} selected={selected?.id} /> diff --git a/rsconcept/frontend/src/models/library.ts b/rsconcept/frontend/src/models/library.ts index 6f9f498d..ed2d6067 100644 --- a/rsconcept/frontend/src/models/library.ts +++ b/rsconcept/frontend/src/models/library.ts @@ -100,16 +100,9 @@ export interface ILibraryItemVersioned extends ILibraryItemData { * Represents common {@link ILibraryItem} editor controller. */ export interface ILibraryItemEditor { - schema?: ILibraryItemData; + schema: ILibraryItemData; + deleteSchema: () => void; isMutable: boolean; - isProcessing: boolean; isAttachedToOSS: boolean; - - setOwner: (newOwner: UserID) => void; - setAccessPolicy: (newPolicy: AccessPolicy) => void; - promptEditors: () => void; - promptLocation: () => void; - - share: () => void; } diff --git a/rsconcept/frontend/src/pages/HomePage.tsx b/rsconcept/frontend/src/pages/HomePage.tsx index 2cb371a4..3b257506 100644 --- a/rsconcept/frontend/src/pages/HomePage.tsx +++ b/rsconcept/frontend/src/pages/HomePage.tsx @@ -1,9 +1,9 @@ import { useEffect } from 'react'; +import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { urls } from '@/app/urls'; import { useAuth } from '@/backend/auth/useAuth'; import Loader from '@/components/ui/Loader'; -import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { PARAMETER } from '@/utils/constants'; function HomePage() { diff --git a/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx b/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx index 123d67bd..df8f58c6 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx @@ -8,7 +8,6 @@ import { IconEdit2, IconEditor, IconMenu, - IconNewVersion, IconOwner, IconReader, IconShare, @@ -54,9 +53,6 @@ function HelpRSMenu() {
  • Клонировать – создать копию схемы
  • -
  • - Сохранить версию -
  • Выгрузить – сохранить в файле формата Экстеор
  • diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx index 1f7679a9..7c8db20b 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx @@ -3,22 +3,19 @@ import clsx from 'clsx'; import FlexColumn from '@/components/ui/FlexColumn'; +import { LibraryItemType } from '@/models/library'; import EditorLibraryItem from '@/pages/RSFormPage/EditorRSFormCard/EditorLibraryItem'; import ToolbarRSFormCard from '@/pages/RSFormPage/EditorRSFormCard/ToolbarRSFormCard'; +import { useModificationStore } from '@/stores/modification'; import { globals } from '@/utils/constants'; import { useOssEdit } from '../OssEditContext'; import FormOSS from './FormOSS'; import OssStats from './OssStats'; -interface EditorOssCardProps { - isModified: boolean; - setIsModified: (newValue: boolean) => void; - onDestroy: () => void; -} - -function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardProps) { +function EditorOssCard() { const controller = useOssEdit(); + const { isModified } = useModificationStore(); function initiateSubmit() { const element = document.getElementById(globals.library_item_editor) as HTMLFormElement; @@ -38,12 +35,7 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr return ( <> - +
    - - + + {controller.schema ? : null} diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx index 032ce23e..88fb9b3a 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx @@ -6,25 +6,27 @@ import { toast } from 'react-toastify'; import { ILibraryUpdateDTO } from '@/backend/library/api'; import { useUpdateItem } from '@/backend/library/useUpdateItem'; +import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss'; import { IconSave } from '@/components/Icons'; import SubmitButton from '@/components/ui/SubmitButton'; import TextArea from '@/components/ui/TextArea'; import TextInput from '@/components/ui/TextInput'; import { LibraryItemType } from '@/models/library'; import ToolbarItemAccess from '@/pages/RSFormPage/EditorRSFormCard/ToolbarItemAccess'; +import { useModificationStore } from '@/stores/modification'; import { information } from '@/utils/labels'; import { useOssEdit } from '../OssEditContext'; interface FormOSSProps { id?: string; - isModified: boolean; - setIsModified: (newValue: boolean) => void; } -function FormOSS({ id, isModified, setIsModified }: FormOSSProps) { +function FormOSS({ id }: FormOSSProps) { const { updateItem: update } = useUpdateItem(); const controller = useOssEdit(); + const { isModified, setIsModified } = useModificationStore(); + const isProcessing = useIsProcessingOss(); const schema = controller.schema; const [title, setTitle] = useState(schema?.title ?? ''); @@ -125,14 +127,14 @@ function FormOSS({ id, isModified, setIsModified }: FormOSSProps) { label='Описание' rows={3} value={comment} - disabled={!controller.isMutable || controller.isProcessing} + disabled={!controller.isMutable || isProcessing} onChange={event => setComment(event.target.value)} /> {controller.isMutable || isModified ? ( } /> diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/EditorOssGraph.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/EditorOssGraph.tsx index 48a7d5b9..5d38ce39 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/EditorOssGraph.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/EditorOssGraph.tsx @@ -4,15 +4,10 @@ import { ReactFlowProvider } from 'reactflow'; import OssFlow from './OssFlow'; -interface EditorOssGraphProps { - isModified: boolean; - setIsModified: (newValue: boolean) => void; -} - -function EditorOssGraph({ isModified, setIsModified }: EditorOssGraphProps) { +function EditorOssGraph() { return ( - + ); } diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx index 9fd9ccc5..adf7c535 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/NodeContextMenu.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; +import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss'; import { IconChild, IconConnect, @@ -49,6 +50,8 @@ function NodeContextMenu({ onRelocateConstituents }: NodeContextMenuProps) { const controller = useOssEdit(); + const isProcessing = useIsProcessingOss(); + const [isOpen, setIsOpen] = useState(false); const ref = useRef(null); const readyForSynthesis = (() => { @@ -64,7 +67,7 @@ function NodeContextMenu({ return false; } - const argumentOperations = argumentIDs.map(id => controller.schema!.operationByID.get(id)!); + const argumentOperations = argumentIDs.map(id => controller.schema.operationByID.get(id)!); if (argumentOperations.some(item => item.result === null)) { return false; } @@ -82,7 +85,7 @@ function NodeContextMenu({ useEffect(() => setIsOpen(true), []); const handleOpenSchema = () => { - controller.openOperationSchema(operation.id); + controller.navigateOperationSchema(operation.id); }; const handleEditSchema = () => { @@ -126,7 +129,7 @@ function NodeContextMenu({ text='Редактировать' title='Редактировать операцию' icon={} - disabled={!controller.isMutable || controller.isProcessing} + disabled={!controller.isMutable || isProcessing} onClick={handleEditOperation} /> @@ -135,7 +138,7 @@ function NodeContextMenu({ text='Открыть схему' titleHtml={prepareTooltip('Открыть привязанную КС', 'Двойной клик')} icon={} - disabled={controller.isProcessing} + disabled={isProcessing} onClick={handleOpenSchema} /> ) : null} @@ -144,7 +147,7 @@ function NodeContextMenu({ text='Создать схему' title='Создать пустую схему для загрузки' icon={} - disabled={controller.isProcessing} + disabled={isProcessing} onClick={handleCreateSchema} /> ) : null} @@ -153,7 +156,7 @@ function NodeContextMenu({ text={!operation.result ? 'Загрузить схему' : 'Изменить схему'} title='Выбрать схему для загрузки' icon={} - disabled={controller.isProcessing} + disabled={isProcessing} onClick={handleEditSchema} /> ) : null} @@ -166,7 +169,7 @@ function NodeContextMenu({ : 'Необходимо предоставить все аргументы' } icon={} - disabled={controller.isProcessing || !readyForSynthesis} + disabled={isProcessing || !readyForSynthesis} onClick={handleRunSynthesis} /> ) : null} @@ -176,7 +179,7 @@ function NodeContextMenu({ text='Конституенты' titleHtml='Перенос конституент
    между схемами' icon={} - disabled={controller.isProcessing} + disabled={isProcessing} onClick={handleRelocateConstituents} /> ) : null} @@ -184,7 +187,7 @@ function NodeContextMenu({ } - disabled={!controller.isMutable || controller.isProcessing || !controller.canDelete(operation.id)} + disabled={!controller.isMutable || isProcessing || !controller.canDelete(operation.id)} onClick={handleDeleteOperation} /> diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx index 029c74bd..a4f077f7 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OssFlow.tsx @@ -16,15 +16,23 @@ import { useReactFlow } from 'reactflow'; +import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; +import { urls } from '@/app/urls'; +import { useLibrary } from '@/backend/library/useLibrary'; +import { useInputCreate } from '@/backend/oss/useInputCreate'; +import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss'; +import { useOperationExecute } from '@/backend/oss/useOperationExecute'; +import { useUpdatePositions } from '@/backend/oss/useUpdatePositions'; import { CProps } from '@/components/props'; import Overlay from '@/components/ui/Overlay'; import { OssNode } from '@/models/miscellaneous'; import { OperationID } from '@/models/oss'; import { useMainHeight } from '@/stores/appLayout'; +import { useModificationStore } from '@/stores/modification'; import { useOSSGraphStore } from '@/stores/ossGraph'; import { APP_COLORS } from '@/styling/color'; import { PARAMETER } from '@/utils/constants'; -import { errors } from '@/utils/labels'; +import { errors, information } from '@/utils/labels'; import { useOssEdit } from '../OssEditContext'; import { OssNodeTypes } from './graph/OssNodeTypes'; @@ -34,20 +42,24 @@ import ToolbarOssGraph from './ToolbarOssGraph'; const ZOOM_MAX = 2; const ZOOM_MIN = 0.5; -interface OssFlowProps { - isModified: boolean; - setIsModified: (newValue: boolean) => void; -} - -function OssFlow({ isModified, setIsModified }: OssFlowProps) { +function OssFlow() { const mainHeight = useMainHeight(); const controller = useOssEdit(); + const router = useConceptNavigation(); + const { items: libraryItems } = useLibrary(); const flow = useReactFlow(); + const { setIsModified } = useModificationStore(); + + const isProcessing = useIsProcessingOss(); const showGrid = useOSSGraphStore(state => state.showGrid); const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate); const edgeStraight = useOSSGraphStore(state => state.edgeStraight); + const { inputCreate } = useInputCreate(); + const { operationExecute } = useOperationExecute(); + const { updatePositions } = useUpdatePositions(); + const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [toggleReset, setToggleReset] = useState(false); @@ -89,8 +101,8 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { type: edgeStraight ? 'straight' : 'simplebezier', animated: edgeAnimate, targetHandle: - controller.schema!.operationByID.get(argument.argument)!.position_x > - controller.schema!.operationByID.get(argument.operation)!.position_x + controller.schema.operationByID.get(argument.argument)!.position_x > + controller.schema.operationByID.get(argument.operation)!.position_x ? 'right' : 'left' })) @@ -117,7 +129,18 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { } function handleSavePositions() { - controller.savePositions(getPositions(), () => setIsModified(false)); + const positions = getPositions(); + updatePositions({ itemID: controller.schema.id, positions: positions }, () => { + positions.forEach(item => { + const operation = controller.schema.operationByID.get(item.id); + if (operation) { + operation.position_x = item.position_x; + operation.position_y = item.position_y; + } + }); + toast.success(information.changesSaved); + setIsModified(false); + }); } function handleCreateOperation(inputs: OperationID[]) { @@ -149,8 +172,19 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { handleDeleteOperation(controller.selected[0]); } - function handleCreateInput(target: OperationID) { - controller.createInput(target, getPositions()); + function handleInputCreate(target: OperationID) { + const operation = controller.schema.operationByID.get(target); + if (!operation) { + return; + } + if (libraryItems.find(item => item.alias === operation.alias && item.location === controller.schema.location)) { + toast.error(errors.inputAlreadyExists); + return; + } + inputCreate({ itemID: controller.schema.id, data: { target: target, positions: getPositions() } }, new_schema => { + toast.success(information.newLibraryItem); + router.push(urls.schema(new_schema.id)); + }); } function handleEditSchema(target: OperationID) { @@ -161,15 +195,21 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { controller.promptEditOperation(target, getPositions()); } - function handleExecuteOperation(target: OperationID) { - controller.executeOperation(target, getPositions()); + function handleOperationExecute(target: OperationID) { + operationExecute( + { + itemID: controller.schema.id, // + data: { target: target, positions: getPositions() } + }, + () => toast.success(information.operationExecuted) + ); } function handleExecuteSelected() { if (controller.selected.length !== 1) { return; } - handleExecuteOperation(controller.selected[0]); + handleOperationExecute(controller.selected[0]); } function handleRelocateConstituents(target: OperationID) { @@ -237,14 +277,14 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { event.preventDefault(); event.stopPropagation(); if (node.data.operation.result) { - controller.openOperationSchema(Number(node.id)); + controller.navigateOperationSchema(Number(node.id)); } else { handleEditOperation(Number(node.id)); } } function handleKeyDown(event: React.KeyboardEvent) { - if (controller.isProcessing) { + if (isProcessing) { return; } if (!controller.isMutable) { @@ -274,7 +314,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
    flow.fitView({ duration: PARAMETER.zoomDuration })} onCreate={() => handleCreateOperation(controller.selected)} onDelete={handleDeleteSelected} @@ -289,10 +328,10 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) { diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/ToolbarOssGraph.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/ToolbarOssGraph.tsx index f9c112ac..bad9d138 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/ToolbarOssGraph.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/ToolbarOssGraph.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx'; +import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss'; import { IconAnimation, IconAnimationOff, @@ -21,6 +22,7 @@ import BadgeHelp from '@/components/info/BadgeHelp'; import MiniButton from '@/components/ui/MiniButton'; import { HelpTopic } from '@/models/miscellaneous'; import { OperationType } from '@/models/oss'; +import { useModificationStore } from '@/stores/modification'; import { useOSSGraphStore } from '@/stores/ossGraph'; import { PARAMETER } from '@/utils/constants'; import { prepareTooltip } from '@/utils/labels'; @@ -28,7 +30,6 @@ import { prepareTooltip } from '@/utils/labels'; import { useOssEdit } from '../OssEditContext'; interface ToolbarOssGraphProps { - isModified: boolean; onCreate: () => void; onDelete: () => void; onEdit: () => void; @@ -40,7 +41,6 @@ interface ToolbarOssGraphProps { } function ToolbarOssGraph({ - isModified, onCreate, onDelete, onEdit, @@ -51,6 +51,8 @@ function ToolbarOssGraph({ onResetPositions }: ToolbarOssGraphProps) { const controller = useOssEdit(); + const { isModified } = useModificationStore(); + const isProcessing = useIsProcessingOss(); const selectedOperation = controller.schema?.operationByID.get(controller.selected[0]); const showGrid = useOSSGraphStore(state => state.showGrid); @@ -73,7 +75,7 @@ function ToolbarOssGraph({ return false; } - const argumentOperations = argumentIDs.map(id => controller.schema!.operationByID.get(id)!); + const argumentOperations = argumentIDs.map(id => controller.schema.operationByID.get(id)!); if (argumentOperations.some(item => item.result === null)) { return false; } @@ -144,35 +146,31 @@ function ToolbarOssGraph({ } - disabled={controller.isProcessing || !isModified} + disabled={isProcessing || !isModified} onClick={onSavePositions} /> } - disabled={controller.isProcessing} + disabled={isProcessing} onClick={onCreate} /> } - disabled={controller.isProcessing || controller.selected.length !== 1 || !readyForSynthesis} + disabled={isProcessing || controller.selected.length !== 1 || !readyForSynthesis} onClick={onExecute} /> } - disabled={controller.selected.length !== 1 || controller.isProcessing} + disabled={controller.selected.length !== 1 || isProcessing} onClick={onEdit} /> } - disabled={ - controller.selected.length !== 1 || - controller.isProcessing || - !controller.canDelete(controller.selected[0]) - } + disabled={controller.selected.length !== 1 || isProcessing || !controller.canDelete(controller.selected[0])} onClick={onDelete} />
    diff --git a/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx b/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx index d68a1db3..addc8b3c 100644 --- a/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx +++ b/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx @@ -3,6 +3,7 @@ import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { urls } from '@/app/urls'; import { useAuth } from '@/backend/auth/useAuth'; +import { useIsProcessingOss } from '@/backend/oss/useIsProcessingOss'; import { IconAdmin, IconAlert, @@ -25,18 +26,17 @@ import useDropdown from '@/hooks/useDropdown'; import { UserRole } from '@/models/user'; import { useRoleStore } from '@/stores/role'; import { describeAccessMode as describeUserRole, labelAccessMode as labelUserRole } from '@/utils/labels'; +import { sharePage } from '@/utils/utils'; import { useOssEdit } from './OssEditContext'; -interface MenuOssTabsProps { - onDestroy: () => void; -} - -function MenuOssTabs({ onDestroy }: MenuOssTabsProps) { +function MenuOssTabs() { const controller = useOssEdit(); const router = useConceptNavigation(); const { user } = useAuth(); + const isProcessing = useIsProcessingOss(); + const role = useRoleStore(state => state.role); const setRole = useRoleStore(state => state.setRole); @@ -46,12 +46,12 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) { function handleDelete() { schemaMenu.hide(); - onDestroy(); + controller.deleteSchema(); } function handleShare() { schemaMenu.hide(); - controller.share(); + sharePage(); } function handleChangeRole(newMode: UserRole) { @@ -96,7 +96,7 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) { } - disabled={controller.isProcessing || role < UserRole.OWNER} + disabled={isProcessing || role < UserRole.OWNER} onClick={handleDelete} /> ) : null} @@ -136,7 +136,7 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) { text='Конституенты' titleHtml='Перенос конституент
    между схемами' icon={} - disabled={controller.isProcessing} + disabled={isProcessing} onClick={handleRelocate} /> diff --git a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx index c3b8510d..49f3c343 100644 --- a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx +++ b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx @@ -6,24 +6,30 @@ import { toast } from 'react-toastify'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { urls } from '@/app/urls'; import { useAuth } from '@/backend/auth/useAuth'; -import { useLibrary } from '@/backend/library/useLibrary'; -import { useSetAccessPolicy } from '@/backend/library/useSetAccessPolicy'; -import { useSetEditors } from '@/backend/library/useSetEditors'; -import { useSetLocation } from '@/backend/library/useSetLocation'; -import { useSetOwner } from '@/backend/library/useSetOwner'; +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 { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library'; -import { Position2D } from '@/models/miscellaneous'; +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 { UserID, UserRole } from '@/models/user'; +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 { errors, information } from '@/utils/labels'; +import { information, prompts } from '@/utils/labels'; -import { RSTabID } from '../RSFormPage/RSTabs'; +import { RSTabID } from '../RSFormPage/RSEditContext'; + +export enum OssTabID { + CARD = 0, + GRAPH = 1 +} export interface ICreateOperationPrompt { defaultX: number; @@ -34,36 +40,27 @@ export interface ICreateOperationPrompt { } export interface IOssEditContext extends ILibraryItemEditor { - schema?: IOperationSchema; + schema: IOperationSchema; selected: OperationID[]; isOwned: boolean; isMutable: boolean; - isProcessing: boolean; isAttachedToOSS: boolean; showTooltip: boolean; setShowTooltip: (newValue: boolean) => void; - setOwner: (newOwner: UserID) => void; - setAccessPolicy: (newPolicy: AccessPolicy) => void; - promptEditors: () => void; - promptLocation: () => void; + navigateTab: (tab: OssTabID) => void; + navigateOperationSchema: (target: OperationID) => void; + deleteSchema: () => void; setSelected: React.Dispatch>; - share: () => void; - - openOperationSchema: (target: OperationID) => void; - - savePositions: (positions: IOperationPosition[], callback?: () => void) => void; - promptCreateOperation: (props: ICreateOperationPrompt) => void; canDelete: (target: OperationID) => boolean; + promptCreateOperation: (props: ICreateOperationPrompt) => void; promptDeleteOperation: (target: OperationID, positions: IOperationPosition[]) => void; - createInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void; - executeOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void; } @@ -78,325 +75,229 @@ export const useOssEdit = () => { interface OssEditStateProps { itemID: LibraryItemID; - selected: OperationID[]; - setSelected: React.Dispatch>; } -export const OssEditState = ({ - itemID, - selected, - setSelected, - children -}: React.PropsWithChildren) => { +export const OssEditState = ({ itemID, children }: React.PropsWithChildren) => { const router = useConceptNavigation(); const { user } = useAuth(); - const { items: libraryItems } = useLibrary(); const adminMode = usePreferencesStore(state => state.adminMode); const role = useRoleStore(state => state.role); const adjustRole = useRoleStore(state => state.adjustRole); - const model = useOssSuspense({ itemID: itemID }); + const { schema } = useOssSuspense({ itemID: itemID }); - const { setOwner: setItemOwner } = useSetOwner(); - const { setLocation: setItemLocation } = useSetLocation(); - const { setAccessPolicy: setItemAccessPolicy } = useSetAccessPolicy(); - const { setEditors: setItemEditors } = useSetEditors(); + const isOwned = user?.id === schema.owner || false; - const isOwned = user?.id === model.schema?.owner || false; - - const isMutable = role > UserRole.READER && !model.schema?.read_only; + const isMutable = role > UserRole.READER && !schema.read_only; const [showTooltip, setShowTooltip] = useState(true); - const [insertPosition, setInsertPosition] = useState({ x: 0, y: 0 }); - const [createCallback, setCreateCallback] = useState<((newID: OperationID) => void) | undefined>(undefined); + const [selected, setSelected] = useState([]); - const showEditEditors = useDialogsStore(state => state.showEditEditors); - const showEditLocation = useDialogsStore(state => state.showChangeLocation); 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 [positions, setPositions] = useState([]); + const { deleteItem } = useDeleteItem(); + const { updatePositions } = useUpdatePositions(); + const { operationCreate } = useOperationCreate(); + const { operationDelete } = useOperationDelete(); + const { operationUpdate } = useOperationUpdate(); + const { relocateConstituents } = useRelocateConstituents(); + const { inputUpdate } = useInputUpdate(); useEffect( () => adjustRole({ - isOwner: model.isOwned, - isEditor: (user && model.schema?.editors.includes(user?.id)) ?? false, + isOwner: isOwned, + isEditor: (user && schema.editors.includes(user?.id)) ?? false, isStaff: user?.is_staff ?? false, adminMode: adminMode }), - [model.schema, adjustRole, model.isOwned, user, adminMode] + [schema, adjustRole, isOwned, user, adminMode] ); - const handleSetLocation = (newLocation: string) => - setItemLocation({ itemID: model.itemID!, location: newLocation }, () => toast.success(information.moveComplete)); + function navigateTab(tab: OssTabID) { + if (!schema) { + return; + } + const url = urls.oss_props({ + id: schema.id, + tab: tab + }); + router.push(url); + } - const share = useCallback(() => { - const currentRef = window.location.href; - const url = currentRef.includes('?') ? currentRef + '&share' : currentRef + '?share'; - navigator.clipboard - .writeText(url) - .then(() => toast.success(information.linkReady)) - .catch(console.error); - }, []); - - const setOwner = (newOwner: UserID) => - setItemOwner({ itemID: model.itemID!, owner: newOwner }, () => toast.success(information.changesSaved)); - - const setAccessPolicy = (newPolicy: AccessPolicy) => - setItemAccessPolicy({ itemID: model.itemID!, policy: newPolicy }, () => toast.success(information.changesSaved)); - - const handleSetEditors = (newEditors: UserID[]) => - setItemEditors({ itemID: model.itemID!, editors: newEditors }, () => toast.success(information.changesSaved)); - - const openOperationSchema = useCallback( + const navigateOperationSchema = useCallback( (target: OperationID) => { - const node = model.schema?.operationByID.get(target); + const node = schema.operationByID.get(target); if (!node?.result) { return; } router.push(urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST })); }, - [router, model] + [router, schema] ); - const savePositions = useCallback( - (positions: IOperationPosition[], callback?: () => void) => { - model.savePositions({ positions: positions }, () => { - positions.forEach(item => { - const operation = model.schema?.operationByID.get(item.id); - if (operation) { - operation.position_x = item.position_x; - operation.position_y = item.position_y; + 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); } }); - toast.success(information.changesSaved); - callback?.(); - }); - }, - [model] - ); + }, + initialInputs: inputs + }); + } - const handleCreateOperation = useCallback( - (data: IOperationCreateData) => { - const target = calculateInsertPosition( - model.schema!, - data.item_data.operation_type, - data.arguments, - positions, - insertPosition - ); - data.positions = positions; - data.item_data.position_x = target.x; - data.item_data.position_y = target.y; - model.createOperation(data, operation => { - toast.success(information.newOperation(operation.alias)); - if (createCallback) { - setTimeout(() => createCallback(operation.id), PARAMETER.refreshTimeout); - } - }); - }, - [model, positions, insertPosition, createCallback] - ); + 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; + } - const handleEditOperation = useCallback( - (data: IOperationUpdateData) => { - data.positions = positions; - model.updateOperation(data, () => toast.success(information.changesSaved)); - }, - [model, positions] - ); - - const canDelete = useCallback( - (target: OperationID) => { - if (!model.schema) { - return false; + 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)); } - const operation = model.schema.operationByID.get(target); - if (!operation) { - return false; - } - if (operation.operation_type === OperationType.INPUT) { - return true; - } - return model.schema.graph.expandOutputs([target]).length === 0; - }, - [model] - ); + }); + } - const handleDeleteOperation = useCallback( - (targetID: OperationID, keepConstituents: boolean, deleteSchema: boolean) => { - const data: IOperationDeleteData = { - target: targetID, - positions: positions, - keep_constituents: keepConstituents, - delete_schema: deleteSchema - }; - model.deleteOperation(data, () => toast.success(information.operationDestroyed)); - }, - [model, positions] - ); - - const createInput = useCallback( - (target: OperationID, positions: IOperationPosition[]) => { - const operation = model.schema?.operationByID.get(target); - if (!model.schema || !operation) { - return; - } - if (libraryItems.find(item => item.alias === operation.alias && item.location === model.schema!.location)) { - toast.error(errors.inputAlreadyExists); - return; - } - model.createInput({ target: target, positions: positions }, new_schema => { - toast.success(information.newLibraryItem); - router.push(urls.schema(new_schema.id)); - }); - }, - [model, libraryItems, router] - ); - - const setTargetInput = useCallback( - (target: OperationID, newInput: LibraryItemID | undefined) => { - const data: IOperationSetInputData = { - target: target, - positions: positions, - input: newInput ?? null - }; - model.setInput(data, () => toast.success(information.changesSaved)); - }, - [model, positions] - ); - - const handleRelocateConstituents = useCallback( - (data: ICstRelocateData) => { - if ( - positions.every(item => { - const operation = model.schema!.operationByID.get(item.id)!; - return operation.position_x === item.position_x && operation.position_y === item.position_y; - }) - ) { - model.relocateConstituents(data, () => toast.success(information.changesSaved)); - } else { - model.savePositions({ positions: positions }, () => - model.relocateConstituents(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) ); } - }, - [model, positions] - ); + }); + } - const executeOperation = useCallback( - (target: OperationID, positions: IOperationPosition[]) => { - const data = { - target: target, - positions: positions - }; - model.executeOperation(data, () => toast.success(information.operationExecuted)); - }, - [model] - ); - - const promptEditors = () => showEditEditors({ editors: model.schema?.editors ?? [], setEditors: handleSetEditors }); - - const promptLocation = () => - showEditLocation({ initial: model.schema?.location ?? '', onChangeLocation: handleSetLocation }); - - const promptCreateOperation = useCallback( - ({ defaultX, defaultY, inputs, positions, callback }: ICreateOperationPrompt) => { - if (!model.schema) { - return; + 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) + ); } - setInsertPosition({ x: defaultX, y: defaultY }); - setPositions(positions); - setCreateCallback(() => callback); - showCreateOperation({ oss: model.schema, onCreate: handleCreateOperation, initialInputs: inputs }); - }, - [model.schema, showCreateOperation, handleCreateOperation] - ); + }); + } - const promptEditOperation = useCallback( - (target: OperationID, positions: IOperationPosition[]) => { - const operation = model.schema?.operationByID.get(target); - if (!model.schema || !operation) { - return; + 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)) + ); + } } - setPositions(positions); - showEditOperation({ oss: model.schema, target: operation, onSubmit: handleEditOperation }); - }, - [model.schema, showEditOperation, handleEditOperation] - ); - - const promptDeleteOperation = useCallback( - (target: OperationID, positions: IOperationPosition[]) => { - const operation = model.schema?.operationByID.get(target); - if (!model.schema || !operation) { - return; - } - setPositions(positions); - showDeleteOperation({ target: operation, onSubmit: handleDeleteOperation }); - }, - [model.schema, showDeleteOperation, handleDeleteOperation] - ); - - const promptEditInput = useCallback( - (target: OperationID, positions: IOperationPosition[]) => { - const operation = model.schema?.operationByID.get(target); - if (!model.schema || !operation) { - return; - } - setPositions(positions); - showEditInput({ oss: model.schema, target: operation, onSubmit: setTargetInput }); - }, - [model.schema, showEditInput, setTargetInput] - ); - - const promptRelocateConstituents = useCallback( - (target: OperationID | undefined, positions: IOperationPosition[]) => { - if (!model.schema) { - return; - } - const operation = target ? model.schema?.operationByID.get(target) : undefined; - setPositions(positions); - showRelocateConstituents({ oss: model.schema, initialTarget: operation, onSubmit: handleRelocateConstituents }); - }, - [model.schema, showRelocateConstituents, handleRelocateConstituents] - ); + }); + } return ( diff --git a/rsconcept/frontend/src/pages/OssPage/OssPage.tsx b/rsconcept/frontend/src/pages/OssPage/OssPage.tsx index 7375cdda..999a70e8 100644 --- a/rsconcept/frontend/src/pages/OssPage/OssPage.tsx +++ b/rsconcept/frontend/src/pages/OssPage/OssPage.tsx @@ -1,23 +1,69 @@ 'use client'; -import { Suspense } from 'react'; +import axios from 'axios'; +import { ErrorBoundary } from 'react-error-boundary'; import { useParams } from 'react-router'; -import Loader from '@/components/ui/Loader'; -import { OssState } from '@/pages/OssPage/OssContext'; +import { useBlockNavigation, useConceptNavigation } from '@/app/Navigation/NavigationContext'; +import { urls } from '@/app/urls'; +import InfoError, { ErrorData } from '@/components/info/InfoError'; +import TextURL from '@/components/ui/TextURL'; +import { useModificationStore } from '@/stores/modification'; +import { OssEditState } from './OssEditContext'; import OssTabs from './OssTabs'; function OssPage() { + const router = useConceptNavigation(); const params = useParams(); const itemID = params.id ? Number(params.id) : undefined; + + const { isModified } = useModificationStore(); + useBlockNavigation(isModified); + + // useBlockNavigation( + // isModified && + // schema !== undefined && + // !!user && + // (user.is_staff || user.id == schema.owner || schema.editors.includes(user.id)) + // ); + + if (!itemID) { + router.replace(urls.page404); + return null; + } + return ( - }> - + + - - + + ); } export default OssPage; + +// ====== Internals ========= +function ProcessError({ error }: { error: ErrorData }): React.ReactElement { + if (axios.isAxiosError(error) && error.response) { + if (error.response.status === 404) { + return ( +
    +

    {`Операционная схема с указанным идентификатором отсутствует`}

    +
    + +
    +
    + ); + } else if (error.response.status === 403) { + return ( +
    +

    Владелец ограничил доступ к данной схеме

    + +
    + ); + } + } + return ; +} diff --git a/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx b/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx index 5f352cae..e0bbc9c6 100644 --- a/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx +++ b/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx @@ -1,79 +1,46 @@ 'use client'; -import axios from 'axios'; import clsx from 'clsx'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { TabList, TabPanel, Tabs } from 'react-tabs'; -import { toast } from 'react-toastify'; -import { useBlockNavigation, useConceptNavigation } from '@/app/Navigation/NavigationContext'; -import { urls } from '@/app/urls'; -import { useAuth } from '@/backend/auth/useAuth'; -import { useDeleteItem } from '@/backend/library/useDeleteItem'; -import InfoError, { ErrorData } from '@/components/info/InfoError'; -import Loader from '@/components/ui/Loader'; +import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import Overlay from '@/components/ui/Overlay'; import TabLabel from '@/components/ui/TabLabel'; -import TextURL from '@/components/ui/TextURL'; import useQueryStrings from '@/hooks/useQueryStrings'; -import { OperationID } from '@/models/oss'; import { useAppLayoutStore } from '@/stores/appLayout'; -import { information, prompts } from '@/utils/labels'; +import { useModificationStore } from '@/stores/modification'; import EditorRSForm from './EditorOssCard'; import EditorTermGraph from './EditorOssGraph'; import MenuOssTabs from './MenuOssTabs'; -import { OssEditState } from './OssEditContext'; - -export enum OssTabID { - CARD = 0, - GRAPH = 1 -} +import { OssTabID, useOssEdit } from './OssEditContext'; function OssTabs() { const router = useConceptNavigation(); const query = useQueryStrings(); const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH; - const { user } = useAuth(); - const { deleteItem } = useDeleteItem(); + + const { schema, navigateTab } = useOssEdit(); const hideFooter = useAppLayoutStore(state => state.hideFooter); - const { schema, loading, loadingError: errorLoading } = useOSSControl(); - const [isModified, setIsModified] = useState(false); - const [selected, setSelected] = useState([]); - useBlockNavigation( - isModified && - schema !== undefined && - !!user && - (user.is_staff || user.id == schema.owner || schema.editors.includes(user.id)) - ); + const { setIsModified } = useModificationStore(); + + useEffect(() => setIsModified(false), [setIsModified]); useEffect(() => { - if (schema) { - const oldTitle = document.title; - document.title = schema.title; - return () => { - document.title = oldTitle; - }; - } - }, [schema, schema?.title]); + const oldTitle = document.title; + document.title = schema.title; + return () => { + document.title = oldTitle; + }; + }, [schema.title]); useEffect(() => { hideFooter(activeTab === OssTabID.GRAPH); }, [activeTab, hideFooter]); - function navigateTab(tab: OssTabID) { - if (!schema) { - return; - } - const url = urls.oss_props({ - id: schema.id, - tab: tab - }); - router.push(url); - } - function onSelectTab(index: number, last: number, event: Event) { if (last === index) { return; @@ -93,78 +60,34 @@ function OssTabs() { navigateTab(index); } - function onDestroySchema() { - if (!schema || !window.confirm(prompts.deleteOSS)) { - return; - } - deleteItem(schema.id, () => { - toast.success(information.itemDestroyed); - router.push(urls.library); - }); - } - return ( - - {loading ? : null} - {errorLoading ? : null} - {schema && !loading ? ( - - - - + + + + - - - - + + + + -
    - - - +
    + + + - - - -
    - - ) : null} - + + + +
    +
    ); } export default OssTabs; - -// ====== Internals ========= -function ProcessError({ error }: { error: ErrorData }): React.ReactElement { - if (axios.isAxiosError(error) && error.response) { - if (error.response.status === 404) { - return ( -
    -

    {`Операционная схема с указанным идентификатором отсутствует`}

    -
    - -
    -
    - ); - } else if (error.response.status === 403) { - return ( -
    -

    Владелец ограничил доступ к данной схеме

    - -
    - ); - } - } - return ; -} diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ControlsOverlay.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ControlsOverlay.tsx deleted file mode 100644 index 50d94c71..00000000 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ControlsOverlay.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import clsx from 'clsx'; - -import { IconEdit } from '@/components/Icons'; -import MiniButton from '@/components/ui/MiniButton'; -import Overlay from '@/components/ui/Overlay'; -import { IConstituenta } from '@/models/rsform'; -import { tooltips } from '@/utils/labels'; - -interface ControlsOverlayProps { - constituenta: IConstituenta; - disabled: boolean; - modified: boolean; - processing: boolean; - - onRename: () => void; - onEditTerm: () => void; -} - -function ControlsOverlay({ constituenta, disabled, modified, processing, onRename, onEditTerm }: ControlsOverlayProps) { - return ( - - {!disabled || processing ? ( - } - disabled={modified} - /> - ) : null} -
    - Имя - {constituenta?.alias ?? ''} -
    - {!disabled || processing ? ( - } - disabled={modified} - /> - ) : null} -
    - ); -} - -export default ControlsOverlay; diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx index f16e4b61..586e285f 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx @@ -2,12 +2,18 @@ import clsx from 'clsx'; import { useState } from 'react'; +import { toast } from 'react-toastify'; +import { useCstUpdate } from '@/backend/rsform/useCstUpdate'; +import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm'; import useWindowSize from '@/hooks/useWindowSize'; -import { ConstituentaID, IConstituenta } from '@/models/rsform'; import { useMainHeight } from '@/stores/appLayout'; +import { useDialogsStore } from '@/stores/dialogs'; +import { useModificationStore } from '@/stores/modification'; import { usePreferencesStore } from '@/stores/preferences'; import { globals } from '@/utils/constants'; +import { information } from '@/utils/labels'; +import { promptUnsaved } from '@/utils/utils'; import { useRSEdit } from '../RSEditContext'; import ViewConstituents from '../ViewConstituents'; @@ -17,23 +23,20 @@ import ToolbarConstituenta from './ToolbarConstituenta'; // Threshold window width to switch layout. const SIDELIST_LAYOUT_THRESHOLD = 1000; // px -interface EditorConstituentaProps { - activeCst?: IConstituenta; - isModified: boolean; - setIsModified: (newValue: boolean) => void; - onOpenEdit: (cstID: ConstituentaID) => void; -} - -function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) { +function EditorConstituenta() { const controller = useRSEdit(); const windowSize = useWindowSize(); const mainHeight = useMainHeight(); const showList = usePreferencesStore(state => state.showCstSideList); + const showEditTerm = useDialogsStore(state => state.showEditWordForms); + const { cstUpdate } = useCstUpdate(); + const { isModified } = useModificationStore(); const [toggleReset, setToggleReset] = useState(false); - const disabled = !activeCst || !controller.isContentEditable || controller.isProcessing; + const isProcessing = useIsProcessingRSForm(); + const disabled = !controller.activeCst || !controller.isContentEditable || isProcessing; const isNarrow = !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD; function handleInput(event: React.KeyboardEvent) { @@ -56,6 +59,29 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit } } } + function handleEditTermForms() { + if (!controller.activeCst) { + return; + } + if (isModified && !promptUnsaved()) { + return; + } + showEditTerm({ + target: controller.activeCst, + onSave: forms => + cstUpdate( + { + itemID: controller.schema.id, + data: { + target: controller.activeCst!.id, + item_data: { term_forms: forms } + } + }, + () => toast.success(information.changesSaved) + ) + }); + } + function initiateSubmit() { const element = document.getElementById(globals.constituenta_editor) as HTMLFormElement; if (element) { @@ -76,9 +102,8 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit } return ( <> setToggleReset(prev => !prev)} /> @@ -97,21 +122,13 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
    diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorControls.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorControls.tsx new file mode 100644 index 00000000..98e9a207 --- /dev/null +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorControls.tsx @@ -0,0 +1,84 @@ +import clsx from 'clsx'; +import { toast } from 'react-toastify'; + +import { ICstRenameDTO } from '@/backend/rsform/api'; +import { useCstRename } from '@/backend/rsform/useCstRename'; +import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm'; +import { IconEdit } from '@/components/Icons'; +import MiniButton from '@/components/ui/MiniButton'; +import Overlay from '@/components/ui/Overlay'; +import { IConstituenta } from '@/models/rsform'; +import { useDialogsStore } from '@/stores/dialogs'; +import { useModificationStore } from '@/stores/modification'; +import { information, tooltips } from '@/utils/labels'; + +import { useRSEdit } from '../RSEditContext'; + +interface EditorControlsProps { + constituenta: IConstituenta; + disabled: boolean; + + onEditTerm: () => void; +} + +function EditorControls({ constituenta, disabled, onEditTerm }: EditorControlsProps) { + const { schema } = useRSEdit(); + const { isModified } = useModificationStore(); + const isProcessing = useIsProcessingRSForm(); + + const showRenameCst = useDialogsStore(state => state.showRenameCst); + const { cstRename } = useCstRename(); + + function handleRenameCst() { + const initialData: ICstRenameDTO = { + target: constituenta.id, + alias: constituenta.alias, + cst_type: constituenta.cst_type + }; + showRenameCst({ + schema: schema, + initial: initialData, + allowChangeType: !constituenta.is_inherited, + onRename: data => { + const oldAlias = initialData.alias; + cstRename({ itemID: schema.id, data }, () => toast.success(information.renameComplete(oldAlias, data.alias))); + } + }); + } + + return ( + + {!disabled || isProcessing ? ( + } + disabled={isModified} + /> + ) : null} +
    + Имя + {constituenta?.alias ?? ''} +
    + {!disabled || isProcessing ? ( + } + disabled={isModified} + /> + ) : null} +
    + ); +} + +export default EditorControls; diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx index ed25b866..7c07e033 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx @@ -6,6 +6,7 @@ import { toast } from 'react-toastify'; import { ICstUpdateDTO } from '@/backend/rsform/api'; import { useCstUpdate } from '@/backend/rsform/useCstUpdate'; +import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm'; import { IconChild, IconPredecessor, IconSave } from '@/components/Icons'; import { CProps } from '@/components/props'; import RefsInput from '@/components/RefsInput'; @@ -13,27 +14,22 @@ import Indicator from '@/components/ui/Indicator'; import Overlay from '@/components/ui/Overlay'; import SubmitButton from '@/components/ui/SubmitButton'; import TextArea from '@/components/ui/TextArea'; -import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform'; +import { ConstituentaID, CstType } from '@/models/rsform'; import { isBaseSet, isBasicConcept, isFunctional } from '@/models/rsformAPI'; import { IExpressionParse, ParsingStatus } from '@/models/rslang'; import { useDialogsStore } from '@/stores/dialogs'; +import { useModificationStore } from '@/stores/modification'; import { errors, information, labelCstTypification } from '@/utils/labels'; import EditorRSExpression from '../EditorRSExpression'; import { useRSEdit } from '../RSEditContext'; -import ControlsOverlay from './ControlsOverlay'; +import EditorControls from './EditorControls'; interface FormConstituentaProps { - disabled: boolean; - id?: string; - state?: IConstituenta; - - isModified: boolean; + disabled: boolean; toggleReset: boolean; - setIsModified: (newValue: boolean) => void; - onRename: () => void; onEditTerm: () => void; onOpenEdit?: (cstID: ConstituentaID) => void; } @@ -41,72 +37,68 @@ interface FormConstituentaProps { function FormConstituenta({ disabled, id, - state, - - isModified, - setIsModified, toggleReset, - onRename, onEditTerm, onOpenEdit }: FormConstituentaProps) { const { cstUpdate } = useCstUpdate(); - const controller = useRSEdit(); - const schema = controller.schema; + const { schema, activeCst } = useRSEdit(); + const { isModified, setIsModified } = useModificationStore(); + const isProcessing = useIsProcessingRSForm(); - const [term, setTerm] = useState(state?.term_raw ?? ''); - const [textDefinition, setTextDefinition] = useState(state?.definition_raw ?? ''); - const [expression, setExpression] = useState(state?.definition_formal ?? ''); - const [convention, setConvention] = useState(state?.convention ?? ''); + const [term, setTerm] = useState(activeCst?.term_raw ?? ''); + const [textDefinition, setTextDefinition] = useState(activeCst?.definition_raw ?? ''); + const [expression, setExpression] = useState(activeCst?.definition_formal ?? ''); + const [convention, setConvention] = useState(activeCst?.convention ?? ''); const [typification, setTypification] = useState('N/A'); const [localParse, setLocalParse] = useState(undefined); - const typeInfo = state + const typeInfo = activeCst ? { - alias: state.alias, - result: localParse ? localParse.typification : state.parse.typification, - args: localParse ? localParse.args : state.parse.args + alias: activeCst.alias, + result: localParse ? localParse.typification : activeCst.parse.typification, + args: localParse ? localParse.args : activeCst.parse.args } : undefined; const [forceComment, setForceComment] = useState(false); - const isBasic = !!state && isBasicConcept(state.cst_type); - const isElementary = !!state && isBaseSet(state.cst_type); - const showConvention = !state || !!state.convention || forceComment || isBasic; + const isBasic = !!activeCst && isBasicConcept(activeCst.cst_type); + const isElementary = !!activeCst && isBaseSet(activeCst.cst_type); + const showConvention = !activeCst || !!activeCst.convention || forceComment || isBasic; - const showTypification = useDialogsStore(state => state.showShowTypeGraph); + const showTypification = useDialogsStore(activeCst => activeCst.showShowTypeGraph); useEffect(() => { - if (state) { - setConvention(state.convention); - setTerm(state.term_raw); - setTextDefinition(state.definition_raw); - setExpression(state.definition_formal); - setTypification(state ? labelCstTypification(state) : 'N/A'); + if (activeCst) { + setConvention(activeCst.convention); + setTerm(activeCst.term_raw); + setTextDefinition(activeCst.definition_raw); + setExpression(activeCst.definition_formal); + setTypification(activeCst ? labelCstTypification(activeCst) : 'N/A'); setForceComment(false); setLocalParse(undefined); } - }, [state, schema, toggleReset, setIsModified]); + }, [activeCst, schema, toggleReset, setIsModified]); useLayoutEffect(() => { - if (!state) { + if (!activeCst) { setIsModified(false); return; } setIsModified( - state.term_raw !== term || - state.definition_raw !== textDefinition || - state.convention !== convention || - state.definition_formal !== expression + activeCst.term_raw !== term || + activeCst.definition_raw !== textDefinition || + activeCst.convention !== convention || + activeCst.definition_formal !== expression ); return () => setIsModified(false); }, [ - state, - state?.term_raw, - state?.definition_formal, - state?.definition_raw, - state?.convention, + activeCst, + activeCst?.term_raw, + activeCst?.definition_formal, + activeCst?.definition_raw, + activeCst?.convention, term, textDefinition, expression, @@ -118,23 +110,23 @@ function FormConstituenta({ if (event) { event.preventDefault(); } - if (!state || controller.isProcessing || !schema) { + if (!activeCst || isProcessing || !schema) { return; } const data: ICstUpdateDTO = { - target: state.id, + target: activeCst.id, item_data: { - term_raw: state.term_raw !== term ? term : undefined, - definition_formal: state.definition_formal !== expression ? expression : undefined, - definition_raw: state.definition_raw !== textDefinition ? textDefinition : undefined, - convention: state.convention !== convention ? convention : undefined + term_raw: activeCst.term_raw !== term ? term : undefined, + definition_formal: activeCst.definition_formal !== expression ? expression : undefined, + definition_raw: activeCst.definition_raw !== textDefinition ? textDefinition : undefined, + convention: activeCst.convention !== convention ? convention : undefined } }; cstUpdate({ itemID: schema.id, data }, () => toast.success(information.changesSaved)); } function handleTypeGraph(event: CProps.EventMouse) { - if (!state || (localParse && !localParse.parseResult) || state.parse.status !== ParsingStatus.VERIFIED) { + if (!activeCst || (localParse && !localParse.parseResult) || activeCst.parse.status !== ParsingStatus.VERIFIED) { toast.error(errors.typeStructureFailed); return; } @@ -145,16 +137,7 @@ function FormConstituenta({ return (
    - {state ? ( - - ) : null} + {activeCst ? : null}
    setTerm(newValue)} /> - {state ? ( + {activeCst ? (