diff --git a/README.md b/README.md index 9995f0eb..89174466 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ This readme file is used mostly to document project dependencies - js-file-download - react-tabs - react-intl - - react-data-table-component - react-select - react-error-boundary - reagraph - react-tooltip + - @tanstack/react-table - @uiw/react-codemirror - @uiw/codemirror-themes - @lezer/lr diff --git a/rsconcept/frontend/package-lock.json b/rsconcept/frontend/package-lock.json index 1aab91c2..c81cc9ea 100644 --- a/rsconcept/frontend/package-lock.json +++ b/rsconcept/frontend/package-lock.json @@ -9,12 +9,12 @@ "version": "1.0.0", "dependencies": { "@lezer/lr": "^1.3.10", + "@tanstack/react-table": "^8.9.7", "@uiw/codemirror-themes": "^4.21.13", "@uiw/react-codemirror": "^4.21.13", "axios": "^1.5.0", "js-file-download": "^0.4.12", "react": "^18.2.0", - "react-data-table-component": "^7.5.4", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.11", "react-intl": "^6.4.4", @@ -3931,6 +3931,37 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.9.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.7.tgz", + "integrity": "sha512-UKUekM8JNUyWbjT1q3s1GpH5OtBL9mJ4258Il23fsahvkh3ou9TuFVmqI0/UPiFROgHkRlCBDNPUhcsC9YPFgg==", + "dependencies": { + "@tanstack/table-core": "8.9.7" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.9.7", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.9.7.tgz", + "integrity": "sha512-lkhVcGDxa9GSoDFPkplPDvzsiUACPZrxT3U1edPs0DCMKFhBDgZ7d1DPd7cqHH0JoybfbQ/qiTQYOQBg8sinJg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tweenjs/tween.js": { "version": "18.6.4", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", @@ -5477,6 +5508,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9318,18 +9350,6 @@ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-data-table-component": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-7.5.4.tgz", - "integrity": "sha512-6DGVj3urJZfEEMuP652fSjxdRVKeyb+9d0YounVc+MX8jwoyXQW6KO10eyZqElE9QtVrKrCeJxR7vht9yxyJiw==", - "dependencies": { - "deepmerge": "^4.2.2" - }, - "peerDependencies": { - "react": ">= 16.8.3", - "styled-components": ">= 4" - } - }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json index 85876372..792c9c80 100644 --- a/rsconcept/frontend/package.json +++ b/rsconcept/frontend/package.json @@ -13,12 +13,12 @@ }, "dependencies": { "@lezer/lr": "^1.3.10", + "@tanstack/react-table": "^8.9.7", "@uiw/codemirror-themes": "^4.21.13", "@uiw/react-codemirror": "^4.21.13", "axios": "^1.5.0", "js-file-download": "^0.4.12", "react": "^18.2.0", - "react-data-table-component": "^7.5.4", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.11", "react-intl": "^6.4.4", diff --git a/rsconcept/frontend/src/components/Common/Button.tsx b/rsconcept/frontend/src/components/Common/Button.tsx index c4458d47..b6ba3023 100644 --- a/rsconcept/frontend/src/components/Common/Button.tsx +++ b/rsconcept/frontend/src/components/Common/Button.tsx @@ -27,7 +27,7 @@ function Button({ disabled={disabled ?? loading} onClick={onClick} title={tooltip} - className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`} + className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colorClass} ${widthClass} ${borderClass} ${cursor}`} {...props} > {icon && {icon}} diff --git a/rsconcept/frontend/src/components/Common/Checkbox.tsx b/rsconcept/frontend/src/components/Common/Checkbox.tsx index 5d2f3f7a..d9d435aa 100644 --- a/rsconcept/frontend/src/components/Common/Checkbox.tsx +++ b/rsconcept/frontend/src/components/Common/Checkbox.tsx @@ -3,7 +3,8 @@ import { useMemo } from 'react'; import { CheckboxChecked } from '../Icons'; import Label from './Label'; -export interface CheckboxProps { +export interface CheckboxProps +extends Omit, 'className' | 'children' | 'title' | 'value' | 'onClick' > { id?: string label?: string required?: boolean @@ -15,7 +16,10 @@ export interface CheckboxProps { setValue?: (newValue: boolean) => void } -function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, setValue }: CheckboxProps) { +function Checkbox({ + id, required, disabled, tooltip, label, + widthClass = 'w-fit', value, setValue, ...props +}: CheckboxProps) { const cursor = useMemo( () => { if (disabled) { @@ -46,8 +50,11 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit' title={tooltip} disabled={disabled} onClick={handleClick} + {...props} > - + + { value && } + { label && } - {value && } ); } diff --git a/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx b/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx deleted file mode 100644 index 261e4cb1..00000000 --- a/rsconcept/frontend/src/components/Common/ConceptDataTable.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import DataTable, { createTheme, type TableProps } from 'react-data-table-component'; - -import { useConceptTheme } from '../../context/ThemeContext'; -import { dataTableDarkT, dataTableLightT } from '../../utils/color'; - -export interface SelectionInfo { - allSelected: boolean - selectedCount: number - selectedRows: T[] -} - -createTheme('customDark', dataTableDarkT, 'dark'); -createTheme('customLight', dataTableLightT, 'light'); - -interface ConceptDataTableProps -extends Omit, 'paginationComponentOptions'> {} - -function ConceptDataTable({ theme, ...props }: ConceptDataTableProps) { - const { darkMode } = useConceptTheme(); - - return ( - - theme={ theme ?? (darkMode ? 'customDark' : 'customLight')} - paginationComponentOptions={{ - rowsPerPageText: 'строк на страницу' - }} - {...props} - /> - ); -} - -export default ConceptDataTable; diff --git a/rsconcept/frontend/src/components/Common/ConceptSelectSingle.tsx b/rsconcept/frontend/src/components/Common/ConceptSelectSingle.tsx index f005d210..c4afef73 100644 --- a/rsconcept/frontend/src/components/Common/ConceptSelectSingle.tsx +++ b/rsconcept/frontend/src/components/Common/ConceptSelectSingle.tsx @@ -17,34 +17,27 @@ function ConceptSelectSingle< > ({ ...props }: ConceptSelectSingleProps) { const { darkMode, colors } = useConceptTheme(); const themeColors = useMemo( - () => { - return !darkMode ? selectLightT : selectDarkT; - }, [darkMode]); + () => !darkMode ? selectLightT : selectDarkT + , [darkMode]); const adjustedStyles: StylesConfig = useMemo( - () => { - return { - control: (styles, { isDisabled }) => { - return { - ...styles, - borderRadius: '0.25rem', - cursor: isDisabled ? 'not-allowed' : 'pointer' - }; - }, - option: (styles, { isSelected }) => { - return { - ...styles, - backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor, - color: isSelected ? colors.fgSelected : styles.color, - borderWidth: '1px', - borderColor: colors.border - }; - }, - input: (styles) => ({...styles}), - placeholder: (styles) => ({...styles}), - singleValue: (styles) => ({...styles}), - }; - }, [colors]); + () => ({ + control: (styles, { isDisabled }) => ({ + ...styles, + borderRadius: '0.25rem', + cursor: isDisabled ? 'not-allowed' : 'pointer' + }), + option: (styles, { isSelected }) => ({ + ...styles, + backgroundColor: isSelected ? colors.bgSelected : styles.backgroundColor, + color: isSelected ? colors.fgSelected : styles.color, + borderWidth: '1px', + borderColor: colors.border + }), + input: (styles) => ({...styles}), + placeholder: (styles) => ({...styles}), + singleValue: (styles) => ({...styles}), + }), [colors]); return ( +extends Omit, 'getCoreRowModel' | 'getSortedRowModel'| 'getPaginationRowModel'> { + onRowClicked?: (row: TData, event: React.MouseEvent) => void + onRowDoubleClicked?: (row: TData, event: React.MouseEvent) => void + noDataComponent?: React.ReactNode + pagination?: boolean +} + +function defaultNoDataComponent() { + return ( + + Данные отсутствуют + ); +} + +export default function DataTable({ + onRowClicked, onRowDoubleClicked, noDataComponent=defaultNoDataComponent(), + enableRowSelection, enableMultiRowSelection, + pagination, + ...options +}: DataTableProps) { + // const [sorting, setSorting] = React.useState([]) + + const tableImpl = useReactTable({ + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: options.enableSorting ? getSortedRowModel() : undefined, + getPaginationRowModel: pagination ? getPaginationRowModel() : undefined, + + state: { + ...options.state + }, + // onSortingChange: setSorting, + enableRowSelection: enableRowSelection, + enableMultiRowSelection: enableMultiRowSelection, + ...options + }); + + const isEmpty = tableImpl.getRowModel().rows.length === 0; + + return ( + + {isEmpty && noDataComponent} + {!isEmpty && + + + {tableImpl.getHeaderGroups().map( + (headerGroup: HeaderGroup) => ( + + {(enableRowSelection ?? enableMultiRowSelection) && + + tableImpl.toggleAllPageRowsSelected(value !== false)} + /> + + } + {headerGroup.headers.map( + (header: Header) => ( + 100 ? 'left': 'center', + width: header.getSize() + }} + > + {/* {header.isPlaceholder ? null : ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} + + )} */} + {header.isPlaceholder ? null + : flexRender(header.column.columnDef.header, header.getContext()) + } + + ))} + + ))} + + + + {tableImpl.getRowModel().rows.map( + (row: Row) => ( + + {(enableRowSelection ?? enableMultiRowSelection) && + + + + } + {row.getVisibleCells().map( + (cell: Cell) => ( + onRowClicked && onRowClicked(row.original, event)} + onDoubleClick={event => onRowDoubleClicked && onRowDoubleClicked(row.original, event)} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + + + + {tableImpl.getFooterGroups().map( + (footerGroup: HeaderGroup) => ( + + {footerGroup.headers.map( + (header: Header) => ( + + {header.isPlaceholder ? null + : flexRender(header.column.columnDef.footer, header.getContext()) + } + + ))} + + ))} + + } +{/* + + + table.setPageIndex(0)} + disabled={!table.getCanPreviousPage()} + > + {'<<'} + + table.previousPage()} + disabled={!table.getCanPreviousPage()} + > + {'<'} + + table.nextPage()} + disabled={!table.getCanNextPage()} + > + {'>'} + + table.setPageIndex(table.getPageCount() - 1)} + disabled={!table.getCanNextPage()} + > + {'>>'} + + + Page + + {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount()} + + + + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="w-16 p-1 border rounded" + /> + + { + table.setPageSize(Number(e.target.value)) + }} + > + {[10, 20, 30, 40, 50].map(pageSize => ( + + Show {pageSize} + + ))} + + + + rerender()} className="p-2 border"> + Rerender + + + ) */} + + ); +} + +// import { TableOptions, useReactTable } from '@tanstack/react-table' + +// import { useConceptTheme } from '../../context/ThemeContext'; +// import { dataTableDarkT, dataTableLightT } from '../../utils/color'; + +// export interface SelectionInfo { +// allSelected: boolean +// selectedCount: number +// selectedRows: T[] +// } + +// interface DataTableProps +// extends TableOptions{} + +// function DataTable({ ...props }: DataTableProps) { +// const { darkMode } = useConceptTheme(); +// const table = useReactTable(props); + +// return ( +// +// theme={ theme ?? (darkMode ? 'customDark' : 'customLight')} +// paginationComponentOptions={{ +// rowsPerPageText: 'строк на страницу' +// }} +// {...props} +// /> +// ); +// } + +// export default DataTable; diff --git a/rsconcept/frontend/src/components/Common/TextArea.tsx b/rsconcept/frontend/src/components/Common/TextArea.tsx index a120447e..ceb1ab01 100644 --- a/rsconcept/frontend/src/components/Common/TextArea.tsx +++ b/rsconcept/frontend/src/components/Common/TextArea.tsx @@ -21,7 +21,7 @@ function TextArea({ { setValue?: (newValue: boolean | null) => void } -function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit', value, setValue }: TristateProps) { +function Tristate({ + id, required, disabled, tooltip, label, + widthClass = 'w-fit', value, setValue, ...props +}: TristateProps) { const cursor = useMemo( () => { if (disabled) { @@ -33,9 +36,11 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit' return; } if (value === false) { - setValue(null); + setValue(null); + } else if (value === null) { + setValue(true); } else { - setValue(!value); + setValue(false); } } @@ -46,8 +51,12 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-fit' title={tooltip} disabled={disabled} onClick={handleClick} + {...props} > - + + { value && } + { value == null && } + { label && } - {value && } - {value === null && } ); } -export default Checkbox; +export default Tristate; diff --git a/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx b/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx index 0668d4df..c9944c6a 100644 --- a/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx +++ b/rsconcept/frontend/src/components/Help/InfoCstStatus.tsx @@ -1,6 +1,6 @@ import { useConceptTheme } from '../../context/ThemeContext'; import { prefixes } from '../../utils/constants'; -import { getCstStatusColor, mapStatusInfo } from '../../utils/staticUI'; +import { getCstStatusBgColor, mapStatusInfo } from '../../utils/staticUI'; interface InfoCstStatusProps { title?: string @@ -18,7 +18,7 @@ function InfoCstStatus({ title }: InfoCstStatusProps) { {info.text} diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index a4345260..1015b654 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -321,7 +321,7 @@ export function InDoor(props: IconProps) { export function CheckboxChecked() { return ( @@ -333,8 +333,8 @@ export function CheckboxChecked() { export function CheckboxNull() { return ( diff --git a/rsconcept/frontend/src/index.css b/rsconcept/frontend/src/index.css index d6a603af..2b8652a6 100644 --- a/rsconcept/frontend/src/index.css +++ b/rsconcept/frontend/src/index.css @@ -7,18 +7,18 @@ :root { /* Light Theme */ --cl-bg-120: hsl(000, 000%, 100%); - --cl-bg-100: hsl(220, 020%, 098%); - --cl-bg-80: hsl(220, 014%, 096%); - --cl-bg-60: hsl(220, 013%, 091%); - --cl-bg-40: hsl(216, 012%, 084%); + --cl-bg-100: hsl(000, 000%, 098%); + --cl-bg-80: hsl(000, 000%, 094%); + --cl-bg-60: hsl(000, 000%, 091%); + --cl-bg-40: hsl(000, 000%, 080%); --cl-fg-60: hsl(000, 000%, 055%); --cl-fg-80: hsl(000, 000%, 047%); --cl-fg-100: hsl(000, 000%, 000%); --cl-prim-bg-100: hsl(220, 100%, 060%); - --cl-prim-bg-80: hsl(220, 100%, 090%); - --cl-prim-bg-60: hsl(220, 100%, 094%); + --cl-prim-bg-80: hsl(220, 080%, 092%); + --cl-prim-bg-60: hsl(190, 080%, 094%); --cl-prim-fg-80: hsl(220, 100%, 050%); --cl-prim-fg-100: hsl(000, 000%, 100%); @@ -38,24 +38,16 @@ --cd-fg-80: hsl(000, 000%, 080%); --cd-fg-100: hsl(000, 000%, 093%); - /* --cd-prim-bg-100: hsl(025, 079%, 052%); - --cd-prim-bg-80: hsl(035, 080%, 043%); - --cd-prim-bg-60: hsl(045, 080%, 031%); + --cd-prim-bg-100: hsl(267, 050%, 050%); + --cd-prim-bg-80: hsl(267, 050%, 032%); + --cd-prim-bg-60: hsl(269, 030%, 028%); - --cd-prim-fg-80: hsl(025, 080%, 050%); - --cd-prim-fg-100: hsl(000, 000%, 100%); */ - - --cd-prim-bg-100: hsl(267, 50%, 50%); - --cd-prim-bg-80: hsl(267, 50%, 35%); - --cd-prim-bg-60: hsl(269, 50%, 20%); - - --cd-prim-fg-60: hsl(267, 50%, 35%); - --cd-prim-fg-80: hsl(267, 50%, 50%); - --cd-prim-fg-100: #ffffff; + --cd-prim-fg-80: hsl(267, 070%, 070%); + --cd-prim-fg-100: hsl(000, 000%, 100%); --cd-red-bg-100: hsl(000, 100%, 015%); - --cd-red-fg-100: hsl(000, 100%, 060%); - --cd-green-fg-100: hsl(120, 80%, 40%); + --cd-red-fg-100: hsl(000, 080%, 055%); + --cd-green-fg-100: hsl(120, 080%, 040%); /* Import overrides */ --toastify-color-dark: var(--cd-bg-60); @@ -246,14 +238,10 @@ } } - .clr-btn-nav { - color: var(--cl-fg-80); - .dark & { - color: var(--cd-fg-80); - } - } - - .clr-btn-clear { + :is(.text-controls, + .clr-btn-nav, + .clr-btn-clear + ) { color: var(--cl-fg-80); &:disabled { color: var(--cl-fg-60); diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx index d0d3e22e..9b7e74f3 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -26,7 +26,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { return ( } + icon={} dense tooltip='Фильтры' colorClass='clr-input clr-hover text-btn' diff --git a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx index aaed0014..3d5ea0b7 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx @@ -37,16 +37,14 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) { function handleChangeQuery(event: React.ChangeEvent) { const newQuery = event.target.value; setQuery(newQuery); - setFilter(prev => { - return { - query: newQuery, - is_owned: prev.is_owned, - is_common: prev.is_common, - is_canonical: prev.is_canonical, - is_subscribed: prev.is_subscribed, - is_personal: prev.is_personal - }; - }); + setFilter(prev => ({ + query: newQuery, + is_owned: prev.is_owned, + is_common: prev.is_common, + is_canonical: prev.is_canonical, + is_subscribed: prev.is_subscribed, + is_personal: prev.is_personal + })); } useLayoutEffect(() => { @@ -83,7 +81,7 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) { onChange={handleChangeStrategy} /> - + void } +const columnHelper = createColumnHelper(); + function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { const { navigateTo } = useConceptNavigation(); const intl = useIntl(); @@ -27,12 +30,13 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { const columns = useMemo( () => [ - { - name: '', + columnHelper.display({ id: 'status', - minWidth: '60px', - maxWidth: '60px', - cell: (item: ILibraryItem) => { + header: '', + size: 60, + maxSize: 60, + cell: props => { + const item = props.row.original; return (<> >); }, - sortable: true, - reorder: true - }, - { - name: 'Шифр', + }), + columnHelper.accessor('alias', { id: 'alias', - maxWidth: '140px', - selector: (item: ILibraryItem) => item.alias, - sortable: true, - reorder: true - }, - { - name: 'Название', + header: 'Шифр', + size: 200, + minSize: 200, + maxSize: 200, + enableSorting: true + }), + columnHelper.accessor('title', { id: 'title', - minWidth: '50%', - selector: (item: ILibraryItem) => item.title, - sortable: true, - reorder: true - }, - { - name: 'Владелец', + header: 'Название', + minSize: 200, + size: 1000, + maxSize: 1000, + enableSorting: true + }), + columnHelper.accessor(item => item.owner ?? 0, { id: 'owner', - selector: (item: ILibraryItem) => item.owner ?? 0, - format: (item: ILibraryItem) => { - return getUserLabel(item.owner); - }, - sortable: true, - reorder: true - }, - { - name: 'Обновлена', + header: 'Владелец', + cell: props => getUserLabel(props.cell.getValue()), + enableSorting: true, + enableResizing: false, + minSize: 200, + size: 300, + maxSize: 300 + }), + columnHelper.accessor('time_update', { id: 'time_update', - selector: (item: ILibraryItem) => item.time_update, - format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale), - sortable: true, - reorder: true - } + header: 'Обновлена', + minSize: 200, + size: 200, + maxSize: 200, + cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale), + enableSorting: true + }) ], [intl, getUserLabel, user]); return ( - + - + @@ -97,14 +100,11 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { - @@ -120,10 +120,9 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { } - - pagination - paginationPerPage={50} - paginationRowsPerPageOptions={[10, 20, 30, 50, 100]} + // pagination + // paginationPerPage={50} + // paginationRowsPerPageOptions={[10, 20, 30, 50, 100]} onRowClicked={openRSForm} /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx b/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx index ce736130..091c4779 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx @@ -18,18 +18,15 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) { const { darkMode, colors } = useConceptTheme(); const [hoverID, setHoverID] = useState(undefined); const hoverNode = useMemo( - () => { - return syntaxTree.find(node => node.uid === hoverID); - }, [hoverID, syntaxTree]); - + () => syntaxTree.find(node => node.uid === hoverID) + , [hoverID, syntaxTree]); + const nodes: GraphNode[] = useMemo( - () => syntaxTree.map(node => { - return { - id: String(node.uid), - label: getASTNodeLabel(node), - fill: getASTNodeColor(node, colors), - }; - }), [syntaxTree, colors]); + () => syntaxTree.map(node => ({ + id: String(node.uid), + label: getASTNodeLabel(node), + fill: getASTNodeColor(node, colors), + })), [syntaxTree, colors]); const edges: GraphEdge[] = useMemo( () => { @@ -47,14 +44,12 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) { }, [syntaxTree]); const handleHoverIn = useCallback( - (node: GraphNode) => { - setHoverID(Number(node.id)); - }, []); + (node: GraphNode) => setHoverID(Number(node.id)) + , []); const handleHoverOut = useCallback( - () => { - setHoverID(undefined); - }, []); + () => setHoverID(undefined) + , []); return ( (); interface EditorItemsProps { onOpenEdit: (cstID: number) => void @@ -25,7 +28,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) const [selected, setSelected] = useState([]); const nothingSelected = useMemo(() => selected.length === 0, [selected]); - const [toggledClearRows, setToggledClearRows] = useState(false); + const [rowSelection, setRowSelection] = useState({}); // Delete selected constituents function handleDelete() { @@ -33,8 +36,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) return; } onDeleteCst(selected, () => { - setToggledClearRows(prev => !prev); - setSelected([]); + setRowSelection({}); }); } @@ -53,12 +55,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) }, -1); const target = Math.max(0, currentIndex - 1) + 1 const data = { - items: selected.map(id => { - return { id: id }; - }), + items: selected.map(id => ({ id: id })), move_to: target } - cstMoveTo(data); + cstMoveTo(data, () => { + const newSelection: RowSelectionState = {}; + selected.forEach((_, index) => { + newSelection[String(target + index - 1)] = true; + }) + setRowSelection(newSelection); + }); } // Move selected cst down @@ -80,12 +86,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) }, -1); const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1 const data: ICstMovetoData = { - items: selected.map(id => { - return { id: id }; - }), + items: selected.map(id => ({ id: id })), move_to: target } - cstMoveTo(data); + cstMoveTo(data, () => { + const newSelection: RowSelectionState = {}; + selected.forEach((_, index) => { + newSelection[String(target + index - 1)] = true; + }) + setRowSelection(newSelection); + }); } // Generate new names for all constituents @@ -156,41 +166,53 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) 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]); - const handleSelectionChange = useCallback( - ({ selectedRows }: { - allSelected: boolean - selectedCount: number - selectedRows: IConstituenta[] - }) => { - setSelected(selectedRows.map(cst => cst.id)); - }, [setSelected]); + 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( () => [ - { - name: 'ID', - id: 'id', - selector: (cst: IConstituenta) => cst.id, - omit: true - }, - { - name: 'Имя', + columnHelper.accessor('alias', { id: 'alias', - cell: (cst: IConstituenta) => { - const info = mapStatusInfo.get(cst.status)!; + header: 'Имя', + size: 65, + minSize: 65, + cell: props => { + const cst = props.row.original; + const info = mapStatusInfo.get(cst.status); return (<> {cst.alias} @@ -198,68 +220,64 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) anchorSelect={`#${prefixes.cst_list}${cst.alias}`} place='right' > - Статус: {info.tooltip} + Статус: {info!.tooltip} >); - }, - width: '65px', - maxWidth: '65px', - reorder: true, - }, - { - name: 'Типизация', + } + }), + columnHelper.accessor(cst => getCstTypificationLabel(cst), { id: 'type', - cell: (cst: IConstituenta) => {getCstTypificationLabel(cst)}, - width: '175px', - maxWidth: '175px', - wrap: true, - reorder: true, - hide: 1600 - }, - { - name: 'Термин', + header: 'Типизация', + size: 175, + maxSize: 175, + cell: props => {props.getValue()} + }), + columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', { id: 'term', - selector: (cst: IConstituenta) => cst.term_resolved || cst.term_raw || '', - width: '350px', - minWidth: '150px', - maxWidth: '350px', - wrap: true, - reorder: true - }, - { - name: 'Формальное определение', + header: 'Термин', + size: 350, + minSize: 150, + maxSize: 350 + }), + columnHelper.accessor('definition_formal', { id: 'expression', - selector: (cst: IConstituenta) => cst.definition_formal || '', - minWidth: '300px', - maxWidth: '500px', - grow: 2, - wrap: true, - reorder: true - }, - { - name: 'Текстовое определение', + header: 'Формальное определение', + size: 300, + minSize: 300, + maxSize: 500 + }), + columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', { id: 'definition', - cell: (cst: IConstituenta) => ( - - {cst.definition_resolved || cst.definition_raw || ''} - - ), - minWidth: '200px', - grow: 2, - wrap: true, - reorder: true - }, - { - name: 'Конвенция / Комментарий', + header: 'Текстовое определение', + size: 200, + minSize: 200, + cell: props => {props.getValue()} + }), + columnHelper.accessor('convention', { id: 'convention', - cell: (cst: IConstituenta) => {cst.convention ?? ''}, - minWidth: '100px', - wrap: true, - reorder: true, - hide: 1800 - } + header: 'Конвенция / Комментарий', + minSize: 100, + maxSize: undefined, + cell: props => {props.getValue()} + }), ], [colors]); + // name: 'Типизация', + // hide: 1600 + // }, + // { + // name: 'Формальное определение', + // grow: 2, + // }, + // { + // name: 'Текстовое определение', + // grow: 2, + // }, + // { + // name: 'Конвенция / Комментарий', + // id: 'convention', + // hide: 1800 + return ( - - + Список пуст - Создайте новую конституенту + handleCreateCst()}> + Создать новую конституенту + } - - striped - highlightOnHover - pointerOnHover - - selectableRows - selectableRowsHighlight - selectableRowsComponentProps={{tabIndex: -1}} - onSelectedRowsChange={handleSelectionChange} - onRowDoubleClicked={cst => onOpenEdit(cst.id)} - onRowClicked={handleRowClicked} - clearSelectedRows={toggledClearRows} - dense /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx index 39ba405a..2b47645c 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx @@ -19,7 +19,7 @@ import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color'; import { prefixes, resources, TIMEOUT_GRAPH_REFRESH } from '../../utils/constants'; import { Graph } from '../../utils/Graph'; import { CstType, IConstituenta, ICstCreateData } from '../../utils/models'; -import { getCstClassColor, getCstStatusColor, +import { getCstClassColor, getCstStatusBgColor, GraphColoringSelector, GraphLayoutSelector, mapColoringLabels, mapLayoutLabels } from '../../utils/staticUI'; @@ -34,7 +34,7 @@ function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, col return getCstClassColor(cst.cst_class, colors); } if (coloringScheme === 'status') { - return getCstStatusColor(cst.status, colors); + return getCstStatusBgColor(cst.status, colors); } return ''; } diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index 3e483a8e..b85c9f84 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -137,7 +137,7 @@ function RSTabs() { if (element) { element.scrollIntoView({ behavior: 'smooth', - block: 'end', + block: 'nearest', inline: 'nearest' }); } @@ -173,9 +173,7 @@ function RSTabs() { return; } const data = { - items: deleted.map(id => { - return { id: id }; - }) + items: deleted.map(id => ({ id: id })) }; let activeIndex = schema.items.findIndex(cst => cst.id === activeID); cstDelete(data, () => { @@ -345,7 +343,7 @@ function RSTabs() { showCloneDialog={handleShowClone} showUploadDialog={() => setShowUpload(true)} /> - Паспорт схемы + Паспорт схемы Конституенты {`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`} diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx index ab817a21..3a8d75ac 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx @@ -71,9 +71,10 @@ function RSTabsMenu({ } + icon={} borderClass='' widthClass='h-full w-fit' + style={{outlineColor: 'transparent'}} dense onClick={schemaMenu.toggle} tabIndex={-1} @@ -123,6 +124,7 @@ function RSTabsMenu({ tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')} borderClass='' widthClass='h-full w-fit' + style={{outlineColor: 'transparent'}} icon={} dense onClick={editMenu.toggle} @@ -136,7 +138,7 @@ function RSTabsMenu({ tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''} > - + { isOwned && Владелец схемы } { !isOwned && Стать владельцем } @@ -165,10 +167,11 @@ function RSTabsMenu({ disabled={processing} icon={isTracking ? - : + : } widthClass='h-full w-fit' borderClass='' + style={{outlineColor: 'transparent'}} dense onClick={onToggleSubscribe} tabIndex={-1} diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx index a80914a1..6a448262 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useConceptTheme } from '../../../context/ThemeContext'; import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models'; -import { getCstStatusColor, mapStatusInfo } from '../../../utils/staticUI'; +import { getCstStatusBgColor, mapStatusInfo } from '../../../utils/staticUI'; interface StatusBarProps { isModified?: boolean @@ -27,7 +27,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) { return ( Статус: [ {data.text} ] diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx index 9f587395..f89ac8ab 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx @@ -1,12 +1,13 @@ +import { createColumnHelper } from '@tanstack/react-table'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import ConceptDataTable from '../../../components/Common/ConceptDataTable'; +import DataTable from '../../../components/Common/DataTable'; import { useRSForm } from '../../../context/RSFormContext'; import { useConceptTheme } from '../../../context/ThemeContext'; import useLocalStorage from '../../../hooks/useLocalStorage'; import { prefixes } from '../../../utils/constants'; import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models'; -import { getCstDescription, getCstStatusColor, getMockConstituenta } from '../../../utils/staticUI'; +import { getCstDescription, getCstStatusFgColor, getMockConstituenta } from '../../../utils/staticUI'; import ConstituentaTooltip from './ConstituentaTooltip'; import DependencyModePicker from './DependencyModePicker'; import MatchModePicker from './MatchModePicker'; @@ -25,6 +26,8 @@ function isMockCst(cst: IConstituenta) { return cst.id <= 0; } +const columnHelper = createColumnHelper(); + function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { const { noNavigation, colors } = useConceptTheme(); const { schema } = useRSForm(); @@ -85,75 +88,61 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } }, [onOpenEdit]); - const conditionalRowStyles = useMemo( - () => [ - { - when: (cst: IConstituenta) => cst.id === activeID, - style: { - backgroundColor: colors.bgSelected, - }, - } - ], [activeID, colors]); - const columns = useMemo( () => [ - { - id: 'id', - selector: (cst: IConstituenta) => cst.id, - omit: true - }, - { - name: 'ID', + columnHelper.accessor('alias', { id: 'alias', - cell: (cst: IConstituenta) => { + header: 'Имя', + size: 65, + minSize: 65, + cell: props => { + const cst = props.row.original; return (<> {cst.alias} >); - }, - width: '65px', - maxWidth: '65px', - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.bgWarning} - } - ] - }, - { - name: 'Описание', + } + }), + columnHelper.accessor(cst => getCstDescription(cst), { id: 'description', - selector: (cst: IConstituenta) => getCstDescription(cst), - minWidth: '350px', - wrap: true, - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.bgWarning} - } - ] - }, - { - name: 'Выражение', + header: 'Описание', + size: 350, + minSize: 350, + maxSize: 350, + cell: props => + + {props.getValue()} + + }), + columnHelper.accessor('definition_formal', { id: 'expression', - selector: (cst: IConstituenta) => cst.definition_formal || '', - minWidth: '200px', - hide: 1600, - grow: 2, - wrap: true, - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.fgWarning} - } - ] - } + header: 'Выражение', + size: 700, + minSize: 0, + maxSize: 700, + cell: props => + + {props.getValue()} + + }) ], [colors]); const maxHeight = useMemo( @@ -181,12 +170,14 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: onChange={setFilterSource} /> - - + Список конституент пуст @@ -194,13 +185,8 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } - striped - highlightOnHover - pointerOnHover - onRowDoubleClicked={handleDoubleClick} onRowClicked={handleRowClicked} - dense /> >); diff --git a/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx b/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx index d50ec2d0..bff7332f 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx @@ -1,7 +1,8 @@ +import { createColumnHelper } from '@tanstack/react-table'; import { useMemo } from 'react'; import { useIntl } from 'react-intl'; -import ConceptDataTable from '../../components/Common/ConceptDataTable'; +import DataTable from '../../components/Common/DataTable'; import { useConceptNavigation } from '../../context/NagivationContext'; import { ILibraryItem } from '../../utils/models'; @@ -9,6 +10,8 @@ interface ViewSubscriptionsProps { items: ILibraryItem[] } +const columnHelper = createColumnHelper(); + function ViewSubscriptions({items}: ViewSubscriptionsProps) { const { navigateTo } = useConceptNavigation(); const intl = useIntl(); @@ -17,50 +20,48 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) { const columns = useMemo(() => [ - { - name: 'Шифр', + columnHelper.accessor('alias', { id: 'alias', - maxWidth: '140px', - selector: (item: ILibraryItem) => item.alias, - sortable: true, - reorder: true - }, - { - name: 'Название', + header: 'Шифр', + size: 200, + minSize: 200, + maxSize: 200, + enableSorting: true + }), + columnHelper.accessor('title', { id: 'title', - minWidth: '50%', - selector: (item: ILibraryItem) => item.title, - sortable: true, - reorder: true - }, - { - name: 'Обновлена', + header: 'Название', + minSize: 200, + size: 800, + maxSize: 800, + enableSorting: true + }), + columnHelper.accessor('time_update', { id: 'time_update', - selector: (item: ILibraryItem) => item.time_update, - format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale), - sortable: true, - reorder: true - } + header: 'Обновлена', + minSize: 200, + size: 200, + maxSize: 200, + cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale), + enableSorting: true + }) ], [intl]); return ( - + Отслеживаемые схемы отсутствуют } - striped - dense - highlightOnHover - pointerOnHover onRowClicked={openRSForm} /> + ) } diff --git a/rsconcept/frontend/src/utils/color.ts b/rsconcept/frontend/src/utils/color.ts index 423b4951..6520a81a 100644 --- a/rsconcept/frontend/src/utils/color.ts +++ b/rsconcept/frontend/src/utils/color.ts @@ -95,67 +95,14 @@ export const darkT: IColorTheme = { bgTeal: 'hsl(192, 080%, 030%)', bgOrange: 'hsl(035, 100%, 035%)', - fgRed: 'hsl(000, 080%, 050%)', - fgGreen: 'hsl(100, 080%, 040%)', + fgRed: 'hsl(000, 080%, 045%)', + fgGreen: 'hsl(100, 080%, 035%)', fgBlue: 'hsl(235, 100%, 080%)', fgPurple: 'hsl(270, 100%, 080%)', fgTeal: 'hsl(192, 100%, 030%)', fgOrange: 'hsl(035, 100%, 050%)' }; -// ========= DATA TABLE THEMES ======== -export const dataTableLightT = { - text: { - primary: lightT.fgDefault, - secondary: lightT.fgDefault, - disabled: lightT.fgDisabled - }, - background: { - default: lightT.bgDefault - }, - highlightOnHover: { - default: lightT.bgHover, - text: lightT.fgDefault - }, - divider: { - default: lightT.border - }, - striped: { - default: lightT.bgControls, - text: lightT.fgDefault - }, - selected: { - default: lightT.bgSelected, - text: lightT.fgDefault - } -} - -export const dataTableDarkT = { - text: { - primary: darkT.fgDefault, - secondary: darkT.fgDefault, - disabled: darkT.fgDisabled - }, - background: { - default: darkT.bgDefault - }, - highlightOnHover: { - default: darkT.bgHover, - text: darkT.fgDefault - }, - divider: { - default: darkT.border - }, - striped: { - default: darkT.bgControls, - text: darkT.fgDefault - }, - selected: { - default: darkT.bgSelected, - text: darkT.fgDefault - } -}; - // ============ SELECT THEMES ========== export const selectLightT = { primary: lightT.bgPrimary, diff --git a/rsconcept/frontend/src/utils/staticUI.ts b/rsconcept/frontend/src/utils/staticUI.ts index e126b8ab..a1f4d805 100644 --- a/rsconcept/frontend/src/utils/staticUI.ts +++ b/rsconcept/frontend/src/utils/staticUI.ts @@ -242,11 +242,13 @@ export function getCstTypeShortcut(type: CstType) { } } -export const CstTypeSelector = (Object.values(CstType)).map( -(typeStr) => { - const type = typeStr as CstType; - return { value: type, label: getCstTypeLabel(type) }; -}); +export const CstTypeSelector = ( + Object.values(CstType)).map( + typeStr => ({ + value: typeStr as CstType, + label: getCstTypeLabel(typeStr as CstType) + }) +); export function getCstCompareLabel(mode: CstMatchMode): string { switch(mode) { @@ -313,7 +315,18 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [ { value: 'type', label: 'Цвет: класс'}, ]; -export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string { +export function getCstStatusBgColor(status: ExpressionStatus, colors: IColorTheme): string { + switch (status) { + case ExpressionStatus.VERIFIED: return colors.bgGreen; + case ExpressionStatus.INCORRECT: return colors.bgRed; + case ExpressionStatus.INCALCULABLE: return colors.bgOrange; + case ExpressionStatus.PROPERTY: return colors.bgTeal; + case ExpressionStatus.UNKNOWN: return colors.bgBlue; + case ExpressionStatus.UNDEFINED: return colors.bgBlue; + } +} + +export function getCstStatusFgColor(status: ExpressionStatus, colors: IColorTheme): string { switch (status) { case ExpressionStatus.VERIFIED: return colors.fgGreen; case ExpressionStatus.INCORRECT: return colors.fgRed;
{info.text} diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index a4345260..1015b654 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -321,7 +321,7 @@ export function InDoor(props: IconProps) { export function CheckboxChecked() { return ( @@ -333,8 +333,8 @@ export function CheckboxChecked() { export function CheckboxNull() { return ( diff --git a/rsconcept/frontend/src/index.css b/rsconcept/frontend/src/index.css index d6a603af..2b8652a6 100644 --- a/rsconcept/frontend/src/index.css +++ b/rsconcept/frontend/src/index.css @@ -7,18 +7,18 @@ :root { /* Light Theme */ --cl-bg-120: hsl(000, 000%, 100%); - --cl-bg-100: hsl(220, 020%, 098%); - --cl-bg-80: hsl(220, 014%, 096%); - --cl-bg-60: hsl(220, 013%, 091%); - --cl-bg-40: hsl(216, 012%, 084%); + --cl-bg-100: hsl(000, 000%, 098%); + --cl-bg-80: hsl(000, 000%, 094%); + --cl-bg-60: hsl(000, 000%, 091%); + --cl-bg-40: hsl(000, 000%, 080%); --cl-fg-60: hsl(000, 000%, 055%); --cl-fg-80: hsl(000, 000%, 047%); --cl-fg-100: hsl(000, 000%, 000%); --cl-prim-bg-100: hsl(220, 100%, 060%); - --cl-prim-bg-80: hsl(220, 100%, 090%); - --cl-prim-bg-60: hsl(220, 100%, 094%); + --cl-prim-bg-80: hsl(220, 080%, 092%); + --cl-prim-bg-60: hsl(190, 080%, 094%); --cl-prim-fg-80: hsl(220, 100%, 050%); --cl-prim-fg-100: hsl(000, 000%, 100%); @@ -38,24 +38,16 @@ --cd-fg-80: hsl(000, 000%, 080%); --cd-fg-100: hsl(000, 000%, 093%); - /* --cd-prim-bg-100: hsl(025, 079%, 052%); - --cd-prim-bg-80: hsl(035, 080%, 043%); - --cd-prim-bg-60: hsl(045, 080%, 031%); + --cd-prim-bg-100: hsl(267, 050%, 050%); + --cd-prim-bg-80: hsl(267, 050%, 032%); + --cd-prim-bg-60: hsl(269, 030%, 028%); - --cd-prim-fg-80: hsl(025, 080%, 050%); - --cd-prim-fg-100: hsl(000, 000%, 100%); */ - - --cd-prim-bg-100: hsl(267, 50%, 50%); - --cd-prim-bg-80: hsl(267, 50%, 35%); - --cd-prim-bg-60: hsl(269, 50%, 20%); - - --cd-prim-fg-60: hsl(267, 50%, 35%); - --cd-prim-fg-80: hsl(267, 50%, 50%); - --cd-prim-fg-100: #ffffff; + --cd-prim-fg-80: hsl(267, 070%, 070%); + --cd-prim-fg-100: hsl(000, 000%, 100%); --cd-red-bg-100: hsl(000, 100%, 015%); - --cd-red-fg-100: hsl(000, 100%, 060%); - --cd-green-fg-100: hsl(120, 80%, 40%); + --cd-red-fg-100: hsl(000, 080%, 055%); + --cd-green-fg-100: hsl(120, 080%, 040%); /* Import overrides */ --toastify-color-dark: var(--cd-bg-60); @@ -246,14 +238,10 @@ } } - .clr-btn-nav { - color: var(--cl-fg-80); - .dark & { - color: var(--cd-fg-80); - } - } - - .clr-btn-clear { + :is(.text-controls, + .clr-btn-nav, + .clr-btn-clear + ) { color: var(--cl-fg-80); &:disabled { color: var(--cl-fg-60); diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx index d0d3e22e..9b7e74f3 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -26,7 +26,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { return ( } + icon={} dense tooltip='Фильтры' colorClass='clr-input clr-hover text-btn' diff --git a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx index aaed0014..3d5ea0b7 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx @@ -37,16 +37,14 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) { function handleChangeQuery(event: React.ChangeEvent) { const newQuery = event.target.value; setQuery(newQuery); - setFilter(prev => { - return { - query: newQuery, - is_owned: prev.is_owned, - is_common: prev.is_common, - is_canonical: prev.is_canonical, - is_subscribed: prev.is_subscribed, - is_personal: prev.is_personal - }; - }); + setFilter(prev => ({ + query: newQuery, + is_owned: prev.is_owned, + is_common: prev.is_common, + is_canonical: prev.is_canonical, + is_subscribed: prev.is_subscribed, + is_personal: prev.is_personal + })); } useLayoutEffect(() => { @@ -83,7 +81,7 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) { onChange={handleChangeStrategy} /> - + void } +const columnHelper = createColumnHelper(); + function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { const { navigateTo } = useConceptNavigation(); const intl = useIntl(); @@ -27,12 +30,13 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { const columns = useMemo( () => [ - { - name: '', + columnHelper.display({ id: 'status', - minWidth: '60px', - maxWidth: '60px', - cell: (item: ILibraryItem) => { + header: '', + size: 60, + maxSize: 60, + cell: props => { + const item = props.row.original; return (<> >); }, - sortable: true, - reorder: true - }, - { - name: 'Шифр', + }), + columnHelper.accessor('alias', { id: 'alias', - maxWidth: '140px', - selector: (item: ILibraryItem) => item.alias, - sortable: true, - reorder: true - }, - { - name: 'Название', + header: 'Шифр', + size: 200, + minSize: 200, + maxSize: 200, + enableSorting: true + }), + columnHelper.accessor('title', { id: 'title', - minWidth: '50%', - selector: (item: ILibraryItem) => item.title, - sortable: true, - reorder: true - }, - { - name: 'Владелец', + header: 'Название', + minSize: 200, + size: 1000, + maxSize: 1000, + enableSorting: true + }), + columnHelper.accessor(item => item.owner ?? 0, { id: 'owner', - selector: (item: ILibraryItem) => item.owner ?? 0, - format: (item: ILibraryItem) => { - return getUserLabel(item.owner); - }, - sortable: true, - reorder: true - }, - { - name: 'Обновлена', + header: 'Владелец', + cell: props => getUserLabel(props.cell.getValue()), + enableSorting: true, + enableResizing: false, + minSize: 200, + size: 300, + maxSize: 300 + }), + columnHelper.accessor('time_update', { id: 'time_update', - selector: (item: ILibraryItem) => item.time_update, - format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale), - sortable: true, - reorder: true - } + header: 'Обновлена', + minSize: 200, + size: 200, + maxSize: 200, + cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale), + enableSorting: true + }) ], [intl, getUserLabel, user]); return ( - + - + @@ -97,14 +100,11 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { - @@ -120,10 +120,9 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) { } - - pagination - paginationPerPage={50} - paginationRowsPerPageOptions={[10, 20, 30, 50, 100]} + // pagination + // paginationPerPage={50} + // paginationRowsPerPageOptions={[10, 20, 30, 50, 100]} onRowClicked={openRSForm} /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx b/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx index ce736130..091c4779 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx @@ -18,18 +18,15 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) { const { darkMode, colors } = useConceptTheme(); const [hoverID, setHoverID] = useState(undefined); const hoverNode = useMemo( - () => { - return syntaxTree.find(node => node.uid === hoverID); - }, [hoverID, syntaxTree]); - + () => syntaxTree.find(node => node.uid === hoverID) + , [hoverID, syntaxTree]); + const nodes: GraphNode[] = useMemo( - () => syntaxTree.map(node => { - return { - id: String(node.uid), - label: getASTNodeLabel(node), - fill: getASTNodeColor(node, colors), - }; - }), [syntaxTree, colors]); + () => syntaxTree.map(node => ({ + id: String(node.uid), + label: getASTNodeLabel(node), + fill: getASTNodeColor(node, colors), + })), [syntaxTree, colors]); const edges: GraphEdge[] = useMemo( () => { @@ -47,14 +44,12 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) { }, [syntaxTree]); const handleHoverIn = useCallback( - (node: GraphNode) => { - setHoverID(Number(node.id)); - }, []); + (node: GraphNode) => setHoverID(Number(node.id)) + , []); const handleHoverOut = useCallback( - () => { - setHoverID(undefined); - }, []); + () => setHoverID(undefined) + , []); return ( (); interface EditorItemsProps { onOpenEdit: (cstID: number) => void @@ -25,7 +28,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) const [selected, setSelected] = useState([]); const nothingSelected = useMemo(() => selected.length === 0, [selected]); - const [toggledClearRows, setToggledClearRows] = useState(false); + const [rowSelection, setRowSelection] = useState({}); // Delete selected constituents function handleDelete() { @@ -33,8 +36,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) return; } onDeleteCst(selected, () => { - setToggledClearRows(prev => !prev); - setSelected([]); + setRowSelection({}); }); } @@ -53,12 +55,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) }, -1); const target = Math.max(0, currentIndex - 1) + 1 const data = { - items: selected.map(id => { - return { id: id }; - }), + items: selected.map(id => ({ id: id })), move_to: target } - cstMoveTo(data); + cstMoveTo(data, () => { + const newSelection: RowSelectionState = {}; + selected.forEach((_, index) => { + newSelection[String(target + index - 1)] = true; + }) + setRowSelection(newSelection); + }); } // Move selected cst down @@ -80,12 +86,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) }, -1); const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1 const data: ICstMovetoData = { - items: selected.map(id => { - return { id: id }; - }), + items: selected.map(id => ({ id: id })), move_to: target } - cstMoveTo(data); + cstMoveTo(data, () => { + const newSelection: RowSelectionState = {}; + selected.forEach((_, index) => { + newSelection[String(target + index - 1)] = true; + }) + setRowSelection(newSelection); + }); } // Generate new names for all constituents @@ -156,41 +166,53 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) 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]); - const handleSelectionChange = useCallback( - ({ selectedRows }: { - allSelected: boolean - selectedCount: number - selectedRows: IConstituenta[] - }) => { - setSelected(selectedRows.map(cst => cst.id)); - }, [setSelected]); + 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( () => [ - { - name: 'ID', - id: 'id', - selector: (cst: IConstituenta) => cst.id, - omit: true - }, - { - name: 'Имя', + columnHelper.accessor('alias', { id: 'alias', - cell: (cst: IConstituenta) => { - const info = mapStatusInfo.get(cst.status)!; + header: 'Имя', + size: 65, + minSize: 65, + cell: props => { + const cst = props.row.original; + const info = mapStatusInfo.get(cst.status); return (<> {cst.alias} @@ -198,68 +220,64 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) anchorSelect={`#${prefixes.cst_list}${cst.alias}`} place='right' > - Статус: {info.tooltip} + Статус: {info!.tooltip} >); - }, - width: '65px', - maxWidth: '65px', - reorder: true, - }, - { - name: 'Типизация', + } + }), + columnHelper.accessor(cst => getCstTypificationLabel(cst), { id: 'type', - cell: (cst: IConstituenta) => {getCstTypificationLabel(cst)}, - width: '175px', - maxWidth: '175px', - wrap: true, - reorder: true, - hide: 1600 - }, - { - name: 'Термин', + header: 'Типизация', + size: 175, + maxSize: 175, + cell: props => {props.getValue()} + }), + columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', { id: 'term', - selector: (cst: IConstituenta) => cst.term_resolved || cst.term_raw || '', - width: '350px', - minWidth: '150px', - maxWidth: '350px', - wrap: true, - reorder: true - }, - { - name: 'Формальное определение', + header: 'Термин', + size: 350, + minSize: 150, + maxSize: 350 + }), + columnHelper.accessor('definition_formal', { id: 'expression', - selector: (cst: IConstituenta) => cst.definition_formal || '', - minWidth: '300px', - maxWidth: '500px', - grow: 2, - wrap: true, - reorder: true - }, - { - name: 'Текстовое определение', + header: 'Формальное определение', + size: 300, + minSize: 300, + maxSize: 500 + }), + columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', { id: 'definition', - cell: (cst: IConstituenta) => ( - - {cst.definition_resolved || cst.definition_raw || ''} - - ), - minWidth: '200px', - grow: 2, - wrap: true, - reorder: true - }, - { - name: 'Конвенция / Комментарий', + header: 'Текстовое определение', + size: 200, + minSize: 200, + cell: props => {props.getValue()} + }), + columnHelper.accessor('convention', { id: 'convention', - cell: (cst: IConstituenta) => {cst.convention ?? ''}, - minWidth: '100px', - wrap: true, - reorder: true, - hide: 1800 - } + header: 'Конвенция / Комментарий', + minSize: 100, + maxSize: undefined, + cell: props => {props.getValue()} + }), ], [colors]); + // name: 'Типизация', + // hide: 1600 + // }, + // { + // name: 'Формальное определение', + // grow: 2, + // }, + // { + // name: 'Текстовое определение', + // grow: 2, + // }, + // { + // name: 'Конвенция / Комментарий', + // id: 'convention', + // hide: 1800 + return ( - - + Список пуст - Создайте новую конституенту + handleCreateCst()}> + Создать новую конституенту + } - - striped - highlightOnHover - pointerOnHover - - selectableRows - selectableRowsHighlight - selectableRowsComponentProps={{tabIndex: -1}} - onSelectedRowsChange={handleSelectionChange} - onRowDoubleClicked={cst => onOpenEdit(cst.id)} - onRowClicked={handleRowClicked} - clearSelectedRows={toggledClearRows} - dense /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx index 39ba405a..2b47645c 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx @@ -19,7 +19,7 @@ import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color'; import { prefixes, resources, TIMEOUT_GRAPH_REFRESH } from '../../utils/constants'; import { Graph } from '../../utils/Graph'; import { CstType, IConstituenta, ICstCreateData } from '../../utils/models'; -import { getCstClassColor, getCstStatusColor, +import { getCstClassColor, getCstStatusBgColor, GraphColoringSelector, GraphLayoutSelector, mapColoringLabels, mapLayoutLabels } from '../../utils/staticUI'; @@ -34,7 +34,7 @@ function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, col return getCstClassColor(cst.cst_class, colors); } if (coloringScheme === 'status') { - return getCstStatusColor(cst.status, colors); + return getCstStatusBgColor(cst.status, colors); } return ''; } diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx index 3e483a8e..b85c9f84 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx @@ -137,7 +137,7 @@ function RSTabs() { if (element) { element.scrollIntoView({ behavior: 'smooth', - block: 'end', + block: 'nearest', inline: 'nearest' }); } @@ -173,9 +173,7 @@ function RSTabs() { return; } const data = { - items: deleted.map(id => { - return { id: id }; - }) + items: deleted.map(id => ({ id: id })) }; let activeIndex = schema.items.findIndex(cst => cst.id === activeID); cstDelete(data, () => { @@ -345,7 +343,7 @@ function RSTabs() { showCloneDialog={handleShowClone} showUploadDialog={() => setShowUpload(true)} /> - Паспорт схемы + Паспорт схемы Конституенты {`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`} diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx index ab817a21..3a8d75ac 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabsMenu.tsx @@ -71,9 +71,10 @@ function RSTabsMenu({ } + icon={} borderClass='' widthClass='h-full w-fit' + style={{outlineColor: 'transparent'}} dense onClick={schemaMenu.toggle} tabIndex={-1} @@ -123,6 +124,7 @@ function RSTabsMenu({ tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')} borderClass='' widthClass='h-full w-fit' + style={{outlineColor: 'transparent'}} icon={} dense onClick={editMenu.toggle} @@ -136,7 +138,7 @@ function RSTabsMenu({ tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''} > - + { isOwned && Владелец схемы } { !isOwned && Стать владельцем } @@ -165,10 +167,11 @@ function RSTabsMenu({ disabled={processing} icon={isTracking ? - : + : } widthClass='h-full w-fit' borderClass='' + style={{outlineColor: 'transparent'}} dense onClick={onToggleSubscribe} tabIndex={-1} diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx index a80914a1..6a448262 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useConceptTheme } from '../../../context/ThemeContext'; import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models'; -import { getCstStatusColor, mapStatusInfo } from '../../../utils/staticUI'; +import { getCstStatusBgColor, mapStatusInfo } from '../../../utils/staticUI'; interface StatusBarProps { isModified?: boolean @@ -27,7 +27,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) { return ( Статус: [ {data.text} ] diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx index 9f587395..f89ac8ab 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx @@ -1,12 +1,13 @@ +import { createColumnHelper } from '@tanstack/react-table'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import ConceptDataTable from '../../../components/Common/ConceptDataTable'; +import DataTable from '../../../components/Common/DataTable'; import { useRSForm } from '../../../context/RSFormContext'; import { useConceptTheme } from '../../../context/ThemeContext'; import useLocalStorage from '../../../hooks/useLocalStorage'; import { prefixes } from '../../../utils/constants'; import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models'; -import { getCstDescription, getCstStatusColor, getMockConstituenta } from '../../../utils/staticUI'; +import { getCstDescription, getCstStatusFgColor, getMockConstituenta } from '../../../utils/staticUI'; import ConstituentaTooltip from './ConstituentaTooltip'; import DependencyModePicker from './DependencyModePicker'; import MatchModePicker from './MatchModePicker'; @@ -25,6 +26,8 @@ function isMockCst(cst: IConstituenta) { return cst.id <= 0; } +const columnHelper = createColumnHelper(); + function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { const { noNavigation, colors } = useConceptTheme(); const { schema } = useRSForm(); @@ -85,75 +88,61 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } }, [onOpenEdit]); - const conditionalRowStyles = useMemo( - () => [ - { - when: (cst: IConstituenta) => cst.id === activeID, - style: { - backgroundColor: colors.bgSelected, - }, - } - ], [activeID, colors]); - const columns = useMemo( () => [ - { - id: 'id', - selector: (cst: IConstituenta) => cst.id, - omit: true - }, - { - name: 'ID', + columnHelper.accessor('alias', { id: 'alias', - cell: (cst: IConstituenta) => { + header: 'Имя', + size: 65, + minSize: 65, + cell: props => { + const cst = props.row.original; return (<> {cst.alias} >); - }, - width: '65px', - maxWidth: '65px', - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.bgWarning} - } - ] - }, - { - name: 'Описание', + } + }), + columnHelper.accessor(cst => getCstDescription(cst), { id: 'description', - selector: (cst: IConstituenta) => getCstDescription(cst), - minWidth: '350px', - wrap: true, - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.bgWarning} - } - ] - }, - { - name: 'Выражение', + header: 'Описание', + size: 350, + minSize: 350, + maxSize: 350, + cell: props => + + {props.getValue()} + + }), + columnHelper.accessor('definition_formal', { id: 'expression', - selector: (cst: IConstituenta) => cst.definition_formal || '', - minWidth: '200px', - hide: 1600, - grow: 2, - wrap: true, - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.fgWarning} - } - ] - } + header: 'Выражение', + size: 700, + minSize: 0, + maxSize: 700, + cell: props => + + {props.getValue()} + + }) ], [colors]); const maxHeight = useMemo( @@ -181,12 +170,14 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: onChange={setFilterSource} /> - - + Список конституент пуст @@ -194,13 +185,8 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } - striped - highlightOnHover - pointerOnHover - onRowDoubleClicked={handleDoubleClick} onRowClicked={handleRowClicked} - dense /> >); diff --git a/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx b/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx index d50ec2d0..bff7332f 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx @@ -1,7 +1,8 @@ +import { createColumnHelper } from '@tanstack/react-table'; import { useMemo } from 'react'; import { useIntl } from 'react-intl'; -import ConceptDataTable from '../../components/Common/ConceptDataTable'; +import DataTable from '../../components/Common/DataTable'; import { useConceptNavigation } from '../../context/NagivationContext'; import { ILibraryItem } from '../../utils/models'; @@ -9,6 +10,8 @@ interface ViewSubscriptionsProps { items: ILibraryItem[] } +const columnHelper = createColumnHelper(); + function ViewSubscriptions({items}: ViewSubscriptionsProps) { const { navigateTo } = useConceptNavigation(); const intl = useIntl(); @@ -17,50 +20,48 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) { const columns = useMemo(() => [ - { - name: 'Шифр', + columnHelper.accessor('alias', { id: 'alias', - maxWidth: '140px', - selector: (item: ILibraryItem) => item.alias, - sortable: true, - reorder: true - }, - { - name: 'Название', + header: 'Шифр', + size: 200, + minSize: 200, + maxSize: 200, + enableSorting: true + }), + columnHelper.accessor('title', { id: 'title', - minWidth: '50%', - selector: (item: ILibraryItem) => item.title, - sortable: true, - reorder: true - }, - { - name: 'Обновлена', + header: 'Название', + minSize: 200, + size: 800, + maxSize: 800, + enableSorting: true + }), + columnHelper.accessor('time_update', { id: 'time_update', - selector: (item: ILibraryItem) => item.time_update, - format: (item: ILibraryItem) => new Date(item.time_update).toLocaleString(intl.locale), - sortable: true, - reorder: true - } + header: 'Обновлена', + minSize: 200, + size: 200, + maxSize: 200, + cell: props => new Date(props.cell.getValue()).toLocaleString(intl.locale), + enableSorting: true + }) ], [intl]); return ( - + Отслеживаемые схемы отсутствуют } - striped - dense - highlightOnHover - pointerOnHover onRowClicked={openRSForm} /> + ) } diff --git a/rsconcept/frontend/src/utils/color.ts b/rsconcept/frontend/src/utils/color.ts index 423b4951..6520a81a 100644 --- a/rsconcept/frontend/src/utils/color.ts +++ b/rsconcept/frontend/src/utils/color.ts @@ -95,67 +95,14 @@ export const darkT: IColorTheme = { bgTeal: 'hsl(192, 080%, 030%)', bgOrange: 'hsl(035, 100%, 035%)', - fgRed: 'hsl(000, 080%, 050%)', - fgGreen: 'hsl(100, 080%, 040%)', + fgRed: 'hsl(000, 080%, 045%)', + fgGreen: 'hsl(100, 080%, 035%)', fgBlue: 'hsl(235, 100%, 080%)', fgPurple: 'hsl(270, 100%, 080%)', fgTeal: 'hsl(192, 100%, 030%)', fgOrange: 'hsl(035, 100%, 050%)' }; -// ========= DATA TABLE THEMES ======== -export const dataTableLightT = { - text: { - primary: lightT.fgDefault, - secondary: lightT.fgDefault, - disabled: lightT.fgDisabled - }, - background: { - default: lightT.bgDefault - }, - highlightOnHover: { - default: lightT.bgHover, - text: lightT.fgDefault - }, - divider: { - default: lightT.border - }, - striped: { - default: lightT.bgControls, - text: lightT.fgDefault - }, - selected: { - default: lightT.bgSelected, - text: lightT.fgDefault - } -} - -export const dataTableDarkT = { - text: { - primary: darkT.fgDefault, - secondary: darkT.fgDefault, - disabled: darkT.fgDisabled - }, - background: { - default: darkT.bgDefault - }, - highlightOnHover: { - default: darkT.bgHover, - text: darkT.fgDefault - }, - divider: { - default: darkT.border - }, - striped: { - default: darkT.bgControls, - text: darkT.fgDefault - }, - selected: { - default: darkT.bgSelected, - text: darkT.fgDefault - } -}; - // ============ SELECT THEMES ========== export const selectLightT = { primary: lightT.bgPrimary, diff --git a/rsconcept/frontend/src/utils/staticUI.ts b/rsconcept/frontend/src/utils/staticUI.ts index e126b8ab..a1f4d805 100644 --- a/rsconcept/frontend/src/utils/staticUI.ts +++ b/rsconcept/frontend/src/utils/staticUI.ts @@ -242,11 +242,13 @@ export function getCstTypeShortcut(type: CstType) { } } -export const CstTypeSelector = (Object.values(CstType)).map( -(typeStr) => { - const type = typeStr as CstType; - return { value: type, label: getCstTypeLabel(type) }; -}); +export const CstTypeSelector = ( + Object.values(CstType)).map( + typeStr => ({ + value: typeStr as CstType, + label: getCstTypeLabel(typeStr as CstType) + }) +); export function getCstCompareLabel(mode: CstMatchMode): string { switch(mode) { @@ -313,7 +315,18 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [ { value: 'type', label: 'Цвет: класс'}, ]; -export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string { +export function getCstStatusBgColor(status: ExpressionStatus, colors: IColorTheme): string { + switch (status) { + case ExpressionStatus.VERIFIED: return colors.bgGreen; + case ExpressionStatus.INCORRECT: return colors.bgRed; + case ExpressionStatus.INCALCULABLE: return colors.bgOrange; + case ExpressionStatus.PROPERTY: return colors.bgTeal; + case ExpressionStatus.UNKNOWN: return colors.bgBlue; + case ExpressionStatus.UNDEFINED: return colors.bgBlue; + } +} + +export function getCstStatusFgColor(status: ExpressionStatus, colors: IColorTheme): string { switch (status) { case ExpressionStatus.VERIFIED: return colors.fgGreen; case ExpressionStatus.INCORRECT: return colors.fgRed;
Статус: {info.tooltip}
Статус: {info!.tooltip}
Список пуст
Создайте новую конституенту
handleCreateCst()}> + Создать новую конституенту +
{ isOwned && Владелец схемы } { !isOwned && Стать владельцем } @@ -165,10 +167,11 @@ function RSTabsMenu({ disabled={processing} icon={isTracking ? - : + : } widthClass='h-full w-fit' borderClass='' + style={{outlineColor: 'transparent'}} dense onClick={onToggleSubscribe} tabIndex={-1} diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx index a80914a1..6a448262 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/StatusBar.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useConceptTheme } from '../../../context/ThemeContext'; import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models'; -import { getCstStatusColor, mapStatusInfo } from '../../../utils/staticUI'; +import { getCstStatusBgColor, mapStatusInfo } from '../../../utils/staticUI'; interface StatusBarProps { isModified?: boolean @@ -27,7 +27,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) { return ( Статус: [ {data.text} ] diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx index 9f587395..f89ac8ab 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx @@ -1,12 +1,13 @@ +import { createColumnHelper } from '@tanstack/react-table'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import ConceptDataTable from '../../../components/Common/ConceptDataTable'; +import DataTable from '../../../components/Common/DataTable'; import { useRSForm } from '../../../context/RSFormContext'; import { useConceptTheme } from '../../../context/ThemeContext'; import useLocalStorage from '../../../hooks/useLocalStorage'; import { prefixes } from '../../../utils/constants'; import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models'; -import { getCstDescription, getCstStatusColor, getMockConstituenta } from '../../../utils/staticUI'; +import { getCstDescription, getCstStatusFgColor, getMockConstituenta } from '../../../utils/staticUI'; import ConstituentaTooltip from './ConstituentaTooltip'; import DependencyModePicker from './DependencyModePicker'; import MatchModePicker from './MatchModePicker'; @@ -25,6 +26,8 @@ function isMockCst(cst: IConstituenta) { return cst.id <= 0; } +const columnHelper = createColumnHelper(); + function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { const { noNavigation, colors } = useConceptTheme(); const { schema } = useRSForm(); @@ -85,75 +88,61 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } }, [onOpenEdit]); - const conditionalRowStyles = useMemo( - () => [ - { - when: (cst: IConstituenta) => cst.id === activeID, - style: { - backgroundColor: colors.bgSelected, - }, - } - ], [activeID, colors]); - const columns = useMemo( () => [ - { - id: 'id', - selector: (cst: IConstituenta) => cst.id, - omit: true - }, - { - name: 'ID', + columnHelper.accessor('alias', { id: 'alias', - cell: (cst: IConstituenta) => { + header: 'Имя', + size: 65, + minSize: 65, + cell: props => { + const cst = props.row.original; return (<> {cst.alias} >); - }, - width: '65px', - maxWidth: '65px', - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.bgWarning} - } - ] - }, - { - name: 'Описание', + } + }), + columnHelper.accessor(cst => getCstDescription(cst), { id: 'description', - selector: (cst: IConstituenta) => getCstDescription(cst), - minWidth: '350px', - wrap: true, - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.bgWarning} - } - ] - }, - { - name: 'Выражение', + header: 'Описание', + size: 350, + minSize: 350, + maxSize: 350, + cell: props => + + {props.getValue()} + + }), + columnHelper.accessor('definition_formal', { id: 'expression', - selector: (cst: IConstituenta) => cst.definition_formal || '', - minWidth: '200px', - hide: 1600, - grow: 2, - wrap: true, - conditionalCellStyles: [ - { - when: (cst: IConstituenta) => isMockCst(cst), - style: {backgroundColor: colors.fgWarning} - } - ] - } + header: 'Выражение', + size: 700, + minSize: 0, + maxSize: 700, + cell: props => + + {props.getValue()} + + }) ], [colors]); const maxHeight = useMemo( @@ -181,12 +170,14 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: onChange={setFilterSource} />
Список конституент пуст