From 8e474160d5806f6b0ba7ebfe347d4892863e83b8 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:54:40 +0300 Subject: [PATCH] F: Add side panel for schema preview pt1 --- .../frontend/src/app/global-tooltips.tsx | 5 +- .../features/help/items/ui/help-oss-graph.tsx | 12 +- .../library/components/icon-show-sidebar.tsx | 4 +- .../oss-page/editor-oss-graph/oss-flow.tsx | 13 ++ .../editor-oss-graph/side-panel/index.tsx | 1 + .../side-panel/side-panel.tsx | 72 +++++++ .../side-panel/toolbar-constituents.tsx | 195 ++++++++++++++++++ .../side-panel/view-schema.tsx | 45 ++++ .../editor-oss-graph/toolbar-oss-graph.tsx | 13 +- .../dialogs/dlg-delete-cst/dlg-delete-cst.tsx | 8 +- .../dlg-delete-cst/list-constituents.tsx | 2 +- rsconcept/frontend/src/stores/preferences.ts | 8 +- rsconcept/frontend/src/styling/utilities.css | 6 + 13 files changed, 367 insertions(+), 17 deletions(-) create mode 100644 rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/index.tsx create mode 100644 rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx create mode 100644 rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-constituents.tsx create mode 100644 rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx diff --git a/rsconcept/frontend/src/app/global-tooltips.tsx b/rsconcept/frontend/src/app/global-tooltips.tsx index ddea32d8..c5674e9b 100644 --- a/rsconcept/frontend/src/app/global-tooltips.tsx +++ b/rsconcept/frontend/src/app/global-tooltips.tsx @@ -10,8 +10,9 @@ export const GlobalTooltips = () => { float id={globalIDs.tooltip} layer='z-topmost' - place='right-start' - className='mt-8 max-w-80 break-words rounded-lg! select-none' + place='bottom-start' + offset={24} + className='max-w-80 break-words rounded-lg! select-none' />

Граф синтеза

-
+

Настройка графа

  • @@ -39,6 +40,9 @@ export function HelpOssGraph() {
  • Вписать в экран
  • +
  • + Панель связанной КС +
  • Диалог настроек
  • @@ -66,7 +70,7 @@ export function HelpOssGraph() { -
    +

    Изменение узлов

    • @@ -97,7 +101,7 @@ export function HelpOssGraph() {
      -
      +

      Общие

      • @@ -114,7 +118,7 @@ export function HelpOssGraph() { -
        +

        Контекстное меню

        • diff --git a/rsconcept/frontend/src/features/library/components/icon-show-sidebar.tsx b/rsconcept/frontend/src/features/library/components/icon-show-sidebar.tsx index 9ea9351a..686ab817 100644 --- a/rsconcept/frontend/src/features/library/components/icon-show-sidebar.tsx +++ b/rsconcept/frontend/src/features/library/components/icon-show-sidebar.tsx @@ -15,9 +15,9 @@ export function IconShowSidebar({ } } else { if (value) { - return ; - } else { return ; + } else { + return ; } } } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx index aad18599..e74c8b68 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow'; import { useMainHeight } from '@/stores/app-layout'; import { useDialogsStore } from '@/stores/dialogs'; +import { usePreferencesStore } from '@/stores/preferences'; import { PARAMETER } from '@/utils/constants'; import { promptText } from '@/utils/labels'; @@ -23,6 +24,7 @@ import { useContextMenu } from './context-menu/use-context-menu'; import { OssNodeTypes } from './graph/oss-node-types'; import { CoordinateDisplay } from './coordinate-display'; import { useOssFlow } from './oss-flow-context'; +import { SidePanel } from './side-panel'; import { ToolbarOssGraph } from './toolbar-oss-graph'; import { useDragging } from './use-dragging'; import { useGetLayout } from './use-get-layout'; @@ -52,6 +54,7 @@ export function OssFlow() { const showGrid = useOSSGraphStore(state => state.showGrid); const showCoordinates = useOSSGraphStore(state => state.showCoordinates); + const showPanel = usePreferencesStore(state => state.showOssSidePanel); const getLayout = useGetLayout(); const { updateLayout } = useUpdateLayout(); @@ -225,6 +228,16 @@ export function OssFlow() { onNodeDrag={handleDrag} onNodeDragStop={handleDragStop} /> + +
        ); } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/index.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/index.tsx new file mode 100644 index 00000000..5c3fc9e1 --- /dev/null +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/index.tsx @@ -0,0 +1 @@ +export { SidePanel } from './side-panel'; diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx new file mode 100644 index 00000000..194927a1 --- /dev/null +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx @@ -0,0 +1,72 @@ +import { Suspense } from 'react'; +import clsx from 'clsx'; +import { useDebounce } from 'use-debounce'; + +import { MiniButton } from '@/components/control'; +import { IconClose } from '@/components/icons'; +import { Loader } from '@/components/loader'; +import { cn } from '@/components/utils'; +import { useMainHeight } from '@/stores/app-layout'; +import { usePreferencesStore } from '@/stores/preferences'; +import { PARAMETER } from '@/utils/constants'; + +import { type IOssItem, NodeType } from '../../../../models/oss'; + +import { ViewSchema } from './view-schema'; + +interface SidePanelProps { + selectedItems: IOssItem[]; + className?: string; + isMounted: boolean; +} + +export function SidePanel({ selectedItems, isMounted, className }: SidePanelProps) { + const selectedOperation = + selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.OPERATION ? selectedItems[0] : null; + const selectedSchema = selectedOperation?.result ?? null; + + const debouncedMounted = useDebounce(isMounted, PARAMETER.moveDuration); + const closePanel = usePreferencesStore(state => state.toggleShowOssSidePanel); + const sidePanelHeight = useMainHeight(); + + return ( +
        + } + className='absolute z-pop top-2 right-1' + onClick={closePanel} + /> + +
        + Содержание КС +
        + + {!selectedOperation ? ( +
        Выделите операцию для просмотра
        + ) : !selectedSchema ? ( +
        Отсутствует концептуальная схема для выбранной операции
        + ) : debouncedMounted ? ( + }> + + + ) : null} +
        + ); +} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-constituents.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-constituents.tsx new file mode 100644 index 00000000..b0156bca --- /dev/null +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-constituents.tsx @@ -0,0 +1,195 @@ +import { urls, useConceptNavigation } from '@/app'; +import { type IConstituenta, type IRSForm } from '@/features/rsform'; +import { CstType, type IConstituentaBasicsDTO, type ICreateConstituentaDTO } from '@/features/rsform/backend/types'; +import { useCreateConstituenta } from '@/features/rsform/backend/use-create-constituenta'; +import { useMoveConstituents } from '@/features/rsform/backend/use-move-constituents'; +import { useMutatingRSForm } from '@/features/rsform/backend/use-mutating-rsform'; +import { generateAlias } from '@/features/rsform/models/rsform-api'; +import { useCstSearchStore } from '@/features/rsform/stores/cst-search'; + +import { MiniButton } from '@/components/control'; +import { IconClone, IconDestroy, IconMoveDown, IconMoveUp, IconNewItem, IconRSForm } from '@/components/icons'; +import { cn } from '@/components/utils'; +import { useDialogsStore } from '@/stores/dialogs'; +import { PARAMETER, prefixes } from '@/utils/constants'; +import { type RO } from '@/utils/meta'; + +interface ToolbarConstituentsProps { + schema: IRSForm; + activeCst: IConstituenta | null; + setActive: (cstID: number) => void; + resetActive: () => void; + className?: string; +} + +export function ToolbarConstituents({ + schema, + activeCst, + setActive, + resetActive, + className +}: ToolbarConstituentsProps) { + const router = useConceptNavigation(); + const isProcessing = useMutatingRSForm(); + const searchText = useCstSearchStore(state => state.query); + const hasSearch = searchText.length > 0; + + const showCreateCst = useDialogsStore(state => state.showCreateCst); + const showDeleteCst = useDialogsStore(state => state.showDeleteCst); + const { moveConstituents } = useMoveConstituents(); + const { createConstituenta } = useCreateConstituenta(); + + function navigateRSForm() { + router.push({ path: urls.schema(schema.id) }); + } + + function onCreateCst(newCst: RO) { + setActive(newCst.id); + setTimeout(() => { + const element = document.getElementById(`${prefixes.cst_list}${newCst.id}`); + if (element) { + element.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'end' + }); + } + }, PARAMETER.refreshTimeout); + } + + function createCst() { + const targetType = activeCst?.cst_type ?? CstType.BASE; + const data: ICreateConstituentaDTO = { + insert_after: activeCst?.id ?? null, + cst_type: targetType, + alias: generateAlias(targetType, schema), + term_raw: '', + definition_formal: '', + definition_raw: '', + convention: '', + term_forms: [] + }; + showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data }); + } + + function cloneCst() { + if (!activeCst) { + return; + } + void createConstituenta({ + itemID: schema.id, + data: { + insert_after: activeCst.id, + cst_type: activeCst.cst_type, + alias: generateAlias(activeCst.cst_type, schema), + term_raw: activeCst.term_raw, + definition_formal: activeCst.definition_formal, + definition_raw: activeCst.definition_raw, + convention: activeCst.convention, + term_forms: activeCst.term_forms + } + }).then(onCreateCst); + } + + function promptDeleteCst() { + if (!activeCst) { + return; + } + showDeleteCst({ + schema: schema, + selected: [activeCst.id], + afterDelete: resetActive + }); + } + + function moveUp() { + if (!activeCst) { + return; + } + const currentIndex = schema.items.reduce((prev, cst, index) => { + if (activeCst.id !== cst.id) { + return prev; + } else if (prev === -1) { + return index; + } + return Math.min(prev, index); + }, -1); + const target = Math.max(0, currentIndex - 1); + void moveConstituents({ + itemID: schema.id, + data: { + items: [activeCst.id], + move_to: target + } + }); + } + + function moveDown() { + if (!activeCst) { + return; + } + let count = 0; + const currentIndex = schema.items.reduce((prev, cst, index) => { + if (activeCst.id !== cst.id) { + return prev; + } else { + count += 1; + if (prev === -1) { + return index; + } + return Math.max(prev, index); + } + }, -1); + const target = Math.min(schema.items.length - 1, currentIndex - count + 2); + void moveConstituents({ + itemID: schema.id, + data: { + items: [activeCst.id], + move_to: target + } + }); + } + + return ( +
        + } + onClick={navigateRSForm} + /> + + } + onClick={createCst} + disabled={isProcessing} + /> + } + onClick={cloneCst} + disabled={!activeCst || isProcessing} + /> + + } + disabled={!activeCst || isProcessing || activeCst?.is_inherited} + /> + + } + onClick={moveUp} + disabled={!activeCst || isProcessing || schema.items.length < 2 || hasSearch} + /> + } + onClick={moveDown} + disabled={!activeCst || isProcessing || schema.items.length < 2 || hasSearch} + /> +
        + ); +} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx new file mode 100644 index 00000000..89b4964f --- /dev/null +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; + +import { useRSFormSuspense } from '@/features/rsform/backend/use-rsform'; +import { RSFormStats } from '@/features/rsform/components/rsform-stats'; +import { ViewConstituents } from '@/features/rsform/components/view-constituents'; + +import { useFitHeight } from '@/stores/app-layout'; + +import { ToolbarConstituents } from './toolbar-constituents'; + +interface ViewSchemaProps { + schemaID: number; +} + +export function ViewSchema({ schemaID }: ViewSchemaProps) { + const { schema } = useRSFormSuspense({ itemID: schemaID }); + const [activeID, setActiveID] = useState(null); + const activeCst = activeID ? schema.cstByID.get(activeID) ?? null : null; + + const listHeight = useFitHeight('14.5rem', '10rem'); + + return ( +
        + setActiveID(null)} + /> + + setActiveID(cst.id)} + maxListHeight={listHeight} + /> + + +
        + ); +} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx index a575bf5c..a4456e93 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { HelpTopic } from '@/features/help'; import { BadgeHelp } from '@/features/help/components/badge-help'; +import { IconShowSidebar } from '@/features/library/components/icon-show-sidebar'; import { type OssNode } from '@/features/oss/models/oss-layout'; import { MiniButton } from '@/components/control'; @@ -20,6 +21,7 @@ import { import { type Styling } from '@/components/props'; import { cn } from '@/components/utils'; import { useDialogsStore } from '@/stores/dialogs'; +import { usePreferencesStore } from '@/stores/preferences'; import { isIOS, prepareTooltip } from '@/utils/utils'; import { useMutatingOss } from '../../../backend/use-mutating-oss'; @@ -64,10 +66,12 @@ export function ToolbarOssGraph({ const { updateLayout } = useUpdateLayout(); - const showOssOptions = useDialogsStore(state => state.showOssOptions); + const showOptions = useDialogsStore(state => state.showOssOptions); + const showSidePanel = usePreferencesStore(state => state.showOssSidePanel); + const toggleShowSidePanel = usePreferencesStore(state => state.toggleShowOssSidePanel); function handleShowOptions() { - showOssOptions(); + showOptions(); } function handleSavePositions() { @@ -110,6 +114,11 @@ export function ToolbarOssGraph({ icon={} onClick={resetView} /> + } + onClick={toggleShowSidePanel} + /> } diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/dlg-delete-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/dlg-delete-cst.tsx index 60220db8..12399267 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/dlg-delete-cst.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/dlg-delete-cst.tsx @@ -15,7 +15,7 @@ import { ListConstituents } from './list-constituents'; export interface DlgDeleteCstProps { schema: IRSForm; selected: number[]; - afterDelete: (initialSchema: IRSForm, deleted: number[]) => void; + afterDelete?: (initialSchema: IRSForm, deleted: number[]) => void; } export function DlgDeleteCst() { @@ -31,7 +31,7 @@ export function DlgDeleteCst() { function handleSubmit() { const deleted = expandOut ? selected.concat(expansion) : selected; - void cstDelete({ itemID: schema.id, data: { items: deleted } }).then(() => afterDelete(schema, deleted)); + void cstDelete({ itemID: schema.id, data: { items: deleted } }).then(() => afterDelete?.(schema, deleted)); } return ( @@ -54,9 +54,7 @@ export function DlgDeleteCst() { value={expandOut} onChange={value => setExpandOut(value)} /> - {hasInherited ? ( -

        Внимание! Выбранные конституенты имеют наследников в ОСС

        - ) : null} + {hasInherited ?

        Внимание! Конституенты имеют наследников в ОСС

        : null} ); } diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/list-constituents.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/list-constituents.tsx index 44bbcca5..9c015962 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/list-constituents.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-delete-cst/list-constituents.tsx @@ -12,7 +12,7 @@ export function ListConstituents({ list, schema, title, prefix }: ListConstituen return (
        {title ? ( -

        +

        {title}: {list.length}

        ) : null} diff --git a/rsconcept/frontend/src/stores/preferences.ts b/rsconcept/frontend/src/stores/preferences.ts index 37e84053..6ef844d7 100644 --- a/rsconcept/frontend/src/stores/preferences.ts +++ b/rsconcept/frontend/src/stores/preferences.ts @@ -28,6 +28,9 @@ interface PreferencesStore { showExpressionControls: boolean; toggleShowExpressionControls: () => void; + + showOssSidePanel: boolean; + toggleShowOssSidePanel: () => void; } export const usePreferencesStore = create()( @@ -76,7 +79,10 @@ export const usePreferencesStore = create()( toggleShowOSSStats: () => set(state => ({ showOSSStats: !state.showOSSStats })), showExpressionControls: true, - toggleShowExpressionControls: () => set(state => ({ showExpressionControls: !state.showExpressionControls })) + toggleShowExpressionControls: () => set(state => ({ showExpressionControls: !state.showExpressionControls })), + + showOssSidePanel: false, + toggleShowOssSidePanel: () => set(state => ({ showOssSidePanel: !state.showOssSidePanel })) }), { version: 1, diff --git a/rsconcept/frontend/src/styling/utilities.css b/rsconcept/frontend/src/styling/utilities.css index 6abc7941..7805bb9b 100644 --- a/rsconcept/frontend/src/styling/utilities.css +++ b/rsconcept/frontend/src/styling/utilities.css @@ -148,6 +148,12 @@ transition-duration: var(--duration-transform); } +@utility cc-animate-panel { + transition-property: translate, opacity; + transition-timing-function: var(--ease-bezier); + transition-duration: var(--duration-transform); +} + @utility cc-animate-position { transition-property: transform top left bottom right margin padding; transition-timing-function: var(--ease-bezier);