mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Add focus cst UI
This commit is contained in:
parent
85d756309e
commit
4d72690234
|
@ -6,7 +6,7 @@ function HelpConstituenta() {
|
|||
return (
|
||||
<div className='dense'>
|
||||
<h1>Редактор конституент</h1>
|
||||
<p>При выделении также подсвечиваются производные и основание</p>
|
||||
<p>Помимо активной конституенты выделяются порожденные и основание</p>
|
||||
<p><b>Сохранить изменения</b>: Ctrl + S или клик по кнопке Сохранить</p>
|
||||
<p className='mt-1'><b>Формальное определение</b></p>
|
||||
<p>- Ctrl + Пробел дополняет до незанятого имени</p>
|
||||
|
|
|
@ -56,8 +56,8 @@ function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps)
|
|||
setValue={value => updateParams({ noTransitive: value })}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Свернуть производные'
|
||||
title='Не отображать производные понятия'
|
||||
label='Свернуть порожденные'
|
||||
title='Не отображать порожденные понятия'
|
||||
value={params.foldDerived}
|
||||
setValue={value => updateParams({ foldDerived: value })}
|
||||
/>
|
||||
|
|
|
@ -116,6 +116,7 @@ export class Graph {
|
|||
const result: GraphNode[] = [];
|
||||
this.nodes.forEach(node => {
|
||||
if (node.outputs.length === 0 && node.inputs.length === 0) {
|
||||
result.push(node);
|
||||
this.nodes.delete(node.id);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -103,6 +103,9 @@ export interface GraphFilterParams {
|
|||
noText: boolean;
|
||||
foldDerived: boolean;
|
||||
|
||||
focusShowInputs: boolean;
|
||||
focusShowOutputs: boolean;
|
||||
|
||||
allowBase: boolean;
|
||||
allowStruct: boolean;
|
||||
allowTerm: boolean;
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|||
|
||||
import InfoConstituenta from '@/components/info/InfoConstituenta';
|
||||
import SelectedCounter from '@/components/info/SelectedCounter';
|
||||
import SelectGraphToolbar from '@/components/select/SelectGraphToolbar';
|
||||
import { GraphCanvasRef, GraphEdge, GraphLayout, GraphNode } from '@/components/ui/GraphUI';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useConceptOptions } from '@/context/OptionsContext';
|
||||
|
@ -14,12 +15,14 @@ import DlgGraphParams from '@/dialogs/DlgGraphParams';
|
|||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { GraphColoring, GraphFilterParams, GraphSizing } from '@/models/miscellaneous';
|
||||
import { applyNodeSizing } from '@/models/miscellaneousAPI';
|
||||
import { ConstituentaID, CstType } from '@/models/rsform';
|
||||
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
|
||||
import { isBasicConcept } from '@/models/rsformAPI';
|
||||
import { colorBgGraphNode } from '@/styling/color';
|
||||
import { PARAMETER, storage } from '@/utils/constants';
|
||||
import { convertBase64ToBlob } from '@/utils/utils';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
import FocusToolbar from './FocusToolbar';
|
||||
import GraphSelectors from './GraphSelectors';
|
||||
import GraphToolbar from './GraphToolbar';
|
||||
import TermGraph from './TermGraph';
|
||||
|
@ -41,6 +44,9 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
noText: false,
|
||||
foldDerived: false,
|
||||
|
||||
focusShowInputs: false,
|
||||
focusShowOutputs: false,
|
||||
|
||||
allowBase: true,
|
||||
allowStruct: true,
|
||||
allowTerm: true,
|
||||
|
@ -51,7 +57,8 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
allowTheorem: true
|
||||
});
|
||||
const [showParamsDialog, setShowParamsDialog] = useState(false);
|
||||
const filtered = useGraphFilter(controller.schema, filterParams);
|
||||
const [focusCst, setFocusCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const filtered = useGraphFilter(controller.schema, filterParams, focusCst);
|
||||
|
||||
const graphRef = useRef<GraphCanvasRef | null>(null);
|
||||
const [hidden, setHidden] = useState<ConstituentaID[]>([]);
|
||||
|
@ -93,7 +100,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
if (cst) {
|
||||
result.push({
|
||||
id: String(node.id),
|
||||
fill: colorBgGraphNode(cst, coloring, colors),
|
||||
fill: focusCst === cst ? colors.bgPurple : colorBgGraphNode(cst, coloring, colors),
|
||||
label: cst.alias,
|
||||
subLabel: !filterParams.noText ? cst.term_resolved : undefined,
|
||||
size: applyNodeSizing(cst, sizing)
|
||||
|
@ -101,23 +108,25 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
}
|
||||
});
|
||||
return result;
|
||||
}, [controller.schema, coloring, sizing, filtered.nodes, filterParams.noText, colors]);
|
||||
}, [controller.schema, coloring, sizing, filtered.nodes, filterParams.noText, colors, focusCst]);
|
||||
|
||||
const edges: GraphEdge[] = useMemo(() => {
|
||||
const result: GraphEdge[] = [];
|
||||
let edgeID = 1;
|
||||
filtered.nodes.forEach(source => {
|
||||
source.outputs.forEach(target => {
|
||||
result.push({
|
||||
id: String(edgeID),
|
||||
source: String(source.id),
|
||||
target: String(target)
|
||||
});
|
||||
edgeID += 1;
|
||||
if (nodes.find(node => node.id === String(target))) {
|
||||
result.push({
|
||||
id: String(edgeID),
|
||||
source: String(source.id),
|
||||
target: String(target)
|
||||
});
|
||||
edgeID += 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, [filtered.nodes]);
|
||||
}, [filtered.nodes, nodes]);
|
||||
|
||||
function handleCreateCst() {
|
||||
if (!controller.schema) {
|
||||
|
@ -174,6 +183,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
}
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
setFocusCst(undefined);
|
||||
controller.deselectAll();
|
||||
}
|
||||
}
|
||||
|
@ -188,6 +198,17 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
}, PARAMETER.graphRefreshDelay);
|
||||
}, [setFilterParams, setToggleResetView]);
|
||||
|
||||
const handleSetFocus = useCallback(
|
||||
(cstID: ConstituentaID | undefined) => {
|
||||
const target = cstID !== undefined ? controller.schema?.cstByID.get(cstID) : cstID;
|
||||
setFocusCst(prev => (prev === target ? undefined : target));
|
||||
if (target) {
|
||||
controller.setSelected([]);
|
||||
}
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
||||
const graph = useMemo(
|
||||
() => (
|
||||
<TermGraph
|
||||
|
@ -202,6 +223,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
onDeselect={controller.deselect}
|
||||
setHoverID={setHoverID}
|
||||
onEdit={onOpenEdit}
|
||||
onSelectCentral={handleSetFocus}
|
||||
toggleResetView={toggleResetView}
|
||||
/>
|
||||
),
|
||||
|
@ -217,7 +239,8 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
onOpenEdit,
|
||||
toggleResetView,
|
||||
controller.select,
|
||||
controller.deselect
|
||||
controller.deselect,
|
||||
handleSetFocus
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -240,25 +263,57 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
position='top-[4.3rem] sm:top-[0.3rem] left-0'
|
||||
/>
|
||||
|
||||
<GraphToolbar
|
||||
is3D={is3D}
|
||||
orbit={orbit}
|
||||
noText={filterParams.noText}
|
||||
foldDerived={filterParams.foldDerived}
|
||||
showParamsDialog={() => setShowParamsDialog(true)}
|
||||
onCreate={handleCreateCst}
|
||||
onDelete={handleDeleteCst}
|
||||
onResetViewpoint={() => setToggleResetView(prev => !prev)}
|
||||
onSaveImage={handleSaveImage}
|
||||
toggleOrbit={() => setOrbit(prev => !prev)}
|
||||
toggleFoldDerived={handleFoldDerived}
|
||||
toggleNoText={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
noText: !prev.noText
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<Overlay
|
||||
position='top-0 pt-1 right-1/2 translate-x-1/2'
|
||||
className='flex flex-col items-center rounded-b-2xl cc-blur'
|
||||
>
|
||||
<GraphToolbar
|
||||
is3D={is3D}
|
||||
orbit={orbit}
|
||||
noText={filterParams.noText}
|
||||
foldDerived={filterParams.foldDerived}
|
||||
showParamsDialog={() => setShowParamsDialog(true)}
|
||||
onCreate={handleCreateCst}
|
||||
onDelete={handleDeleteCst}
|
||||
onResetViewpoint={() => setToggleResetView(prev => !prev)}
|
||||
onSaveImage={handleSaveImage}
|
||||
toggleOrbit={() => setOrbit(prev => !prev)}
|
||||
toggleFoldDerived={handleFoldDerived}
|
||||
toggleNoText={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
noText: !prev.noText
|
||||
}))
|
||||
}
|
||||
/>
|
||||
{!focusCst ? (
|
||||
<SelectGraphToolbar
|
||||
graph={controller.schema!.graph}
|
||||
core={controller.schema!.items.filter(cst => isBasicConcept(cst.cst_type)).map(cst => cst.id)}
|
||||
setSelected={controller.setSelected}
|
||||
/>
|
||||
) : null}
|
||||
{focusCst ? (
|
||||
<FocusToolbar
|
||||
center={focusCst}
|
||||
reset={() => handleSetFocus(undefined)}
|
||||
showInputs={filterParams.focusShowInputs}
|
||||
showOutputs={filterParams.focusShowOutputs}
|
||||
toggleShowInputs={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
focusShowInputs: !prev.focusShowInputs
|
||||
}))
|
||||
}
|
||||
toggleShowOutputs={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
focusShowOutputs: !prev.focusShowOutputs
|
||||
}))
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</Overlay>
|
||||
|
||||
{hoverCst ? (
|
||||
<Overlay
|
||||
|
@ -286,6 +341,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
schema={controller.schema}
|
||||
coloringScheme={coloring}
|
||||
toggleSelection={controller.toggleSelect}
|
||||
setFocus={handleSetFocus}
|
||||
onEdit={onOpenEdit}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { IconGraphInputs, IconGraphOutputs, IconReset } from '@/components/Icons';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import { useConceptOptions } from '@/context/OptionsContext';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
||||
interface FocusToolbarProps {
|
||||
center: IConstituenta;
|
||||
showInputs: boolean;
|
||||
showOutputs: boolean;
|
||||
|
||||
reset: () => void;
|
||||
toggleShowInputs: () => void;
|
||||
toggleShowOutputs: () => void;
|
||||
}
|
||||
|
||||
function FocusToolbar({
|
||||
center,
|
||||
reset,
|
||||
showInputs,
|
||||
showOutputs,
|
||||
toggleShowInputs,
|
||||
toggleShowOutputs
|
||||
}: FocusToolbarProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
const controller = useRSEdit();
|
||||
|
||||
const resetSelection = useCallback(() => {
|
||||
reset();
|
||||
controller.setSelected([]);
|
||||
}, [reset, controller]);
|
||||
|
||||
return (
|
||||
<div className='cc-icons items-center'>
|
||||
<div className='w-[7.8rem] text-right select-none' style={{ color: colors.fgPurple }}>
|
||||
Фокус
|
||||
<b className='px-1'> {center.alias} </b>
|
||||
</div>
|
||||
<MiniButton
|
||||
titleHtml='Сбросить фокус'
|
||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={resetSelection}
|
||||
/>
|
||||
<MiniButton
|
||||
title={showInputs ? 'Скрыть поставщиков' : 'Отобразить поставщиков'}
|
||||
icon={
|
||||
showInputs ? (
|
||||
<IconGraphInputs size='1.25rem' className='icon-green' />
|
||||
) : (
|
||||
<IconGraphInputs size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleShowInputs}
|
||||
/>
|
||||
<MiniButton
|
||||
title={showOutputs ? 'Скрыть потребителей' : 'Отобразить потребителей'}
|
||||
icon={
|
||||
showOutputs ? (
|
||||
<IconGraphOutputs size='1.25rem' className='icon-green' />
|
||||
) : (
|
||||
<IconGraphOutputs size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleShowOutputs}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FocusToolbar;
|
|
@ -1,5 +1,3 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
IconClustering,
|
||||
IconClusteringOff,
|
||||
|
@ -13,11 +11,8 @@ import {
|
|||
IconTextOff
|
||||
} from '@/components/Icons';
|
||||
import BadgeHelp from '@/components/man/BadgeHelp';
|
||||
import SelectGraphToolbar from '@/components/select/SelectGraphToolbar';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { isBasicConcept } from '@/models/rsformAPI';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
||||
|
@ -56,78 +51,68 @@ function GraphToolbar({
|
|||
const controller = useRSEdit();
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
position='top-0 pt-1 right-1/2 translate-x-1/2'
|
||||
className='flex flex-col items-center rounded-b-2xl cc-blur'
|
||||
>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
title='Настройки фильтрации узлов и связей'
|
||||
icon={<IconFilter size='1.25rem' className='icon-primary' />}
|
||||
onClick={showParamsDialog}
|
||||
/>
|
||||
<MiniButton
|
||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||
title='Граф целиком'
|
||||
onClick={onResetViewpoint}
|
||||
/>
|
||||
<MiniButton
|
||||
title={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||
icon={
|
||||
!noText ? (
|
||||
<IconText size='1.25rem' className='icon-green' />
|
||||
) : (
|
||||
<IconTextOff size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleNoText}
|
||||
/>
|
||||
<MiniButton
|
||||
title={!foldDerived ? 'Скрыть производные' : 'Отобразить производные'}
|
||||
icon={
|
||||
!foldDerived ? (
|
||||
<IconClustering size='1.25rem' className='icon-green' />
|
||||
) : (
|
||||
<IconClusteringOff size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleFoldDerived}
|
||||
/>
|
||||
<MiniButton
|
||||
icon={<IconRotate3D size='1.25rem' className={orbit ? 'icon-green' : 'icon-primary'} />}
|
||||
title='Анимация вращения'
|
||||
disabled={!is3D}
|
||||
onClick={toggleOrbit}
|
||||
/>
|
||||
{controller.isContentEditable ? (
|
||||
<MiniButton
|
||||
title='Новая конституента'
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={onCreate}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isContentEditable ? (
|
||||
<MiniButton
|
||||
title='Удалить выбранные'
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={controller.nothingSelected || controller.isProcessing}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
||||
title='Сохранить изображение'
|
||||
onClick={onSaveImage}
|
||||
/>
|
||||
<BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} />
|
||||
</div>
|
||||
<SelectGraphToolbar
|
||||
graph={controller.schema!.graph}
|
||||
core={controller.schema!.items.filter(cst => isBasicConcept(cst.cst_type)).map(cst => cst.id)}
|
||||
setSelected={controller.setSelected}
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
title='Настройки фильтрации узлов и связей'
|
||||
icon={<IconFilter size='1.25rem' className='icon-primary' />}
|
||||
onClick={showParamsDialog}
|
||||
/>
|
||||
</Overlay>
|
||||
<MiniButton
|
||||
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
|
||||
title='Граф целиком'
|
||||
onClick={onResetViewpoint}
|
||||
/>
|
||||
<MiniButton
|
||||
title={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||
icon={
|
||||
!noText ? (
|
||||
<IconText size='1.25rem' className='icon-green' />
|
||||
) : (
|
||||
<IconTextOff size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleNoText}
|
||||
/>
|
||||
<MiniButton
|
||||
title={!foldDerived ? 'Скрыть порожденные' : 'Отобразить порожденные'}
|
||||
icon={
|
||||
!foldDerived ? (
|
||||
<IconClustering size='1.25rem' className='icon-green' />
|
||||
) : (
|
||||
<IconClusteringOff size='1.25rem' className='icon-primary' />
|
||||
)
|
||||
}
|
||||
onClick={toggleFoldDerived}
|
||||
/>
|
||||
<MiniButton
|
||||
icon={<IconRotate3D size='1.25rem' className={orbit ? 'icon-green' : 'icon-primary'} />}
|
||||
title='Анимация вращения'
|
||||
disabled={!is3D}
|
||||
onClick={toggleOrbit}
|
||||
/>
|
||||
{controller.isContentEditable ? (
|
||||
<MiniButton
|
||||
title='Новая конституента'
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={onCreate}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isContentEditable ? (
|
||||
<MiniButton
|
||||
title='Удалить выбранные'
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={controller.nothingSelected || controller.isProcessing}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
||||
title='Сохранить изображение'
|
||||
onClick={onSaveImage}
|
||||
/>
|
||||
<BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ interface TermGraphProps {
|
|||
|
||||
setHoverID: (newID: ConstituentaID | undefined) => void;
|
||||
onEdit: (cstID: ConstituentaID) => void;
|
||||
onSelectCentral: (selectedID: ConstituentaID) => void;
|
||||
onSelect: (newID: ConstituentaID) => void;
|
||||
onDeselect: (newID: ConstituentaID) => void;
|
||||
|
||||
|
@ -37,9 +38,11 @@ function TermGraph({
|
|||
toggleResetView,
|
||||
setHoverID,
|
||||
onEdit,
|
||||
onSelectCentral,
|
||||
onSelect,
|
||||
onDeselect
|
||||
}: TermGraphProps) {
|
||||
let ctrlKey: boolean = false;
|
||||
const { calculateHeight, darkMode } = useConceptOptions();
|
||||
|
||||
const { selections, setSelections } = useSelection({
|
||||
|
@ -63,13 +66,15 @@ function TermGraph({
|
|||
|
||||
const handleNodeClick = useCallback(
|
||||
(node: GraphNode) => {
|
||||
if (selections.includes(node.id)) {
|
||||
if (ctrlKey) {
|
||||
onSelectCentral(Number(node.id));
|
||||
} else if (selections.includes(node.id)) {
|
||||
onDeselect(Number(node.id));
|
||||
} else {
|
||||
onSelect(Number(node.id));
|
||||
}
|
||||
},
|
||||
[onSelect, selections, onDeselect]
|
||||
[onSelect, selections, onDeselect, onSelectCentral, ctrlKey]
|
||||
);
|
||||
|
||||
const handleNodeDoubleClick = useCallback(
|
||||
|
@ -96,7 +101,13 @@ function TermGraph({
|
|||
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||
|
||||
return (
|
||||
<div className='outline-none'>
|
||||
<div
|
||||
className='outline-none'
|
||||
tabIndex={-1}
|
||||
// TODO: fix hacky way of tracking CTRL. Expose event from onNodeClick instead
|
||||
onKeyUp={event => (ctrlKey = event.ctrlKey)}
|
||||
onKeyDown={event => (ctrlKey = event.ctrlKey)}
|
||||
>
|
||||
<div className='relative' style={{ width: canvasWidth, height: canvasHeight }}>
|
||||
<GraphUI
|
||||
nodes={nodes}
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { IconDropArrow, IconDropArrowUp } from '@/components/Icons';
|
||||
import ConstituentaTooltip from '@/components/info/ConstituentaTooltip';
|
||||
import { CProps } from '@/components/props';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useConceptOptions } from '@/context/OptionsContext';
|
||||
|
@ -24,15 +25,27 @@ interface ViewHiddenProps {
|
|||
coloringScheme: GraphColoring;
|
||||
|
||||
toggleSelection: (cstID: ConstituentaID) => void;
|
||||
setFocus: (cstID: ConstituentaID) => void;
|
||||
onEdit: (cstID: ConstituentaID) => void;
|
||||
}
|
||||
|
||||
function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme, onEdit }: ViewHiddenProps) {
|
||||
function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) {
|
||||
const { colors, calculateHeight } = useConceptOptions();
|
||||
const windowSize = useWindowSize();
|
||||
const localSelected = useMemo(() => items.filter(id => selected.includes(id)), [items, selected]);
|
||||
const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(cstID: ConstituentaID, event: CProps.EventMouse) => {
|
||||
if (event.ctrlKey) {
|
||||
setFocus(cstID);
|
||||
} else {
|
||||
toggleSelection(cstID);
|
||||
}
|
||||
},
|
||||
[setFocus, toggleSelection]
|
||||
);
|
||||
|
||||
if (!schema || items.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -93,7 +106,7 @@ function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme,
|
|||
backgroundColor: colorBgGraphNode(cst, adjustedColoring, colors),
|
||||
...(localSelected.includes(cstID) ? { outlineWidth: '2px', outlineStyle: 'solid' } : {})
|
||||
}}
|
||||
onClick={() => toggleSelection(cstID)}
|
||||
onClick={event => handleClick(cstID, event)}
|
||||
onDoubleClick={() => onEdit(cstID)}
|
||||
>
|
||||
{cst.alias}
|
||||
|
|
|
@ -2,9 +2,9 @@ import { useLayoutEffect, useMemo, useState } from 'react';
|
|||
|
||||
import { Graph } from '@/models/Graph';
|
||||
import { GraphFilterParams } from '@/models/miscellaneous';
|
||||
import { CstType, IRSForm } from '@/models/rsform';
|
||||
import { ConstituentaID, CstType, IConstituenta, IRSForm } from '@/models/rsform';
|
||||
|
||||
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams) {
|
||||
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams, focusCst: IConstituenta | undefined) {
|
||||
const [filtered, setFiltered] = useState<Graph>(new Graph());
|
||||
|
||||
const allowedTypes: CstType[] = useMemo(() => {
|
||||
|
@ -29,32 +29,45 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams)
|
|||
if (params.noHermits) {
|
||||
graph.removeIsolated();
|
||||
}
|
||||
if (params.noTransitive) {
|
||||
graph.transitiveReduction();
|
||||
}
|
||||
if (params.noTemplates) {
|
||||
schema.items.forEach(cst => {
|
||||
if (cst.is_template) {
|
||||
if (cst !== focusCst && cst.is_template) {
|
||||
graph.foldNode(cst.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (allowedTypes.length < Object.values(CstType).length) {
|
||||
schema.items.forEach(cst => {
|
||||
if (!allowedTypes.includes(cst.cst_type)) {
|
||||
if (cst !== focusCst && !allowedTypes.includes(cst.cst_type)) {
|
||||
graph.foldNode(cst.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (params.foldDerived) {
|
||||
if (!focusCst && params.foldDerived) {
|
||||
schema.items.forEach(cst => {
|
||||
if (cst.parent_alias) {
|
||||
if (cst.parent) {
|
||||
graph.foldNode(cst.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (focusCst) {
|
||||
const includes: ConstituentaID[] = [
|
||||
focusCst.id,
|
||||
...focusCst.children,
|
||||
...(params.focusShowInputs ? schema.graph.expandInputs([focusCst.id]) : []),
|
||||
...(params.focusShowOutputs ? schema.graph.expandOutputs([focusCst.id]) : [])
|
||||
];
|
||||
schema.items.forEach(cst => {
|
||||
if (!includes.includes(cst.id)) {
|
||||
graph.foldNode(cst.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (params.noTransitive) {
|
||||
graph.transitiveReduction();
|
||||
}
|
||||
setFiltered(graph);
|
||||
}, [schema, params, allowedTypes]);
|
||||
}, [schema, params, allowedTypes, focusCst]);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
|
|
@ -145,11 +145,11 @@ function RSTabs() {
|
|||
|
||||
const onOpenCst = useCallback(
|
||||
(cstID: ConstituentaID) => {
|
||||
if (cstID !== activeCst?.id) {
|
||||
if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
|
||||
navigateTab(RSTabID.CST_EDIT, cstID);
|
||||
}
|
||||
},
|
||||
[navigateTab, activeCst]
|
||||
[navigateTab, activeCst, activeTab]
|
||||
);
|
||||
|
||||
const onDestroySchema = useCallback(() => {
|
||||
|
|
|
@ -90,7 +90,7 @@ export const storage = {
|
|||
librarySearchStrategy: 'library.search.strategy',
|
||||
libraryPagination: 'library.pagination',
|
||||
|
||||
rsgraphFilter: 'rsgraph.filter_options',
|
||||
rsgraphFilter: 'rsgraph.filter2',
|
||||
rsgraphLayout: 'rsgraph.layout',
|
||||
rsgraphColoring: 'rsgraph.coloring',
|
||||
rsgraphSizing: 'rsgraph.sizing',
|
||||
|
|
Loading…
Reference in New Issue
Block a user