mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor constituenta selection and tooltip visibility
This commit is contained in:
parent
5b3862d46c
commit
2b7a4c04ce
|
@ -1,19 +1,19 @@
|
|||
import Overlay from '@/components/ui/Overlay';
|
||||
|
||||
interface SelectedCounterProps {
|
||||
total: number;
|
||||
selected: number;
|
||||
totalCount: number;
|
||||
selectedCount: number;
|
||||
position?: string;
|
||||
hideZero?: boolean;
|
||||
}
|
||||
|
||||
function SelectedCounter({ total, selected, hideZero, position = 'top-0 left-0' }: SelectedCounterProps) {
|
||||
if (selected === 0 && hideZero) {
|
||||
function SelectedCounter({ totalCount, selectedCount, hideZero, position = 'top-0 left-0' }: SelectedCounterProps) {
|
||||
if (selectedCount === 0 && hideZero) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Overlay position={`px-2 ${position}`} className='select-none whitespace-nowrap clr-app'>
|
||||
Выбор {selected} из {total}
|
||||
Выбор {selectedCount} из {totalCount}
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ interface ButtonProps extends CProps.Control, CProps.Colors, CProps.Button {
|
|||
icon?: React.ReactNode;
|
||||
|
||||
dense?: boolean;
|
||||
hideTitle?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
|
@ -19,6 +20,7 @@ function Button({
|
|||
loading,
|
||||
dense,
|
||||
disabled,
|
||||
hideTitle,
|
||||
noBorder,
|
||||
noOutline,
|
||||
colors = 'clr-btn-default',
|
||||
|
@ -46,6 +48,7 @@ function Button({
|
|||
)}
|
||||
data-tooltip-id={title ? globalIDs.tooltip : undefined}
|
||||
data-tooltip-content={title}
|
||||
data-tooltip-hidden={hideTitle}
|
||||
{...restProps}
|
||||
>
|
||||
{icon ? icon : null}
|
||||
|
|
|
@ -7,9 +7,10 @@ import { CProps } from '../props';
|
|||
interface MiniButtonProps extends CProps.Button {
|
||||
icon: React.ReactNode;
|
||||
noHover?: boolean;
|
||||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
function MiniButton({ icon, noHover, tabIndex, title, className, ...restProps }: MiniButtonProps) {
|
||||
function MiniButton({ icon, noHover, hideTitle, tabIndex, title, className, ...restProps }: MiniButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
|
@ -27,6 +28,7 @@ function MiniButton({ icon, noHover, tabIndex, title, className, ...restProps }:
|
|||
)}
|
||||
data-tooltip-id={title ? globalIDs.tooltip : undefined}
|
||||
data-tooltip-content={title}
|
||||
data-tooltip-hidden={hideTitle}
|
||||
{...restProps}
|
||||
>
|
||||
{icon}
|
||||
|
|
|
@ -10,6 +10,7 @@ interface SelectorButtonProps extends CProps.Button {
|
|||
|
||||
colors?: string;
|
||||
transparent?: boolean;
|
||||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
function SelectorButton({
|
||||
|
@ -19,13 +20,12 @@ function SelectorButton({
|
|||
colors = 'clr-btn-default',
|
||||
className,
|
||||
transparent,
|
||||
hideTitle,
|
||||
...restProps
|
||||
}: SelectorButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
data-tooltip-id={title ? globalIDs.tooltip : undefined}
|
||||
data-tooltip-content={title}
|
||||
className={clsx(
|
||||
'px-1 flex flex-start items-center gap-1',
|
||||
'text-sm font-controls select-none',
|
||||
|
@ -38,6 +38,9 @@ function SelectorButton({
|
|||
className,
|
||||
!transparent && colors
|
||||
)}
|
||||
data-tooltip-id={title ? globalIDs.tooltip : undefined}
|
||||
data-tooltip-content={title}
|
||||
data-tooltip-hidden={hideTitle}
|
||||
{...restProps}
|
||||
>
|
||||
{icon ? icon : null}
|
||||
|
|
|
@ -47,6 +47,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
|
|||
transparent
|
||||
tabIndex={-1}
|
||||
title='Список фильтров'
|
||||
hideTitle={strategyMenu.isOpen}
|
||||
className='h-full'
|
||||
icon={<BiFilterAlt size='1.25rem' />}
|
||||
text={labelLibraryFilter(value)}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { CstType, IConstituenta, ICstCreateData, ICstRenameData } from '@/models/rsform';
|
||||
import { CstType, IConstituenta, IRSForm } from '@/models/rsform';
|
||||
import { globalIDs } from '@/utils/constants';
|
||||
|
||||
import ViewConstituents from '../ViewConstituents';
|
||||
|
@ -20,79 +19,43 @@ const UNFOLDED_HEIGHT = '59.1rem';
|
|||
const SIDELIST_HIDE_THRESHOLD = 1100; // px
|
||||
|
||||
interface EditorConstituentaProps {
|
||||
schema?: IRSForm;
|
||||
isMutable: boolean;
|
||||
|
||||
activeID?: number;
|
||||
activeCst?: IConstituenta | undefined;
|
||||
activeCst?: IConstituenta;
|
||||
isModified: boolean;
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>;
|
||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
onOpenEdit: (cstID: number) => void;
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void;
|
||||
onRenameCst: (initial: ICstRenameData) => void;
|
||||
onClone: () => void;
|
||||
onCreate: (type?: CstType) => void;
|
||||
onRename: () => void;
|
||||
onEditTerm: () => void;
|
||||
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
function EditorConstituenta({
|
||||
schema,
|
||||
isMutable,
|
||||
isModified,
|
||||
setIsModified,
|
||||
activeID,
|
||||
activeCst,
|
||||
onEditTerm,
|
||||
onCreateCst,
|
||||
onRenameCst,
|
||||
onClone,
|
||||
onCreate,
|
||||
onRename,
|
||||
onOpenEdit,
|
||||
onDeleteCst
|
||||
onDelete
|
||||
}: EditorConstituentaProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const [showList, setShowList] = useLocalStorage('rseditor-show-list', true);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
||||
const disabled = useMemo(() => !activeCst || !isMutable, [activeCst, isMutable]);
|
||||
|
||||
function handleDelete() {
|
||||
if (!schema || !activeID) {
|
||||
return;
|
||||
}
|
||||
onDeleteCst([activeID]);
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
if (!activeID || !schema) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateData = {
|
||||
insert_after: activeID,
|
||||
cst_type: activeCst?.cst_type ?? CstType.BASE,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
term_forms: []
|
||||
};
|
||||
onCreateCst(data);
|
||||
}
|
||||
|
||||
function handleClone() {
|
||||
if (!activeID || !schema || !activeCst) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateData = {
|
||||
insert_after: activeID,
|
||||
cst_type: activeCst.cst_type,
|
||||
alias: '',
|
||||
term_raw: activeCst.term_raw,
|
||||
definition_formal: activeCst.definition_formal,
|
||||
definition_raw: activeCst.definition_raw,
|
||||
convention: activeCst.convention,
|
||||
term_forms: activeCst.term_forms
|
||||
};
|
||||
onCreateCst(data, true);
|
||||
onCreate(activeCst?.cst_type);
|
||||
}
|
||||
|
||||
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
|
@ -125,7 +88,7 @@ function EditorConstituenta({
|
|||
function processAltKey(code: string): boolean {
|
||||
switch (code) {
|
||||
case 'KeyV':
|
||||
handleClone();
|
||||
onClone();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -138,8 +101,8 @@ function EditorConstituenta({
|
|||
isModified={isModified}
|
||||
onSubmit={initiateSubmit}
|
||||
onReset={() => setToggleReset(prev => !prev)}
|
||||
onDelete={handleDelete}
|
||||
onClone={handleClone}
|
||||
onDelete={onDelete}
|
||||
onClone={onClone}
|
||||
onCreate={handleCreate}
|
||||
/>
|
||||
<div tabIndex={-1} className='flex max-w-[95rem]' onKeyDown={handleInput}>
|
||||
|
@ -153,7 +116,7 @@ function EditorConstituenta({
|
|||
onToggleList={() => setShowList(prev => !prev)}
|
||||
setIsModified={setIsModified}
|
||||
onEditTerm={onEditTerm}
|
||||
onRenameCst={onRenameCst}
|
||||
onRename={onRename}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{showList && windowSize.width && windowSize.width >= SIDELIST_HIDE_THRESHOLD ? (
|
||||
|
@ -161,7 +124,7 @@ function EditorConstituenta({
|
|||
schema={schema}
|
||||
expression={activeCst?.definition_formal ?? ''}
|
||||
baseHeight={UNFOLDED_HEIGHT}
|
||||
activeID={activeID}
|
||||
activeID={activeCst?.id}
|
||||
onOpenEdit={onOpenEdit}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { Dispatch, SetStateAction, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { LiaEdit } from 'react-icons/lia';
|
||||
import { toast } from 'react-toastify';
|
||||
|
@ -12,7 +12,7 @@ import Overlay from '@/components/ui/Overlay';
|
|||
import SubmitButton from '@/components/ui/SubmitButton';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { IConstituenta, ICstRenameData, ICstUpdateData } from '@/models/rsform';
|
||||
import { IConstituenta, ICstUpdateData } from '@/models/rsform';
|
||||
import { classnames } from '@/utils/constants';
|
||||
import { labelCstTypification } from '@/utils/labels';
|
||||
|
||||
|
@ -27,10 +27,10 @@ interface FormConstituentaProps {
|
|||
|
||||
isModified: boolean;
|
||||
toggleReset: boolean;
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>;
|
||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
onToggleList: () => void;
|
||||
onRenameCst: (initial: ICstRenameData) => void;
|
||||
onRename: () => void;
|
||||
onEditTerm: () => void;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ function FormConstituenta({
|
|||
setIsModified,
|
||||
constituenta,
|
||||
toggleReset,
|
||||
onRenameCst,
|
||||
onRename,
|
||||
onEditTerm,
|
||||
onToggleList
|
||||
}: FormConstituentaProps) {
|
||||
|
@ -109,18 +109,6 @@ function FormConstituenta({
|
|||
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
||||
}
|
||||
|
||||
function handleRename() {
|
||||
if (!constituenta) {
|
||||
return;
|
||||
}
|
||||
const data: ICstRenameData = {
|
||||
id: constituenta.id,
|
||||
alias: constituenta.alias,
|
||||
cst_type: constituenta.cst_type
|
||||
};
|
||||
onRenameCst(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Overlay position='top-1 left-[4.1rem]' className='flex select-none'>
|
||||
|
@ -139,7 +127,7 @@ function FormConstituenta({
|
|||
noHover
|
||||
title='Переименовать конституенту'
|
||||
disabled={disabled}
|
||||
onClick={handleRename}
|
||||
onClick={onRename}
|
||||
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
||||
/>
|
||||
</Overlay>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import InfoLibraryItem from '@/components/InfoLibraryItem';
|
||||
import Divider from '@/components/ui/Divider';
|
||||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
|
@ -17,7 +15,7 @@ interface EditorRSFormProps {
|
|||
isModified: boolean;
|
||||
isMutable: boolean;
|
||||
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>;
|
||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onDestroy: () => void;
|
||||
onClaim: () => void;
|
||||
onShare: () => void;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { Dispatch, SetStateAction, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -19,7 +19,7 @@ interface FormRSFormProps {
|
|||
id?: string;
|
||||
disabled: boolean;
|
||||
isModified: boolean;
|
||||
setIsModified: Dispatch<SetStateAction<boolean>>;
|
||||
setIsModified: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function FormRSForm({ id, disabled, isModified, setIsModified }: FormRSFormProps) {
|
||||
|
|
|
@ -5,46 +5,59 @@ import { useLayoutEffect, useState } from 'react';
|
|||
import { type RowSelectionState } from '@/components/DataTable';
|
||||
import SelectedCounter from '@/components/SelectedCounter';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { CstType, ICstCreateData, ICstMovetoData } from '@/models/rsform';
|
||||
import { CstType, ICstMovetoData } from '@/models/rsform';
|
||||
|
||||
import RSListToolbar from './RSListToolbar';
|
||||
import RSTable from './RSTable';
|
||||
|
||||
interface EditorRSListProps {
|
||||
isMutable: boolean;
|
||||
selected: number[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
onOpenEdit: (cstID: number) => void;
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void;
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void;
|
||||
onClone: () => void;
|
||||
onCreate: (type?: CstType) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: EditorRSListProps) {
|
||||
function EditorRSList({
|
||||
selected,
|
||||
setSelected,
|
||||
isMutable,
|
||||
onOpenEdit,
|
||||
onClone,
|
||||
onCreate,
|
||||
onDelete
|
||||
}: EditorRSListProps) {
|
||||
const { schema, cstMoveTo } = useRSForm();
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!schema || Object.keys(rowSelection).length === 0) {
|
||||
if (!schema || selected.length === 0) {
|
||||
setRowSelection({});
|
||||
} else {
|
||||
const newRowSelection: RowSelectionState = {};
|
||||
schema.items.forEach((cst, index) => {
|
||||
newRowSelection[String(index)] = selected.includes(cst.id);
|
||||
});
|
||||
setRowSelection(newRowSelection);
|
||||
}
|
||||
}, [selected, schema]);
|
||||
|
||||
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
||||
if (!schema) {
|
||||
setSelected([]);
|
||||
} else {
|
||||
const selected: number[] = [];
|
||||
schema.items.forEach((cst, index) => {
|
||||
if (rowSelection[String(index)] === true) {
|
||||
selected.push(cst.id);
|
||||
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
||||
const newSelection: number[] = [];
|
||||
schema?.items.forEach((cst, index) => {
|
||||
if (newRowSelection[String(index)] === true) {
|
||||
newSelection.push(cst.id);
|
||||
}
|
||||
});
|
||||
setSelected(selected);
|
||||
setSelected(newSelection);
|
||||
}
|
||||
}, [rowSelection, schema]);
|
||||
|
||||
// Delete selected constituents
|
||||
function handleDelete() {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
onDeleteCst(selected, () => {
|
||||
setRowSelection({});
|
||||
});
|
||||
}
|
||||
|
||||
// Move selected cst up
|
||||
|
@ -65,13 +78,7 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
items: selected,
|
||||
move_to: target
|
||||
};
|
||||
cstMoveTo(data, () => {
|
||||
const newSelection: RowSelectionState = {};
|
||||
selected.forEach((_, index) => {
|
||||
newSelection[String(target + index - 1)] = true;
|
||||
});
|
||||
setRowSelection(newSelection);
|
||||
});
|
||||
cstMoveTo(data);
|
||||
}
|
||||
|
||||
// Move selected cst down
|
||||
|
@ -96,57 +103,7 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
items: selected,
|
||||
move_to: target
|
||||
};
|
||||
cstMoveTo(data, () => {
|
||||
const newSelection: RowSelectionState = {};
|
||||
selected.forEach((_, index) => {
|
||||
newSelection[String(target + index - 1)] = true;
|
||||
});
|
||||
setRowSelection(newSelection);
|
||||
});
|
||||
}
|
||||
|
||||
function handleCreateCst(type?: CstType) {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const selectedPosition = selected.reduce((prev, cstID) => {
|
||||
const position = schema.items.findIndex(cst => cst.id === cstID);
|
||||
return Math.max(position, prev);
|
||||
}, -1);
|
||||
const insert_where = selectedPosition >= 0 ? schema.items[selectedPosition].id : undefined;
|
||||
const data: ICstCreateData = {
|
||||
insert_after: insert_where ?? null,
|
||||
cst_type: type ?? CstType.BASE,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
term_forms: []
|
||||
};
|
||||
onCreateCst(data, type !== undefined);
|
||||
}
|
||||
|
||||
// Clone selected
|
||||
function handleClone() {
|
||||
if (selected.length < 1 || !schema) {
|
||||
return;
|
||||
}
|
||||
const activeCst = schema.items.find(cst => cst.id === selected[0]);
|
||||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateData = {
|
||||
insert_after: activeCst.id,
|
||||
cst_type: activeCst.cst_type,
|
||||
alias: '',
|
||||
term_raw: activeCst.term_raw,
|
||||
definition_formal: activeCst.definition_formal,
|
||||
definition_raw: activeCst.definition_raw,
|
||||
convention: activeCst.convention,
|
||||
term_forms: activeCst.term_forms
|
||||
};
|
||||
onCreateCst(data, true);
|
||||
cstMoveTo(data);
|
||||
}
|
||||
|
||||
// Implement hotkeys for working with constituents table
|
||||
|
@ -156,7 +113,7 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
}
|
||||
if (event.key === 'Delete' && selected.length > 0) {
|
||||
event.preventDefault();
|
||||
handleDelete();
|
||||
onDelete();
|
||||
return;
|
||||
}
|
||||
if (!event.altKey || event.shiftKey) {
|
||||
|
@ -174,21 +131,21 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
switch (code) {
|
||||
case 'ArrowUp': handleMoveUp(); return true;
|
||||
case 'ArrowDown': handleMoveDown(); return true;
|
||||
case 'KeyV': handleClone(); return true;
|
||||
case 'KeyV': onClone(); return true;
|
||||
}
|
||||
}
|
||||
// prettier-ignore
|
||||
switch (code) {
|
||||
case 'Backquote': handleCreateCst(); return true;
|
||||
case 'Backquote': onCreate(); return true;
|
||||
|
||||
case 'Digit1': handleCreateCst(CstType.BASE); return true;
|
||||
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
|
||||
case 'Digit3': handleCreateCst(CstType.TERM); return true;
|
||||
case 'Digit4': handleCreateCst(CstType.AXIOM); return true;
|
||||
case 'KeyQ': handleCreateCst(CstType.FUNCTION); return true;
|
||||
case 'KeyW': handleCreateCst(CstType.PREDICATE); return true;
|
||||
case 'Digit5': handleCreateCst(CstType.CONSTANT); return true;
|
||||
case 'Digit6': handleCreateCst(CstType.THEOREM); return true;
|
||||
case 'Digit1': onCreate(CstType.BASE); return true;
|
||||
case 'Digit2': onCreate(CstType.STRUCTURED); return true;
|
||||
case 'Digit3': onCreate(CstType.TERM); return true;
|
||||
case 'Digit4': onCreate(CstType.AXIOM); return true;
|
||||
case 'KeyQ': onCreate(CstType.FUNCTION); return true;
|
||||
case 'KeyW': onCreate(CstType.PREDICATE); return true;
|
||||
case 'Digit5': onCreate(CstType.CONSTANT); return true;
|
||||
case 'Digit6': onCreate(CstType.THEOREM); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -196,8 +153,8 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
return (
|
||||
<div tabIndex={-1} className='outline-none' onKeyDown={handleTableKey}>
|
||||
<SelectedCounter
|
||||
total={schema?.stats?.count_all ?? 0}
|
||||
selected={selected.length}
|
||||
totalCount={schema?.stats?.count_all ?? 0}
|
||||
selectedCount={selected.length}
|
||||
position='top-[0.3rem] left-2'
|
||||
/>
|
||||
|
||||
|
@ -206,9 +163,9 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
isMutable={isMutable}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
onClone={handleClone}
|
||||
onCreate={handleCreateCst}
|
||||
onDelete={handleDelete}
|
||||
onClone={onClone}
|
||||
onCreate={onCreate}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
|
||||
<div className='pt-[2.3rem] border-b' />
|
||||
|
@ -216,9 +173,9 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
<RSTable
|
||||
items={schema?.items}
|
||||
selected={rowSelection}
|
||||
setSelected={setRowSelection}
|
||||
setSelected={handleRowSelection}
|
||||
onEdit={onOpenEdit}
|
||||
onCreateNew={() => handleCreateCst()}
|
||||
onCreateNew={onCreate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -67,6 +67,7 @@ function RSListToolbar({
|
|||
<div ref={insertMenu.ref}>
|
||||
<MiniButton
|
||||
title='Добавить пустую конституенту'
|
||||
hideTitle={insertMenu.isOpen}
|
||||
icon={<BiDownArrowCircle size='1.25rem' className={isMutable ? 'clr-text-success' : ''} />}
|
||||
disabled={!isMutable}
|
||||
onClick={insertMenu.toggle}
|
||||
|
|
|
@ -8,12 +8,11 @@ import { GraphEdge, GraphNode, LayoutTypes } from 'reagraph';
|
|||
import InfoConstituenta from '@/components/InfoConstituenta';
|
||||
import SelectedCounter from '@/components/SelectedCounter';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import DlgGraphParams from '@/dialogs/DlgGraphParams';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { GraphColoringScheme, GraphFilterParams } from '@/models/miscellaneous';
|
||||
import { CstType, ICstCreateData } from '@/models/rsform';
|
||||
import { CstType, IRSForm } from '@/models/rsform';
|
||||
import { colorBgGraphNode } from '@/styling/color';
|
||||
import { classnames, TIMEOUT_GRAPH_REFRESH } from '@/utils/constants';
|
||||
|
||||
|
@ -25,16 +24,25 @@ import ViewHidden from './ViewHidden';
|
|||
|
||||
interface EditorTermGraphProps {
|
||||
isMutable: boolean;
|
||||
selected: number[];
|
||||
schema?: IRSForm;
|
||||
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
onOpenEdit: (cstID: number) => void;
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void;
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void;
|
||||
onCreate: (type: CstType, definition: string) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||
const { schema } = useRSForm();
|
||||
function EditorTermGraph({
|
||||
schema,
|
||||
selected,
|
||||
setSelected,
|
||||
isMutable,
|
||||
onOpenEdit,
|
||||
onCreate,
|
||||
onDelete
|
||||
}: EditorTermGraphProps) {
|
||||
const { colors } = useConceptTheme();
|
||||
|
||||
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
|
||||
const [filterParams, setFilterParams] = useLocalStorage<GraphFilterParams>('graph_filter', {
|
||||
noHermits: true,
|
||||
noTemplates: false,
|
||||
|
@ -51,14 +59,10 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
allowTheorem: true
|
||||
});
|
||||
const [showParamsDialog, setShowParamsDialog] = useState(false);
|
||||
const filtered = useGraphFilter(schema, filterParams, toggleDataUpdate);
|
||||
const filtered = useGraphFilter(schema, filterParams);
|
||||
|
||||
const [selectedGraph, setSelectedGraph] = useState<number[]>([]);
|
||||
const [hidden, setHidden] = useState<number[]>([]);
|
||||
const [selectedHidden, setSelectedHidden] = useState<number[]>([]);
|
||||
const selected: number[] = useMemo(() => {
|
||||
return [...selectedHidden, ...selectedGraph];
|
||||
}, [selectedHidden, selectedGraph]);
|
||||
|
||||
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
|
||||
|
||||
const [layout, setLayout] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
|
||||
|
@ -72,7 +76,6 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
}, [schema?.items, hoverID]);
|
||||
|
||||
const [toggleResetView, setToggleResetView] = useState(false);
|
||||
const [toggleResetSelection, setToggleResetSelection] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!schema) {
|
||||
|
@ -85,9 +88,8 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
}
|
||||
});
|
||||
setHidden(newDismissed);
|
||||
setSelectedHidden([]);
|
||||
setHoverID(undefined);
|
||||
}, [schema, filtered, toggleDataUpdate]);
|
||||
}, [schema, filtered]);
|
||||
|
||||
const nodes: GraphNode[] = useMemo(() => {
|
||||
const result: GraphNode[] = [];
|
||||
|
@ -123,15 +125,20 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
return result;
|
||||
}, [filtered.nodes]);
|
||||
|
||||
const handleGraphSelection = useCallback(
|
||||
(newID: number) => {
|
||||
setSelected(prev => [...prev, newID]);
|
||||
},
|
||||
[setSelected]
|
||||
);
|
||||
|
||||
function toggleDismissed(cstID: number) {
|
||||
setSelectedHidden(prev => {
|
||||
const index = prev.findIndex(id => cstID === id);
|
||||
if (index !== -1) {
|
||||
prev.splice(index, 1);
|
||||
setSelected(prev => {
|
||||
if (prev.includes(cstID)) {
|
||||
return [...prev.filter(id => id !== cstID)];
|
||||
} else {
|
||||
prev.push(cstID);
|
||||
return [...prev, cstID];
|
||||
}
|
||||
return [...prev];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -139,29 +146,15 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateData = {
|
||||
insert_after: null,
|
||||
cst_type: selected.length === 0 ? CstType.BASE : CstType.TERM,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: selected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '),
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
term_forms: []
|
||||
};
|
||||
onCreateCst(data);
|
||||
const definition = selected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' ');
|
||||
onCreate(selected.length === 0 ? CstType.BASE : CstType.TERM, definition);
|
||||
}
|
||||
|
||||
function handleDeleteCst() {
|
||||
if (!schema || selected.length === 0) {
|
||||
return;
|
||||
}
|
||||
onDeleteCst(selected, () => {
|
||||
setHidden([]);
|
||||
setSelectedHidden([]);
|
||||
setToggleResetSelection(prev => !prev);
|
||||
setToggleDataUpdate(prev => !prev);
|
||||
});
|
||||
onDelete();
|
||||
}
|
||||
|
||||
function handleChangeLayout(newLayout: LayoutTypes) {
|
||||
|
@ -206,8 +199,8 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
|
||||
<SelectedCounter
|
||||
hideZero
|
||||
total={schema?.stats?.count_all ?? 0}
|
||||
selected={selected.length}
|
||||
totalCount={schema?.stats?.count_all ?? 0}
|
||||
selectedCount={selected.length}
|
||||
position='top-[0.3rem] left-0'
|
||||
/>
|
||||
|
||||
|
@ -249,7 +242,7 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
/>
|
||||
<ViewHidden
|
||||
items={hidden}
|
||||
selected={selectedHidden}
|
||||
selected={selected}
|
||||
schema={schema!}
|
||||
coloringScheme={coloringScheme}
|
||||
toggleSelection={toggleDismissed}
|
||||
|
@ -260,15 +253,15 @@ function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Ed
|
|||
<TermGraph
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
selectedIDs={selected}
|
||||
layout={layout}
|
||||
is3D={is3D}
|
||||
orbit={orbit}
|
||||
setSelected={setSelectedGraph}
|
||||
onSelect={handleGraphSelection}
|
||||
setHoverID={setHoverID}
|
||||
onEdit={onOpenEdit}
|
||||
onDeselect={() => setSelectedHidden([])}
|
||||
onDeselectAll={() => setSelected([])}
|
||||
toggleResetView={toggleResetView}
|
||||
toggleResetSelection={toggleResetSelection}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,18 +10,18 @@ import { resources } from '@/utils/constants';
|
|||
interface TermGraphProps {
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
selectedIDs: number[];
|
||||
|
||||
layout: LayoutTypes;
|
||||
is3D: boolean;
|
||||
orbit: boolean;
|
||||
|
||||
setSelected: (selected: number[]) => void;
|
||||
setHoverID: (newID: number | undefined) => void;
|
||||
onEdit: (cstID: number) => void;
|
||||
onDeselect: () => void;
|
||||
onSelect: (newID: number) => void;
|
||||
onDeselectAll: () => void;
|
||||
|
||||
toggleResetView: boolean;
|
||||
toggleResetSelection: boolean;
|
||||
}
|
||||
|
||||
const TREE_SIZE_MILESTONE = 50;
|
||||
|
@ -29,21 +29,20 @@ const TREE_SIZE_MILESTONE = 50;
|
|||
function TermGraph({
|
||||
nodes,
|
||||
edges,
|
||||
selectedIDs,
|
||||
layout,
|
||||
is3D,
|
||||
orbit,
|
||||
toggleResetView,
|
||||
toggleResetSelection,
|
||||
setHoverID,
|
||||
onEdit,
|
||||
setSelected,
|
||||
onDeselect
|
||||
onSelect,
|
||||
onDeselectAll
|
||||
}: TermGraphProps) {
|
||||
const { noNavigation, darkMode } = useConceptTheme();
|
||||
const graphRef = useRef<GraphCanvasRef | null>(null);
|
||||
|
||||
const { selections, actives, onNodeClick, clearSelections, onCanvasClick, onNodePointerOver, onNodePointerOut } =
|
||||
useSelection({
|
||||
const { selections, actives, setSelections, onCanvasClick, onNodePointerOver, onNodePointerOut } = useSelection({
|
||||
ref: graphRef,
|
||||
nodes,
|
||||
edges,
|
||||
|
@ -73,19 +72,19 @@ function TermGraph({
|
|||
(node: GraphNode) => {
|
||||
if (selections.includes(node.id)) {
|
||||
onEdit(Number(node.id));
|
||||
return;
|
||||
} else {
|
||||
onSelect(Number(node.id));
|
||||
}
|
||||
if (onNodeClick) onNodeClick(node);
|
||||
},
|
||||
[onNodeClick, selections, onEdit]
|
||||
[onSelect, selections, onEdit]
|
||||
);
|
||||
|
||||
const handleCanvasClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
onDeselect();
|
||||
onDeselectAll();
|
||||
if (onCanvasClick) onCanvasClick(event);
|
||||
},
|
||||
[onCanvasClick, onDeselect]
|
||||
[onCanvasClick, onDeselectAll]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
@ -94,12 +93,9 @@ function TermGraph({
|
|||
}, [toggleResetView]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
clearSelections();
|
||||
}, [toggleResetSelection, clearSelections]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setSelected(selections.map(id => Number(id)));
|
||||
}, [selections, setSelected]);
|
||||
const newSelections = nodes.filter(node => selectedIDs.includes(Number(node.id))).map(node => node.id);
|
||||
setSelections(newSelections);
|
||||
}, [selectedIDs, setSelections, nodes]);
|
||||
|
||||
const canvasWidth = useMemo(() => {
|
||||
return 'calc(100vw - 1.1rem)';
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Graph } from '@/models/Graph';
|
||||
import { GraphFilterParams } from '@/models/miscellaneous';
|
||||
import { CstType, IRSForm } from '@/models/rsform';
|
||||
import { Graph } from '@/models/Graph';
|
||||
|
||||
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams, toggleUpdate: boolean) {
|
||||
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams) {
|
||||
const [filtered, setFiltered] = useState<Graph>(new Graph());
|
||||
|
||||
const allowedTypes: CstType[] = useMemo(() => {
|
||||
|
@ -47,7 +47,7 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams,
|
|||
});
|
||||
}
|
||||
setFiltered(graph);
|
||||
}, [schema, params, allowedTypes, toggleUpdate]);
|
||||
}, [schema, params, allowedTypes]);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
|||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
||||
import useQueryStrings from '@/hooks/useQueryStrings';
|
||||
import { UserAccessMode } from '@/models/miscellaneous';
|
||||
import { IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '@/models/rsform';
|
||||
import { CstType, IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '@/models/rsform';
|
||||
import { generateAlias } from '@/models/rsformAPI';
|
||||
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '@/utils/constants';
|
||||
|
||||
|
@ -85,14 +85,18 @@ function RSTabs() {
|
|||
);
|
||||
}, [user?.is_staff, mode, isOwned, loading, processing]);
|
||||
|
||||
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
||||
const activeCst = useMemo(() => schema?.items?.find(cst => cst.id === activeID), [schema?.items, activeID]);
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
const activeCst: IConstituenta | undefined = useMemo(() => {
|
||||
if (!schema || selected.length === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return schema.items.find(cst => cst.id === selected.at(-1));
|
||||
}
|
||||
}, [schema, selected]);
|
||||
|
||||
const [showUpload, setShowUpload] = useState(false);
|
||||
const [showClone, setShowClone] = useState(false);
|
||||
|
||||
const [afterDelete, setAfterDelete] = useState<((items: number[]) => void) | undefined>(undefined);
|
||||
const [toBeDeleted, setToBeDeleted] = useState<number[]>([]);
|
||||
const [showDeleteCst, setShowDeleteCst] = useState(false);
|
||||
|
||||
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
|
||||
|
@ -118,10 +122,19 @@ function RSTabs() {
|
|||
|
||||
useLayoutEffect(() => {
|
||||
setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST);
|
||||
setActiveID(Number(cstQuery) ?? (schema && schema?.items.length > 0 ? schema.items[0].id : undefined));
|
||||
setIsModified(false);
|
||||
if (activeTab === RSTabID.CST_EDIT) {
|
||||
const cstID = Number(cstQuery);
|
||||
if (cstID && schema && schema.items.find(cst => cst.id === cstID)) {
|
||||
setSelected([cstID]);
|
||||
} else if (schema && schema?.items.length > 0) {
|
||||
setSelected([schema.items[0].id]);
|
||||
} else {
|
||||
setSelected([]);
|
||||
}
|
||||
}
|
||||
return () => setNoFooter(false);
|
||||
}, [activeTab, cstQuery, setActiveID, schema, setNoFooter, setIsModified]);
|
||||
}, [activeTab, cstQuery, setSelected, schema, setNoFooter, setIsModified]);
|
||||
|
||||
useLayoutEffect(
|
||||
() =>
|
||||
|
@ -137,10 +150,6 @@ function RSTabs() {
|
|||
[schema, setMode, isOwned]
|
||||
);
|
||||
|
||||
function onSelectTab(index: number) {
|
||||
navigateTab(index, activeID);
|
||||
}
|
||||
|
||||
const navigateTab = useCallback(
|
||||
(tab: RSTabID, activeID?: number) => {
|
||||
if (!schema) {
|
||||
|
@ -162,6 +171,10 @@ function RSTabs() {
|
|||
[router, schema, activeTab]
|
||||
);
|
||||
|
||||
function onSelectTab(index: number) {
|
||||
navigateTab(index, selected.length > 0 ? selected.at(-1) : undefined);
|
||||
}
|
||||
|
||||
const handleCreateCst = useCallback(
|
||||
(data: ICstCreateData) => {
|
||||
if (!schema?.items) {
|
||||
|
@ -189,17 +202,44 @@ function RSTabs() {
|
|||
);
|
||||
|
||||
const promptCreateCst = useCallback(
|
||||
(initialData: ICstCreateData, skipDialog?: boolean) => {
|
||||
(type: CstType | undefined, skipDialog: boolean, definition?: string) => {
|
||||
const data: ICstCreateData = {
|
||||
insert_after: activeCst?.id ?? null,
|
||||
cst_type: type ?? activeCst?.cst_type ?? CstType.BASE,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: definition ?? '',
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
term_forms: []
|
||||
};
|
||||
if (skipDialog) {
|
||||
handleCreateCst(initialData);
|
||||
handleCreateCst(data);
|
||||
} else {
|
||||
setCreateInitialData(initialData);
|
||||
setCreateInitialData(data);
|
||||
setShowCreateCst(true);
|
||||
}
|
||||
},
|
||||
[handleCreateCst]
|
||||
[handleCreateCst, activeCst]
|
||||
);
|
||||
|
||||
const handleCloneCst = useCallback(() => {
|
||||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateData = {
|
||||
insert_after: activeCst.id,
|
||||
cst_type: activeCst.cst_type,
|
||||
alias: '',
|
||||
term_raw: activeCst.term_raw,
|
||||
definition_formal: activeCst.definition_formal,
|
||||
definition_raw: activeCst.definition_raw,
|
||||
convention: activeCst.convention,
|
||||
term_forms: activeCst.term_forms
|
||||
};
|
||||
handleCreateCst(data);
|
||||
}, [activeCst, handleCreateCst]);
|
||||
|
||||
const handleRenameCst = useCallback(
|
||||
(data: ICstRenameData) => {
|
||||
cstRename(data, () => toast.success(`Переименование: ${renameInitialData!.alias} -> ${data.alias}`));
|
||||
|
@ -207,10 +247,18 @@ function RSTabs() {
|
|||
[cstRename, renameInitialData]
|
||||
);
|
||||
|
||||
const promptRenameCst = useCallback((initialData: ICstRenameData) => {
|
||||
setRenameInitialData(initialData);
|
||||
const promptRenameCst = useCallback(() => {
|
||||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
const data: ICstRenameData = {
|
||||
id: activeCst.id,
|
||||
alias: activeCst.alias,
|
||||
cst_type: activeCst.cst_type
|
||||
};
|
||||
setRenameInitialData(data);
|
||||
setShowRenameCst(true);
|
||||
}, []);
|
||||
}, [activeCst]);
|
||||
|
||||
const onReindex = useCallback(() => resetAliases(() => toast.success('Имена конституент обновлены')), [resetAliases]);
|
||||
|
||||
|
@ -225,33 +273,26 @@ function RSTabs() {
|
|||
|
||||
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
|
||||
const isEmpty = deleted.length === schema.items.length;
|
||||
const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeID, schema.items, deleted);
|
||||
const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeCst?.id, schema.items, deleted);
|
||||
|
||||
cstDelete(data, () => {
|
||||
toast.success(`Конституенты удалены: ${deletedNames}`);
|
||||
if (isEmpty) {
|
||||
navigateTab(RSTabID.CST_LIST);
|
||||
} else if (!nextActive) {
|
||||
navigateTab(activeTab);
|
||||
} else {
|
||||
} else if (activeTab === RSTabID.CST_EDIT) {
|
||||
navigateTab(activeTab, nextActive);
|
||||
} else {
|
||||
setSelected(nextActive ? [nextActive] : []);
|
||||
navigateTab(activeTab);
|
||||
}
|
||||
if (afterDelete) afterDelete(deleted);
|
||||
});
|
||||
},
|
||||
[afterDelete, cstDelete, schema, activeID, activeTab, navigateTab]
|
||||
[cstDelete, schema, activeTab, activeCst, navigateTab]
|
||||
);
|
||||
|
||||
const promptDeleteCst = useCallback((selected: number[], callback?: (items: number[]) => void) => {
|
||||
setAfterDelete(() => (items: number[]) => {
|
||||
if (callback) callback(items);
|
||||
});
|
||||
setToBeDeleted(selected);
|
||||
setShowDeleteCst(true);
|
||||
}, []);
|
||||
|
||||
const onOpenCst = useCallback(
|
||||
(cstID: number) => {
|
||||
setSelected([cstID]);
|
||||
navigateTab(RSTabID.CST_EDIT, cstID);
|
||||
},
|
||||
[navigateTab]
|
||||
|
@ -334,16 +375,16 @@ function RSTabs() {
|
|||
|
||||
const handleSaveWordforms = useCallback(
|
||||
(forms: TermForm[]) => {
|
||||
if (!activeID) {
|
||||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
const data: ICstUpdateData = {
|
||||
id: activeID,
|
||||
id: activeCst.id,
|
||||
term_forms: forms
|
||||
};
|
||||
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
||||
},
|
||||
[cstUpdate, activeID]
|
||||
[cstUpdate, activeCst]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -371,7 +412,7 @@ function RSTabs() {
|
|||
schema={schema!}
|
||||
hideWindow={() => setShowDeleteCst(false)}
|
||||
onDelete={handleDeleteCst}
|
||||
selected={toBeDeleted}
|
||||
selected={selected}
|
||||
/>
|
||||
) : null}
|
||||
{showEditTerm ? (
|
||||
|
@ -438,34 +479,41 @@ function RSTabs() {
|
|||
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '' : 'none' }}>
|
||||
<EditorRSList
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onClone={handleCloneCst}
|
||||
onCreate={type => promptCreateCst(type, type !== undefined)}
|
||||
onDelete={() => setShowDeleteCst(true)}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '' : 'none' }}>
|
||||
<EditorConstituenta
|
||||
schema={schema}
|
||||
isMutable={isMutable}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
activeID={activeID}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onRenameCst={promptRenameCst}
|
||||
onClone={handleCloneCst}
|
||||
onCreate={type => promptCreateCst(type, false)}
|
||||
onDelete={() => setShowDeleteCst(true)}
|
||||
onRename={promptRenameCst}
|
||||
onEditTerm={promptShowEditTerm}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '' : 'none' }}>
|
||||
<EditorTermGraph
|
||||
schema={schema}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
isMutable={isMutable}
|
||||
onOpenEdit={onOpenCst}
|
||||
onCreateCst={promptCreateCst}
|
||||
onDeleteCst={promptDeleteCst}
|
||||
onCreate={(type, definition) => promptCreateCst(type, false, definition)}
|
||||
onDelete={() => setShowDeleteCst(true)}
|
||||
/>
|
||||
</TabPanel>
|
||||
</AnimateFade>
|
||||
|
|
|
@ -118,6 +118,7 @@ function RSTabsMenu({
|
|||
dense
|
||||
tabIndex={-1}
|
||||
title='Меню'
|
||||
hideTitle={schemaMenu.isOpen}
|
||||
icon={<BiMenu size='1.25rem' className='clr-text-controls' />}
|
||||
className='h-full pl-2'
|
||||
style={{ outlineColor: 'transparent' }}
|
||||
|
@ -172,6 +173,7 @@ function RSTabsMenu({
|
|||
noBorder
|
||||
tabIndex={-1}
|
||||
title={'Редактирование'}
|
||||
hideTitle={editMenu.isOpen}
|
||||
className='h-full'
|
||||
style={{ outlineColor: 'transparent' }}
|
||||
icon={<FiEdit size='1.25rem' className={isMutable ? 'clr-text-success' : 'clr-text-warning'} />}
|
||||
|
@ -201,6 +203,7 @@ function RSTabsMenu({
|
|||
noBorder
|
||||
tabIndex={-1}
|
||||
title={`Режим ${labelAccessMode(mode)}`}
|
||||
hideTitle={accessMenu.isOpen}
|
||||
className='h-full pr-2'
|
||||
style={{ outlineColor: 'transparent' }}
|
||||
icon={
|
||||
|
|
|
@ -82,6 +82,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
transparent
|
||||
tabIndex={-1}
|
||||
title='Настройка атрибутов для фильтрации'
|
||||
hideTitle={matchModeMenu.isOpen}
|
||||
className='h-full'
|
||||
icon={<BiFilterAlt size='1.25rem' />}
|
||||
text={labelCstMatchMode(filterMatch)}
|
||||
|
@ -94,6 +95,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
const matchMode = value as CstMatchMode;
|
||||
return (
|
||||
<DropdownButton
|
||||
className='w-[22rem]'
|
||||
key={`${prefixes.cst_match_mode_list}${index}`}
|
||||
onClick={() => handleMatchModeChange(matchMode)}
|
||||
>
|
||||
|
@ -111,6 +113,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
transparent
|
||||
tabIndex={-1}
|
||||
title='Настройка фильтрации по графу термов'
|
||||
hideTitle={sourceMenu.isOpen}
|
||||
className='h-full pr-2'
|
||||
icon={<BiCog size='1.25rem' />}
|
||||
text={labelCstSource(filterSource)}
|
||||
|
@ -122,7 +125,11 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
|
|||
.map((value, index) => {
|
||||
const source = value as DependencyMode;
|
||||
return (
|
||||
<DropdownButton key={`${prefixes.cst_source_list}${index}`} onClick={() => handleSourceChange(source)}>
|
||||
<DropdownButton
|
||||
className='w-[23rem]'
|
||||
key={`${prefixes.cst_source_list}${index}`}
|
||||
onClick={() => handleSourceChange(source)}
|
||||
>
|
||||
<p>
|
||||
<b>{labelCstSource(source)}:</b> {describeCstSource(source)}
|
||||
</p>
|
||||
|
|
Loading…
Reference in New Issue
Block a user