diff --git a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx index f32af26a..6a684a8e 100644 --- a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx +++ b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx @@ -137,9 +137,13 @@ const RefsInput = forwardRef( (event: React.KeyboardEvent) => { if (!thisRef.current?.view) { event.preventDefault(); + event.stopPropagation(); return; } if ((event.ctrlKey || event.metaKey) && event.code === 'Space') { + event.preventDefault(); + event.stopPropagation(); + const wrap = new CodeMirrorWrapper(thisRef.current as Required); wrap.fixSelection(ReferenceTokens); const nodes = wrap.getEnvelopingNodes(ReferenceTokens); diff --git a/rsconcept/frontend/src/components/select/GraphSelectionToolbar.tsx b/rsconcept/frontend/src/components/select/GraphSelectionToolbar.tsx index bc69b096..ff8a399a 100644 --- a/rsconcept/frontend/src/components/select/GraphSelectionToolbar.tsx +++ b/rsconcept/frontend/src/components/select/GraphSelectionToolbar.tsx @@ -18,40 +18,54 @@ interface GraphSelectionToolbarProps extends CProps.Styling { graph: Graph; core: number[]; setSelected: React.Dispatch>; + emptySelection?: boolean; } -function GraphSelectionToolbar({ className, graph, core, setSelected, ...restProps }: GraphSelectionToolbarProps) { +function GraphSelectionToolbar({ + className, + graph, + core, + setSelected, + emptySelection, + ...restProps +}: GraphSelectionToolbarProps) { return (
} onClick={() => setSelected([])} + disabled={emptySelection} /> } onClick={() => setSelected(prev => [...prev, ...graph.expandAllInputs(prev)])} + disabled={emptySelection} /> } onClick={() => setSelected(prev => [...prev, ...graph.expandAllOutputs(prev)])} + disabled={emptySelection} /> } onClick={() => setSelected(prev => graph.maximizePart(prev))} + disabled={emptySelection} /> } onClick={() => setSelected(prev => [...prev, ...graph.expandInputs(prev)])} + disabled={emptySelection} /> } onClick={() => setSelected(prev => [...prev, ...graph.expandOutputs(prev)])} + disabled={emptySelection} /> isBasicConcept(cst.cst_type)).map(cst => cst.id)} setSelected={setSelected} + emptySelection={selected.length === 0} className='w-full ml-8' /> ) : null} diff --git a/rsconcept/frontend/src/components/ui/Checkbox.tsx b/rsconcept/frontend/src/components/ui/Checkbox.tsx index 373d831b..dc4b8d3a 100644 --- a/rsconcept/frontend/src/components/ui/Checkbox.tsx +++ b/rsconcept/frontend/src/components/ui/Checkbox.tsx @@ -37,6 +37,7 @@ function Checkbox({ function handleClick(event: CProps.EventMouse): void { event.preventDefault(); + event.stopPropagation(); if (disabled || !setValue) { return; } diff --git a/rsconcept/frontend/src/components/ui/CheckboxTristate.tsx b/rsconcept/frontend/src/components/ui/CheckboxTristate.tsx index 83d83823..e19a195c 100644 --- a/rsconcept/frontend/src/components/ui/CheckboxTristate.tsx +++ b/rsconcept/frontend/src/components/ui/CheckboxTristate.tsx @@ -35,6 +35,7 @@ function CheckboxTristate({ function handleClick(event: CProps.EventMouse): void { event.preventDefault(); + event.stopPropagation(); if (disabled || !setValue) { return; } diff --git a/rsconcept/frontend/src/pages/ManualsPage/items/HelpRSFormItems.tsx b/rsconcept/frontend/src/pages/ManualsPage/items/HelpRSFormItems.tsx index 1eebcf71..5c3bcbc8 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/items/HelpRSFormItems.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/items/HelpRSFormItems.tsx @@ -2,30 +2,42 @@ import InfoCstStatus from '@/components/info/InfoCstStatus'; import Divider from '@/components/ui/Divider'; import { HelpTopic } from '@/models/miscellaneous'; -import { IconAlias, IconMoveDown, IconMoveUp } from '../../../components/Icons'; +import { IconAlias, IconDestroy, IconMoveDown, IconMoveUp, IconOpenList, IconReset } from '../../../components/Icons'; import LinkTopic from '../../../components/ui/LinkTopic'; function HelpRSFormItems() { - // prettier-ignore return ( -
-

Список конституент

-

Конституенты обладают уникальным

-

Список поддерживает выделение и перемещение

+
+

Список конституент

+

+ + Конституенты обладают уникальным +

-

Управление списком

-
  • Клик на строку – выделение
  • -
  • Shift + клик – выделение нескольких
  • -
  • Alt + клик – Редактор
  • -
  • Двойной клик – Редактор
  • -
  • Alt + вверх/вниз – перемещение
  • -
  • Delete – удаление
  • -
  • Alt + 1-6,Q,W – добавление
  • +

    Управление списком

    +
  • + сбросить выделение: ESC +
  • +
  • Клик на строку – выделение
  • +
  • Shift + клик – выделение нескольких
  • +
  • Alt + клик – Редактор
  • +
  • Двойной клик – Редактор
  • +
  • + + Alt + вверх/вниз – перемещение +
  • +
  • + удаление: Delete +
  • +
  • + добавление: Alt + 1-6,Q,W –{' '} +
  • - + - -
    ); + +
    + ); } export default HelpRSFormItems; diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx index bfabbcae..6000bfbb 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx @@ -117,7 +117,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit } schema={controller.schema} expression={activeCst?.definition_formal ?? ''} isBottom={isNarrow} - activeID={activeCst?.id} + activeCst={activeCst} onOpenEdit={onOpenEdit} /> ) : null} diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx index f6de8e3a..9dc56651 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx @@ -50,11 +50,18 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) { } function handleKeyDown(event: React.KeyboardEvent) { + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + controller.deselectAll(); + return; + } if (!controller.isContentEditable || controller.isProcessing) { return; } if (event.key === 'Delete' && controller.selected.length > 0) { event.preventDefault(); + event.stopPropagation(); controller.deleteCst(); return; } @@ -63,6 +70,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) { } if (processAltKey(event.code)) { event.preventDefault(); + event.stopPropagation(); return; } } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx index f5ed6dad..c297995f 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx @@ -1,4 +1,12 @@ -import { IconClone, IconDestroy, IconMoveDown, IconMoveUp, IconNewItem, IconOpenList } from '@/components/Icons'; +import { + IconClone, + IconDestroy, + IconMoveDown, + IconMoveUp, + IconNewItem, + IconOpenList, + IconReset +} from '@/components/Icons'; import BadgeHelp from '@/components/info/BadgeHelp'; import Dropdown from '@/components/ui/Dropdown'; import DropdownButton from '@/components/ui/DropdownButton'; @@ -19,6 +27,12 @@ function RSListToolbar() { return ( + } + disabled={controller.selected.length === 0} + onClick={controller.deselectAll} + /> } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx index 4bf595aa..92c339af 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx @@ -186,6 +186,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { } if (event.key === 'Escape') { event.preventDefault(); + event.stopPropagation(); setFocusCst(undefined); controller.deselectAll(); return; @@ -195,6 +196,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { } if (event.key === 'Delete') { event.preventDefault(); + event.stopPropagation(); handleDeleteCst(); return; } @@ -286,7 +288,17 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { ); return ( - <> + + + {showParamsDialog ? ( + setShowParamsDialog(false)} + initial={filterParams} + onConfirm={handleChangeParams} + /> + ) : null} + + isBasicConcept(cst.cst_type)).map(cst => cst.id)} setSelected={controller.setSelected} + emptySelection={controller.selected.length === 0} /> ) : null} {focusCst ? ( @@ -338,44 +351,33 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { /> ) : null} - - - {showParamsDialog ? ( - setShowParamsDialog(false)} - initial={filterParams} - onConfirm={handleChangeParams} - /> - ) : null} - - + - {hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? ( - - - - ) : null} - - -
    - {selectors} - {viewHidden} -
    + {hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? ( + + + ) : null} - {graph} -
    - + +
    + {selectors} + {viewHidden} +
    +
    + + {graph} +
    ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index ce08e80b..b2bf12ad 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -114,14 +114,14 @@ function RSTabs() { const onCreateCst = useCallback( (newCst: IConstituentaMeta) => { navigateTab(activeTab, newCst.id); - if (activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST) { + if (activeTab === RSTabID.CST_LIST) { setTimeout(() => { const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'nearest', - inline: 'nearest' + inline: 'end' }); } }, PARAMETER.refreshTimeout); diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx index b3409edd..ca62e2ec 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx @@ -9,12 +9,12 @@ import { useConceptOptions } from '@/context/OptionsContext'; import useWindowSize from '@/hooks/useWindowSize'; import { ConstituentaID, IConstituenta } from '@/models/rsform'; import { isMockCst } from '@/models/rsformAPI'; -import { prefixes } from '@/utils/constants'; +import { PARAMETER, prefixes } from '@/utils/constants'; import { describeConstituenta } from '@/utils/labels'; interface ConstituentsTableProps { items: IConstituenta[]; - activeID?: ConstituentaID; + activeCst?: IConstituenta; onOpenEdit: (cstID: ConstituentaID) => void; denseThreshold?: number; maxHeight: string; @@ -22,12 +22,29 @@ interface ConstituentsTableProps { const columnHelper = createColumnHelper(); -function ConstituentsTable({ items, activeID, onOpenEdit, maxHeight, denseThreshold = 9999 }: ConstituentsTableProps) { +function ConstituentsTable({ items, activeCst, onOpenEdit, maxHeight, denseThreshold = 9999 }: ConstituentsTableProps) { const { colors } = useConceptOptions(); const windowSize = useWindowSize(); const [columnVisibility, setColumnVisibility] = useState({ expression: true }); + useLayoutEffect(() => { + if (!activeCst) { + return; + } + setTimeout(() => { + const element = document.getElementById(`${prefixes.cst_side_table}${activeCst.alias}`); + console.log(element); + if (element) { + element.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'end' + }); + } + }, PARAMETER.refreshTimeout); + }, [activeCst]); + useLayoutEffect(() => { setColumnVisibility(prev => { const newValue = (windowSize.width ?? 0) >= denseThreshold; @@ -104,25 +121,25 @@ function ConstituentsTable({ items, activeID, onOpenEdit, maxHeight, denseThresh const conditionalRowStyles = useMemo( (): IConditionalStyle[] => [ { - when: (cst: IConstituenta) => cst.id === activeID, + when: (cst: IConstituenta) => cst.id === activeCst?.id, style: { backgroundColor: colors.bgSelected } }, { - when: (cst: IConstituenta) => cst.parent === activeID && cst.id !== activeID, + when: (cst: IConstituenta) => cst.parent === activeCst?.id && cst.id !== activeCst?.id, style: { backgroundColor: colors.bgOrange50 } }, { - when: (cst: IConstituenta) => activeID !== undefined && cst.children.includes(activeID), + when: (cst: IConstituenta) => activeCst?.id !== undefined && cst.children.includes(activeCst.id), style: { backgroundColor: colors.bgGreen50 } } ], - [activeID, colors] + [activeCst, colors] ); return ( diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx index 219cea34..dc379f66 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx @@ -17,12 +17,12 @@ const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500; interface ViewConstituentsProps { expression: string; isBottom?: boolean; - activeID?: ConstituentaID; + activeCst?: IConstituenta; schema?: IRSForm; onOpenEdit: (cstID: ConstituentaID) => void; } -function ViewConstituents({ expression, schema, activeID, isBottom, onOpenEdit }: ViewConstituentsProps) { +function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit }: ViewConstituentsProps) { const { calculateHeight } = useConceptOptions(); const [filteredData, setFilteredData] = useState(schema?.items ?? []); @@ -32,12 +32,12 @@ function ViewConstituents({ expression, schema, activeID, isBottom, onOpenEdit } ), - [isBottom, filteredData, activeID, onOpenEdit, calculateHeight] + [isBottom, filteredData, activeCst, onOpenEdit, calculateHeight] ); return ( @@ -55,7 +55,7 @@ function ViewConstituents({ expression, schema, activeID, isBottom, onOpenEdit } >