diff --git a/rsconcept/frontend/src/components/Common/Modal.tsx b/rsconcept/frontend/src/components/Common/Modal.tsx index eccdec55..f144ec7c 100644 --- a/rsconcept/frontend/src/components/Common/Modal.tsx +++ b/rsconcept/frontend/src/components/Common/Modal.tsx @@ -35,17 +35,16 @@ function Modal({ if (onSubmit) onSubmit(); }; - return ( - <> + return (<>
- {title ?

{title}

: null} + {title ?

{title}

: null}
{children}
-
+
{!readonly ?
- - ); + ); } export default Modal; diff --git a/rsconcept/frontend/src/components/Help/HelpTerminologyControl.tsx b/rsconcept/frontend/src/components/Help/HelpTerminologyControl.tsx index 9a5c3d7d..1cb2ac4d 100644 --- a/rsconcept/frontend/src/components/Help/HelpTerminologyControl.tsx +++ b/rsconcept/frontend/src/components/Help/HelpTerminologyControl.tsx @@ -1,7 +1,7 @@ function HelpTerminologyControl() { return ( -
+

Терминологизация: Контроль терминологии

Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.

Для этого используется механизм текстовых отсылок: использование термина и связывание слов.

diff --git a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx index ee6075bd..90bc8170 100644 --- a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx +++ b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx @@ -3,7 +3,7 @@ import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react'; import MiniButton from '../../components/Common/MiniButton'; import DataTable, { IConditionalStyle } from '../../components/DataTable'; -import { CheckIcon, CrossIcon } from '../../components/Icons'; +import { ArrowsRotateIcon, CheckIcon, CrossIcon } from '../../components/Icons'; import RSInput from '../../components/RSInput'; import ConstituentaPicker from '../../components/Shared/ConstituentaPicker'; import { useConceptTheme } from '../../context/ThemeContext'; @@ -32,6 +32,15 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) { const [argumentValue, setArgumentValue] = useState(''); + const selectedClearable = useMemo( + () => { + return argumentValue && !!selectedArgument && !!selectedArgument.value; + }, [argumentValue, selectedArgument]); + + const isModified = useMemo( + () => (selectedArgument && argumentValue !== selectedArgument.value), + [selectedArgument, argumentValue]); + useEffect( () => { if (!selectedArgument && state.arguments.length > 0) { @@ -55,24 +64,25 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) { const handleClearArgument = useCallback( (target: IArgumentValue) => { - target.value = ''; + const newArg = { ...target, value: '' } partialUpdate({ - arguments: [ - target, - ...state.arguments.filter(arg => arg.alias !== target.alias) - ] + arguments: state.arguments.map((arg) => (arg.alias !== target.alias ? arg : newArg)) }); + setSelectedArgument(newArg); }, [partialUpdate, state.arguments]); + const handleReset = useCallback( + () => { + setArgumentValue(selectedArgument?.value ?? ''); + }, [selectedArgument]); + const handleAssignArgument = useCallback( (target: IArgumentValue, value: string) => { - target.value = value; + const newArg = { ...target, value: value } partialUpdate({ - arguments: [ - target, - ...state.arguments.filter(arg => arg.alias !== target.alias) - ] + arguments: state.arguments.map((arg) => (arg.alias !== target.alias ? arg : newArg)) }); + setSelectedArgument(newArg); }, [partialUpdate, state.arguments]); const columns = useMemo( @@ -148,21 +158,31 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) { {selectedArgument?.alias || 'ARG'} = - setArgumentValue(newValue)} /> - } - disabled={!argumentValue || !selectedArgument} - onClick={() => handleAssignArgument(selectedArgument!, argumentValue)} - /> +
+ } + disabled={!argumentValue || !selectedArgument} + onClick={() => handleAssignArgument(selectedArgument!, argumentValue)} + /> + } + /> + } + onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined} + /> +
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx index 602fbe04..34c99b5a 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx @@ -27,7 +27,7 @@ function ConstituentaToolbar({ return (
-
+
} @@ -67,7 +67,10 @@ function ConstituentaToolbar({
- +
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx index e4c1daaa..1784551a 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx @@ -1,22 +1,11 @@ -import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; +import { useLayoutEffect, useState } from 'react'; import { toast } from 'react-toastify'; -import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../../components/DataTable'; -import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge'; +import { type RowSelectionState } from '../../../components/DataTable'; import { useRSForm } from '../../../context/RSFormContext'; -import { useConceptTheme } from '../../../context/ThemeContext'; -import useWindowSize from '../../../hooks/useWindowSize'; -import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../../models/rsform' -import { prefixes } from '../../../utils/constants'; -import { labelCstTypification } from '../../../utils/labels'; +import { CstType, ICstCreateData, ICstMovetoData } from '../../../models/rsform' import RSListToolbar from './RSListToolbar'; - -// Window width cutoff for columns -const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000; -const COLUMN_TYPE_HIDE_THRESHOLD = 1200; -const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800; - -const columnHelper = createColumnHelper(); +import RSTable from './RSTable'; interface EditorRSListProps { onOpenEdit: (cstID: number) => void @@ -26,13 +15,25 @@ interface EditorRSListProps { } function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) { - const { colors, noNavigation } = useConceptTheme(); - const windowSize = useWindowSize(); const { schema, editorMode: isEditable, cstMoveTo, resetAliases } = useRSForm(); const [selected, setSelected] = useState([]); - + const [rowSelection, setRowSelection] = useState({}); - const [columnVisibility, setColumnVisibility] = useState({}) + + useLayoutEffect( + () => { + if (!schema || Object.keys(rowSelection).length === 0) { + setSelected([]); + } else { + const selected: number[] = []; + schema.items.forEach((cst, index) => { + if (rowSelection[String(index)] === true) { + selected.push(cst.id); + } + }); + setSelected(selected); + } + }, [rowSelection, schema]); // Delete selected constituents function handleDelete() { @@ -179,123 +180,19 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi } } switch (code) { - case 'Backquote': handleCreateCst(); 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 'Backquote': handleCreateCst(); 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; } return false; } - const handleRowClicked = useCallback( - (cst: IConstituenta, event: React.MouseEvent) => { - if (event.altKey) { - event.preventDefault(); - onOpenEdit(cst.id); - } - }, [onOpenEdit]); - - const handleRowDoubleClicked = useCallback( - (cst: IConstituenta, event: React.MouseEvent) => { - event.preventDefault(); - onOpenEdit(cst.id); - }, [onOpenEdit]); - - useLayoutEffect( - () => { - setColumnVisibility({ - 'type': (windowSize.width ?? 0) >= COLUMN_TYPE_HIDE_THRESHOLD, - 'convention': (windowSize.width ?? 0) >= COLUMN_CONVENTION_HIDE_THRESHOLD, - 'definition': (windowSize.width ?? 0) >= COLUMN_DEFINITION_HIDE_THRESHOLD - }); - }, [windowSize]); - - useLayoutEffect( - () => { - if (!schema || Object.keys(rowSelection).length === 0) { - setSelected([]); - } else { - const selected: number[] = []; - schema.items.forEach((cst, index) => { - if (rowSelection[String(index)] === true) { - selected.push(cst.id); - } - }); - setSelected(selected); - } - }, [rowSelection, schema]); - - const columns = useMemo( - () => [ - columnHelper.accessor('alias', { - id: 'alias', - header: 'Имя', - size: 65, - minSize: 65, - maxSize: 65, - cell: props => - - }), - columnHelper.accessor(cst => labelCstTypification(cst), { - id: 'type', - header: 'Типизация', - size: 150, - minSize: 150, - maxSize: 150, - enableHiding: true, - cell: props =>
{props.getValue()}
- }), - columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', { - id: 'term', - header: 'Термин', - size: 500, - minSize: 150, - maxSize: 500 - }), - columnHelper.accessor('definition_formal', { - id: 'expression', - header: 'Формальное определение', - size: 1000, - minSize: 300, - maxSize: 1000, - cell: props =>
{props.getValue()}
- }), - columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', { - id: 'definition', - header: 'Текстовое определение', - size: 1000, - minSize: 200, - maxSize: 1000, - cell: props =>
{props.getValue()}
- }), - columnHelper.accessor('convention', { - id: 'convention', - header: 'Конвенция / Комментарий', - size: 500, - minSize: 100, - maxSize: 500, - enableHiding: true, - cell: props =>
{props.getValue()}
- }) - ], [colors]); - - const tableHeight = useMemo( - () => { - return !noNavigation ? - 'calc(100vh - 7.2rem - 4px)' - : 'calc(100vh - 4.4rem - 4px)'; - }, [noNavigation]); - return (
- -
- -

Список пуст

-

handleCreateCst()} - > - Создать новую конституенту -

- - } - /> -
+ handleCreateCst()} + />
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx new file mode 100644 index 00000000..f014ecec --- /dev/null +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx @@ -0,0 +1,159 @@ +import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; + +import DataTable, { createColumnHelper,RowSelectionState,VisibilityState } from '../../../components/DataTable'; +import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge'; +import { useConceptTheme } from '../../../context/ThemeContext'; +import useWindowSize from '../../../hooks/useWindowSize'; +import { IConstituenta } from '../../../models/rsform'; +import { prefixes } from '../../../utils/constants'; +import { labelCstTypification } from '../../../utils/labels'; + +interface RSTableProps { + items?: IConstituenta[] + selected: RowSelectionState + setSelected: React.Dispatch> + + onEdit: (cstID: number) => void + onCreateNew: () => void +} + +// Window width cutoff for columns +const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000; +const COLUMN_TYPE_HIDE_THRESHOLD = 1200; +const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800; + +const columnHelper = createColumnHelper(); + +function RSTable({ + items, selected, setSelected, + onEdit, onCreateNew +}: RSTableProps) { + const { colors, noNavigation } = useConceptTheme(); + const windowSize = useWindowSize(); + + const [columnVisibility, setColumnVisibility] = useState({}); + + + useLayoutEffect( + () => { + setColumnVisibility({ + 'type': (windowSize.width ?? 0) >= COLUMN_TYPE_HIDE_THRESHOLD, + 'convention': (windowSize.width ?? 0) >= COLUMN_CONVENTION_HIDE_THRESHOLD, + 'definition': (windowSize.width ?? 0) >= COLUMN_DEFINITION_HIDE_THRESHOLD + }); + }, [windowSize]); + + const handleRowClicked = useCallback( + (cst: IConstituenta, event: React.MouseEvent) => { + if (event.altKey) { + event.preventDefault(); + onEdit(cst.id); + } + }, [onEdit]); + + const handleRowDoubleClicked = useCallback( + (cst: IConstituenta, event: React.MouseEvent) => { + event.preventDefault(); + onEdit(cst.id); + }, [onEdit]); + + const columns = useMemo( + () => [ + columnHelper.accessor('alias', { + id: 'alias', + header: 'Имя', + size: 65, + minSize: 65, + maxSize: 65, + cell: props => + + }), + columnHelper.accessor(cst => labelCstTypification(cst), { + id: 'type', + header: 'Типизация', + size: 150, + minSize: 150, + maxSize: 150, + enableHiding: true, + cell: props =>
{props.getValue()}
+ }), + columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', { + id: 'term', + header: 'Термин', + size: 500, + minSize: 150, + maxSize: 500 + }), + columnHelper.accessor('definition_formal', { + id: 'expression', + header: 'Формальное определение', + size: 1000, + minSize: 300, + maxSize: 1000, + cell: props =>
{props.getValue()}
+ }), + columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', { + id: 'definition', + header: 'Текстовое определение', + size: 1000, + minSize: 200, + maxSize: 1000, + cell: props =>
{props.getValue()}
+ }), + columnHelper.accessor('convention', { + id: 'convention', + header: 'Конвенция / Комментарий', + size: 500, + minSize: 100, + maxSize: 500, + enableHiding: true, + cell: props =>
{props.getValue()}
+ }) + ], [colors]); + + const tableHeight = useMemo( + () => { + return !noNavigation ? + 'calc(100vh - 7.2rem - 4px)' + : 'calc(100vh - 4.4rem - 4px)'; + }, [noNavigation]); + + return ( +
+ +

Список пуст

+

onCreateNew()} + > + Создать новую конституенту +

+ + } + /> +
); +} + +export default RSTable; \ No newline at end of file