diff --git a/rsconcept/frontend/src/components/ConstituentaMultiPicker.tsx b/rsconcept/frontend/src/components/ConstituentaMultiPicker.tsx new file mode 100644 index 00000000..f86275da --- /dev/null +++ b/rsconcept/frontend/src/components/ConstituentaMultiPicker.tsx @@ -0,0 +1,145 @@ +'use client'; + +import clsx from 'clsx'; +import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; + +import DataTable, { createColumnHelper, RowSelectionState } from '@/components/DataTable'; +import { useConceptTheme } from '@/context/ThemeContext'; +import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; +import { describeConstituenta } from '@/utils/labels'; + +import ConstituentaBadge from './ConstituentaBadge'; +import Button from './ui/Button'; +import FlexColumn from './ui/FlexColumn'; + +interface ConstituentaMultiPickerProps { + id?: string; + schema?: IRSForm; + prefixID: string; + rows?: number; + + selected: ConstituentaID[]; + setSelected: React.Dispatch; +} + +const columnHelper = createColumnHelper(); + +function ConstituentaMultiPicker({ id, schema, prefixID, rows, selected, setSelected }: ConstituentaMultiPickerProps) { + const { colors } = useConceptTheme(); + const [rowSelection, setRowSelection] = useState({}); + + useLayoutEffect(() => { + 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) { + if (!schema) { + setSelected([]); + } else { + const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater; + const newSelection: ConstituentaID[] = []; + schema.items.forEach((cst, index) => { + if (newRowSelection[String(index)] === true) { + newSelection.push(cst.id); + } + }); + setSelected(newSelection); + } + } + + const selectBasis = useCallback(() => { + if (!schema || selected.length === 0) { + return; + } + const addition = schema.graph.expandInputs(selected).filter(id => !selected.includes(id)); + if (addition.length > 0) { + setSelected([...selected, ...addition]); + } + }, [schema, selected, setSelected]); + + const selectDependant = useCallback(() => { + if (!schema || selected.length === 0) { + return; + } + const addition = schema.graph.expandOutputs(selected).filter(id => !selected.includes(id)); + if (addition.length > 0) { + setSelected([...selected, ...addition]); + } + }, [schema, selected, setSelected]); + + const columns = useMemo( + () => [ + columnHelper.accessor('alias', { + id: 'alias', + header: 'Имя', + size: 65, + cell: props => + }), + columnHelper.accessor(cst => describeConstituenta(cst), { + id: 'description', + size: 1000, + header: 'Описание' + }) + ], + [colors, prefixID] + ); + + return ( +
+
+ + Выбраны {selected.length} из {schema?.items.length ?? 0} + +
+
+
+ +

Список пуст

+ + } + /> +
+ ); +} + +export default ConstituentaMultiPicker; diff --git a/rsconcept/frontend/src/components/ConstituentaPicker.tsx b/rsconcept/frontend/src/components/ConstituentaPicker.tsx index 72f8921c..97c94bd2 100644 --- a/rsconcept/frontend/src/components/ConstituentaPicker.tsx +++ b/rsconcept/frontend/src/components/ConstituentaPicker.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useEffect, useMemo, useState } from 'react'; import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/DataTable'; @@ -14,7 +16,7 @@ import FlexColumn from './ui/FlexColumn'; interface ConstituentaPickerProps { id?: string; - prefixID?: string; + prefixID: string; data?: IConstituenta[]; rows?: number; @@ -74,8 +76,6 @@ function ConstituentaPicker({ [colors, prefixID, describeFunc] ); - const size = useMemo(() => `calc(2px + (2px + 1.8rem)*${rows})`, [rows]); - const conditionalRowStyles = useMemo( (): IConditionalStyle[] => [ { @@ -96,11 +96,12 @@ function ConstituentaPicker({ /> {isLoading ? : null} {error ? : null} {!isLoading && !error && !hasNoData ? ( - + {children} ) : null} + {!isLoading && !error && hasNoData ? ( + + Данные не загружены + + ) : null} ); } diff --git a/rsconcept/frontend/src/components/DataTable/DataTable.tsx b/rsconcept/frontend/src/components/DataTable/DataTable.tsx index 5bc0746d..d285a891 100644 --- a/rsconcept/frontend/src/components/DataTable/DataTable.tsx +++ b/rsconcept/frontend/src/components/DataTable/DataTable.tsx @@ -36,6 +36,7 @@ export interface DataTableProps id?: string; dense?: boolean; rows?: number; + contentHeight?: string; headPosition?: string; noHeader?: boolean; noFooter?: boolean; @@ -73,6 +74,7 @@ function DataTable({ className, dense, rows, + contentHeight = '1.1875rem', headPosition, conditionalRowStyles, noFooter, @@ -130,11 +132,11 @@ function DataTable({ return undefined; } if (dense) { - return `calc(2px + (2px + 1.6875rem)*${rows} + ${noHeader ? '0px' : '(2px + 2.1875rem)'})`; + return `calc(2px + (2px + ${contentHeight} + 0.5rem)*${rows} + ${noHeader ? '0px' : '(2px + 2.1875rem)'})`; } else { - return `calc(2px + (2px + 2.1875rem)*${rows + (noHeader ? 0 : 1)})`; + return `calc(2px + (2px + ${contentHeight} + 1rem)*${rows + (noHeader ? 0 : 1)})`; } - }, [rows, dense, noHeader]); + }, [rows, dense, noHeader, contentHeight]); return (
diff --git a/rsconcept/frontend/src/components/RSInput/RSInput.tsx b/rsconcept/frontend/src/components/RSInput/RSInput.tsx index bb3be7be..b0d47e61 100644 --- a/rsconcept/frontend/src/components/RSInput/RSInput.tsx +++ b/rsconcept/frontend/src/components/RSInput/RSInput.tsx @@ -19,34 +19,6 @@ import { RSLanguage } from './rslang'; import { getSymbolSubstitute, RSTextWrapper } from './textEditing'; import { rsHoverTooltip } from './tooltip'; -const editorSetup: BasicSetupOptions = { - highlightSpecialChars: false, - history: true, - drawSelection: true, - syntaxHighlighting: false, - defaultKeymap: true, - historyKeymap: true, - - lineNumbers: false, - highlightActiveLineGutter: false, - foldGutter: false, - dropCursor: true, - allowMultipleSelections: false, - indentOnInput: false, - bracketMatching: false, - closeBrackets: false, - autocompletion: false, - rectangularSelection: false, - crosshairCursor: false, - highlightActiveLine: false, - highlightSelectionMatches: false, - closeBracketsKeymap: false, - searchKeymap: false, - foldKeymap: false, - completionKeymap: false, - lintKeymap: false -}; - interface RSInputProps extends Pick< ReactCodeMirrorProps, @@ -60,7 +32,22 @@ interface RSInputProps } const RSInput = forwardRef( - ({ id, label, onChange, onAnalyze, disabled, noTooltip, className, style, ...restProps }, ref) => { + ( + { + id, // prettier: split lines + label, + disabled, + noTooltip, + + className, + style, + + onChange, + onAnalyze, + ...restProps + }, + ref + ) => { const { darkMode, colors } = useConceptTheme(); const { schema } = useRSForm(); @@ -167,3 +154,32 @@ const RSInput = forwardRef( ); export default RSInput; + +// ======= Internal ========== +const editorSetup: BasicSetupOptions = { + highlightSpecialChars: false, + history: true, + drawSelection: true, + syntaxHighlighting: false, + defaultKeymap: true, + historyKeymap: true, + + lineNumbers: false, + highlightActiveLineGutter: false, + foldGutter: false, + dropCursor: true, + allowMultipleSelections: false, + indentOnInput: false, + bracketMatching: false, + closeBrackets: false, + autocompletion: false, + rectangularSelection: false, + crosshairCursor: false, + highlightActiveLine: false, + highlightSelectionMatches: false, + closeBracketsKeymap: false, + searchKeymap: false, + foldKeymap: false, + completionKeymap: false, + lintKeymap: false +}; diff --git a/rsconcept/frontend/src/dialogs/DlgCreateCst.tsx b/rsconcept/frontend/src/dialogs/DlgCreateCst.tsx index 358f01cf..c76a8926 100644 --- a/rsconcept/frontend/src/dialogs/DlgCreateCst.tsx +++ b/rsconcept/frontend/src/dialogs/DlgCreateCst.tsx @@ -76,7 +76,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro id='dlg_cst_term' spellCheck label='Термин' - placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение' + placeholder='Обозначение, используемое в текстовых определениях' rows={2} value={cstData.term_raw} onChange={event => updateCstData({ term_raw: event.target.value })} @@ -84,8 +84,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro updateCstData({ definition_formal: value })} /> @@ -93,7 +92,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro id='dlg_cst_definition' spellCheck label='Текстовое определение' - placeholder='Лингвистическая интерпретация формального выражения' + placeholder='Текстовая интерпретация формального выражения' rows={2} value={cstData.definition_raw} onChange={event => updateCstData({ definition_raw: event.target.value })} diff --git a/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/ConstituentsTab.tsx b/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/ConstituentsTab.tsx index b1a125d1..5c5d2c8b 100644 --- a/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/ConstituentsTab.tsx +++ b/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/ConstituentsTab.tsx @@ -1,18 +1,31 @@ 'use client'; -import { LibraryItemID } from '@/models/library'; -import { IRSForm } from '@/models/rsform'; +import ConstituentaMultiPicker from '@/components/ConstituentaMultiPicker'; +import DataLoader from '@/components/DataLoader'; +import { ErrorData } from '@/components/InfoError'; +import { ConstituentaID, IRSForm } from '@/models/rsform'; +import { prefixes } from '@/utils/constants'; interface ConstituentsTabProps { schema?: IRSForm; loading?: boolean; - selected: LibraryItemID[]; - setSelected: React.Dispatch; + error?: ErrorData; + selected: ConstituentaID[]; + setSelected: React.Dispatch; } -// { schema, loading, selected, setSelected }: ConstituentsTabProps -function ConstituentsTab(props: ConstituentsTabProps) { - return <>2 - {props.loading}; +function ConstituentsTab({ schema, error, loading, selected, setSelected }: ConstituentsTabProps) { + return ( + + + + ); } export default ConstituentsTab; diff --git a/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/SchemaTab.tsx b/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/SchemaTab.tsx index 202b07e4..33bb80e5 100644 --- a/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/SchemaTab.tsx +++ b/rsconcept/frontend/src/dialogs/DlgInlineSynthesis/SchemaTab.tsx @@ -3,7 +3,6 @@ import { useMemo } from 'react'; import SchemaPicker from '@/components/SchemaPicker'; -import FlexColumn from '@/components/ui/FlexColumn'; import TextInput from '@/components/ui/TextInput'; import { useLibrary } from '@/context/LibraryContext'; import { LibraryItemID } from '@/models/library'; @@ -18,18 +17,21 @@ function SchemaTab({ selected, setSelected }: SchemaTabProps) { const selectedInfo = useMemo(() => library.items.find(item => item.id === selected), [selected, library.items]); return ( - - +
+
+ Выбрана + +
- +
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx index 5838185b..a6d5ae42 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx @@ -128,7 +128,7 @@ function FormConstituenta({ ({}); const controller = useRSEdit(); @@ -91,6 +93,8 @@ function EditorRSList({ selected, setSelected, onOpenEdit }: EditorRSListProps) return false; } + const tableHeight = useMemo(() => calculateHeight('4.05rem + 5px'), [calculateHeight]); + return (
{controller.isContentEditable || controller.isProcessing ? ( @@ -112,8 +116,9 @@ function EditorRSList({ selected, setSelected, onOpenEdit }: EditorRSListProps) /> >; @@ -29,8 +30,8 @@ const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800; const columnHelper = createColumnHelper(); -function RSTable({ items, enableSelection, selected, setSelected, onEdit, onCreateNew }: RSTableProps) { - const { colors, calculateHeight } = useConceptTheme(); +function RSTable({ items, maxHeight, enableSelection, selected, setSelected, onEdit, onCreateNew }: RSTableProps) { + const { colors } = useConceptTheme(); const windowSize = useWindowSize(); const [columnVisibility, setColumnVisibility] = useState({}); @@ -115,14 +116,12 @@ function RSTable({ items, enableSelection, selected, setSelected, onEdit, onCrea [colors] ); - const tableHeight = useMemo(() => calculateHeight('4.05rem + 5px'), [calculateHeight]); - return (