mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implement editing capabilities for TermGraph
This commit is contained in:
parent
101631a8be
commit
e1601ab137
|
@ -76,10 +76,10 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
const isEditable = useMemo(
|
const isEditable = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
!loading && !isReadonly &&
|
!loading && !processing && !isReadonly &&
|
||||||
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
||||||
)
|
)
|
||||||
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading])
|
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading, processing])
|
||||||
|
|
||||||
const isTracking = useMemo(
|
const isTracking = useMemo(
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -90,7 +90,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new constituenta
|
// Add new constituenta
|
||||||
function handleCreateCst(type?: CstType){
|
function handleCreateCst(type?: CstType) {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge,
|
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge,
|
||||||
GraphNode, LayoutTypes, lightTheme, Sphere, useSelection
|
GraphNode, LayoutTypes, lightTheme, Sphere, useSelection
|
||||||
} from 'reagraph';
|
} from 'reagraph';
|
||||||
|
@ -12,7 +12,7 @@ import MiniButton from '../../components/Common/MiniButton';
|
||||||
import InfoConstituenta from '../../components/Help/InfoConstituenta';
|
import InfoConstituenta from '../../components/Help/InfoConstituenta';
|
||||||
import InfoCstClass from '../../components/Help/InfoCstClass';
|
import InfoCstClass from '../../components/Help/InfoCstClass';
|
||||||
import CstStatusInfo from '../../components/Help/InfoCstStatus';
|
import CstStatusInfo from '../../components/Help/InfoCstStatus';
|
||||||
import { ArrowsRotateIcon, FilterCogIcon, HelpIcon } from '../../components/Icons';
|
import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
@ -57,12 +57,12 @@ export interface GraphEditorParams {
|
||||||
|
|
||||||
interface EditorTermGraphProps {
|
interface EditorTermGraphProps {
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
// onCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
|
onCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
|
||||||
// onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||||
const { schema } = useRSForm();
|
const { schema, isEditable } = useRSForm();
|
||||||
const { darkMode, noNavigation } = useConceptTheme();
|
const { darkMode, noNavigation } = useConceptTheme();
|
||||||
|
|
||||||
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
|
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
|
||||||
|
@ -87,6 +87,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
const [ selectedDismissed, setSelectedDismissed ] = useState<number[]>([]);
|
const [ selectedDismissed, setSelectedDismissed ] = useState<number[]>([]);
|
||||||
const graphRef = useRef<GraphCanvasRef | null>(null);
|
const graphRef = useRef<GraphCanvasRef | null>(null);
|
||||||
const [showOptions, setShowOptions] = useState(false);
|
const [showOptions, setShowOptions] = useState(false);
|
||||||
|
const [toggleUpdate, setToggleUpdate] = useState(false);
|
||||||
|
|
||||||
const [hoverID, setHoverID] = useState<string | undefined>(undefined);
|
const [hoverID, setHoverID] = useState<string | undefined>(undefined);
|
||||||
const hoverCst = useMemo(
|
const hoverCst = useMemo(
|
||||||
|
@ -109,7 +110,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
return result;
|
return result;
|
||||||
}, [allowBase, allowStruct, allowTerm, allowAxiom, allowFunction, allowPredicate, allowConstant, allowTheorem]);
|
}, [allowBase, allowStruct, allowTerm, allowAxiom, allowFunction, allowPredicate, allowConstant, allowTheorem]);
|
||||||
|
|
||||||
useEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
setFiltered(new Graph());
|
setFiltered(new Graph());
|
||||||
|
@ -146,7 +147,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
setDismissed(newDismissed);
|
setDismissed(newDismissed);
|
||||||
setSelectedDismissed([]);
|
setSelectedDismissed([]);
|
||||||
setHoverID(undefined);
|
setHoverID(undefined);
|
||||||
}, [schema, noHermits, noTransitive, noTemplates, allowedTypes]);
|
}, [schema, noHermits, noTransitive, noTemplates, allowedTypes, toggleUpdate]);
|
||||||
|
|
||||||
function toggleDismissed(cstID: number) {
|
function toggleDismissed(cstID: number) {
|
||||||
setSelectedDismissed(prev => {
|
setSelectedDismissed(prev => {
|
||||||
|
@ -199,6 +200,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
const {
|
const {
|
||||||
selections, actives,
|
selections, actives,
|
||||||
onNodeClick,
|
onNodeClick,
|
||||||
|
clearSelections,
|
||||||
onCanvasClick,
|
onCanvasClick,
|
||||||
onNodePointerOver,
|
onNodePointerOver,
|
||||||
onNodePointerOut
|
onNodePointerOut
|
||||||
|
@ -213,9 +215,10 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const allSelected: string[] = useMemo(
|
const allSelected: string[] = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return [ ... selectedDismissed.map(id => String(id)), ... selections];
|
return [ ... selectedDismissed.map(id => String(id)), ... selections];
|
||||||
}, [selectedDismissed, selections]);
|
}, [selectedDismissed, selections]);
|
||||||
|
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
|
||||||
|
|
||||||
const handleRecreate = useCallback(
|
const handleRecreate = useCallback(
|
||||||
() => {
|
() => {
|
||||||
|
@ -250,6 +253,43 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
if (onCanvasClick) onCanvasClick(event);
|
if (onCanvasClick) onCanvasClick(event);
|
||||||
}, [onCanvasClick]);
|
}, [onCanvasClick]);
|
||||||
|
|
||||||
|
// Implement hotkeys for editing
|
||||||
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
|
console.log(event);
|
||||||
|
if (!isEditable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key === 'Delete' && allSelected.length > 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
handleDeleteCst();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreateCst() {
|
||||||
|
if (!schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedPosition = allSelected.reduce((prev, cstID) => {
|
||||||
|
const position = schema.items.findIndex(cst => cst.id === Number(cstID));
|
||||||
|
return Math.max(position, prev);
|
||||||
|
}, -1);
|
||||||
|
const insert_where = selectedPosition >= 0 ? schema.items[selectedPosition].id : undefined;
|
||||||
|
onCreateCst(insert_where, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteCst() {
|
||||||
|
if (!schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onDeleteCst([... allSelected.map(id => Number(id))], () => {
|
||||||
|
clearSelections();
|
||||||
|
setDismissed([]);
|
||||||
|
setSelectedDismissed([]);
|
||||||
|
setToggleUpdate(prev => !prev);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getOptions() {
|
function getOptions() {
|
||||||
return {
|
return {
|
||||||
noHermits: noHermits,
|
noHermits: noHermits,
|
||||||
|
@ -305,7 +345,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
}, [selectedDismissed]);
|
}, [selectedDismissed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-between w-full'>
|
<div className='flex justify-between w-full' tabIndex={0} onKeyDown={handleKeyDown}>
|
||||||
{showOptions &&
|
{showOptions &&
|
||||||
<DlgGraphOptions
|
<DlgGraphOptions
|
||||||
hideWindow={() => setShowOptions(false)}
|
hideWindow={() => setShowOptions(false)}
|
||||||
|
@ -321,11 +361,27 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
<div className='mr-3 whitespace-nowrap'>
|
<div className='flex items-center justify-between'>
|
||||||
Выбраны
|
<div className='mr-3 whitespace-nowrap'>
|
||||||
<span className='ml-2'>
|
Выбраны
|
||||||
<b>{allSelected.length}</b> из {schema?.stats?.count_all ?? 0}
|
<span className='ml-1'>
|
||||||
</span>
|
<b>{allSelected.length}</b> из {schema?.stats?.count_all ?? 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Удалить выбранные'
|
||||||
|
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={5}/>}
|
||||||
|
disabled={!isEditable || nothingSelected}
|
||||||
|
onClick={handleDeleteCst}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Новая конституента'
|
||||||
|
icon={<SmallPlusIcon color='text-green' size={5}/>}
|
||||||
|
disabled={!isEditable}
|
||||||
|
onClick={handleCreateCst}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider margins='mt-3 mb-2' />
|
<Divider margins='mt-3 mb-2' />
|
||||||
|
@ -401,7 +457,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-wrap w-full h-full overflow-auto'>
|
<div className='w-full h-full overflow-auto'>
|
||||||
<div
|
<div
|
||||||
className='relative border-t border-r'
|
className='relative border-t border-r'
|
||||||
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
|
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
|
||||||
|
|
|
@ -266,6 +266,8 @@ function RSTabs() {
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<EditorTermGraph
|
<EditorTermGraph
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
|
onCreateCst={promptCreateCst}
|
||||||
|
onDeleteCst={promptDeleteCst}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user