2023-09-10 20:17:18 +03:00
|
|
|
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
|
2023-10-06 14:39:32 +03:00
|
|
|
|
import Dropdown from '../../../components/Common/Dropdown';
|
|
|
|
|
import DropdownButton from '../../../components/Common/DropdownButton';
|
|
|
|
|
import SelectorButton from '../../../components/Common/SelectorButton';
|
2023-09-10 20:17:18 +03:00
|
|
|
|
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
|
2023-10-16 02:17:31 +03:00
|
|
|
|
import { CogIcon, FilterIcon, MagnifyingGlassIcon } from '../../../components/Icons';
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import { useRSForm } from '../../../context/RSFormContext';
|
2023-07-31 22:38:58 +03:00
|
|
|
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
2023-10-06 14:39:32 +03:00
|
|
|
|
import useDropdown from '../../../hooks/useDropdown';
|
2023-07-28 00:03:37 +03:00
|
|
|
|
import useLocalStorage from '../../../hooks/useLocalStorage';
|
2023-09-10 20:17:18 +03:00
|
|
|
|
import useWindowSize from '../../../hooks/useWindowSize';
|
2023-10-06 14:39:32 +03:00
|
|
|
|
import { DependencyMode as CstSource } from '../../../models/miscelanious';
|
2023-09-11 20:31:54 +03:00
|
|
|
|
import { CstMatchMode } from '../../../models/miscelanious';
|
|
|
|
|
import { applyGraphFilter } from '../../../models/miscelanious';
|
|
|
|
|
import { CstType, extractGlobals, IConstituenta, matchConstituenta } from '../../../models/rsform';
|
2023-09-21 14:58:01 +03:00
|
|
|
|
import { createMockConstituenta } from '../../../models/rsform';
|
|
|
|
|
import { colorfgCstStatus } from '../../../utils/color';
|
2023-07-31 22:38:58 +03:00
|
|
|
|
import { prefixes } from '../../../utils/constants';
|
2023-10-06 14:39:32 +03:00
|
|
|
|
import {
|
|
|
|
|
describeConstituenta, describeCstMathchMode,
|
|
|
|
|
describeCstSource, labelCstMathchMode,
|
|
|
|
|
labelCstSource
|
|
|
|
|
} from '../../../utils/labels';
|
2023-07-30 16:48:25 +03:00
|
|
|
|
import ConstituentaTooltip from './ConstituentaTooltip';
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
// Height that should be left to accomodate navigation panel + bottom margin
|
2023-08-27 15:39:49 +03:00
|
|
|
|
const LOCAL_NAVIGATION_H = '2.1rem';
|
2023-08-13 13:18:50 +03:00
|
|
|
|
|
2023-09-10 20:17:18 +03:00
|
|
|
|
// Window width cutoff for expression show
|
|
|
|
|
const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500;
|
|
|
|
|
|
2023-07-28 00:03:37 +03:00
|
|
|
|
interface ViewSideConstituentsProps {
|
2023-07-20 17:11:03 +03:00
|
|
|
|
expression: string
|
2023-08-13 13:18:50 +03:00
|
|
|
|
baseHeight: string
|
2023-08-22 22:38:27 +03:00
|
|
|
|
activeID?: number
|
|
|
|
|
onOpenEdit: (cstID: number) => void
|
2023-08-22 20:29:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isMockCst(cst: IConstituenta) {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
return cst.id <= 0;
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-09 20:36:55 +03:00
|
|
|
|
const columnHelper = createColumnHelper<IConstituenta>();
|
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
2023-09-10 20:17:18 +03:00
|
|
|
|
const windowSize = useWindowSize();
|
2023-08-27 15:39:49 +03:00
|
|
|
|
const { noNavigation, colors } = useConceptTheme();
|
2023-08-02 18:24:17 +03:00
|
|
|
|
const { schema } = useRSForm();
|
2023-09-10 20:17:18 +03:00
|
|
|
|
|
|
|
|
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({'expression': true})
|
2023-08-02 21:35:24 +03:00
|
|
|
|
|
|
|
|
|
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
|
|
|
|
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '');
|
2023-10-06 14:39:32 +03:00
|
|
|
|
const [filterSource, setFilterSource] = useLocalStorage('side-filter-dependency', CstSource.ALL);
|
2023-08-02 21:35:24 +03:00
|
|
|
|
|
2023-07-25 20:27:29 +03:00
|
|
|
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
2023-10-06 14:39:32 +03:00
|
|
|
|
const matchModeMenu = useDropdown();
|
|
|
|
|
const sourceMenu = useDropdown();
|
|
|
|
|
|
2023-09-10 20:17:18 +03:00
|
|
|
|
useLayoutEffect(
|
|
|
|
|
() => {
|
|
|
|
|
setColumnVisibility(prev => {
|
|
|
|
|
const newValue = (windowSize.width ?? 0) >= COLUMN_EXPRESSION_HIDE_THRESHOLD;
|
|
|
|
|
if (newValue === prev['expression']) {
|
|
|
|
|
return prev;
|
|
|
|
|
} else {
|
|
|
|
|
return {'expression': newValue}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}, [windowSize]);
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(
|
2023-08-13 13:18:50 +03:00
|
|
|
|
() => {
|
2023-07-20 17:11:03 +03:00
|
|
|
|
if (!schema?.items) {
|
|
|
|
|
setFilteredData([]);
|
2023-07-26 23:11:00 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-02 21:35:24 +03:00
|
|
|
|
let filtered: IConstituenta[] = [];
|
2023-10-06 14:39:32 +03:00
|
|
|
|
if (filterSource === CstSource.EXPRESSION) {
|
2023-07-22 03:18:48 +03:00
|
|
|
|
const aliases = extractGlobals(expression);
|
2023-08-02 21:35:24 +03:00
|
|
|
|
filtered = schema.items.filter((cst) => aliases.has(cst.alias));
|
2023-07-22 03:18:48 +03:00
|
|
|
|
const names = filtered.map(cst => cst.alias)
|
2023-07-25 20:27:29 +03:00
|
|
|
|
const diff = Array.from(aliases).filter(name => !names.includes(name));
|
2023-07-22 03:18:48 +03:00
|
|
|
|
if (diff.length > 0) {
|
|
|
|
|
diff.forEach(
|
2023-08-29 15:17:16 +03:00
|
|
|
|
(alias, index) => filtered.push(
|
2023-09-21 14:58:01 +03:00
|
|
|
|
createMockConstituenta(
|
2023-08-29 15:17:16 +03:00
|
|
|
|
schema.id,
|
|
|
|
|
-index,
|
|
|
|
|
alias,
|
|
|
|
|
CstType.BASE,
|
|
|
|
|
'Конституента отсутствует'
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
2023-07-22 03:18:48 +03:00
|
|
|
|
}
|
2023-08-02 21:35:24 +03:00
|
|
|
|
} else if (!activeID) {
|
|
|
|
|
filtered = schema.items
|
2023-07-20 17:11:03 +03:00
|
|
|
|
} else {
|
2023-08-02 21:35:24 +03:00
|
|
|
|
filtered = applyGraphFilter(schema, activeID, filterSource);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
2023-08-02 21:35:24 +03:00
|
|
|
|
if (filterText) {
|
|
|
|
|
filtered = filtered.filter((cst) => matchConstituenta(filterText, cst, filterMatch));
|
|
|
|
|
}
|
|
|
|
|
setFilteredData(filtered);
|
|
|
|
|
}, [filterText, setFilteredData, filterSource, expression, schema, filterMatch, activeID]);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
|
|
|
|
const handleRowClicked = useCallback(
|
2023-07-25 20:27:29 +03:00
|
|
|
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
2023-08-22 20:29:07 +03:00
|
|
|
|
if (event.altKey && !isMockCst(cst)) {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onOpenEdit(cst.id);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}, [onOpenEdit]);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
|
|
|
|
const handleDoubleClick = useCallback(
|
2023-07-26 23:11:00 +03:00
|
|
|
|
(cst: IConstituenta) => {
|
2023-08-22 20:29:07 +03:00
|
|
|
|
if (!isMockCst(cst)) {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onOpenEdit(cst.id);
|
|
|
|
|
}
|
|
|
|
|
}, [onOpenEdit]);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
2023-10-06 14:39:32 +03:00
|
|
|
|
const handleMatchModeChange = useCallback(
|
|
|
|
|
(newValue: CstMatchMode) => {
|
|
|
|
|
matchModeMenu.hide();
|
|
|
|
|
setFilterMatch(newValue);
|
|
|
|
|
}, [matchModeMenu, setFilterMatch]);
|
|
|
|
|
|
|
|
|
|
const handleSourceChange = useCallback(
|
|
|
|
|
(newValue: CstSource) => {
|
|
|
|
|
sourceMenu.hide();
|
|
|
|
|
setFilterSource(newValue);
|
|
|
|
|
}, [sourceMenu, setFilterSource]);
|
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
const columns = useMemo(
|
|
|
|
|
() => [
|
2023-09-09 20:36:55 +03:00
|
|
|
|
columnHelper.accessor('alias', {
|
2023-08-13 13:18:50 +03:00
|
|
|
|
id: 'alias',
|
2023-09-09 20:36:55 +03:00
|
|
|
|
header: 'Имя',
|
|
|
|
|
size: 65,
|
|
|
|
|
minSize: 65,
|
2023-10-16 01:22:08 +03:00
|
|
|
|
footer: undefined,
|
2023-09-09 20:36:55 +03:00
|
|
|
|
cell: props => {
|
|
|
|
|
const cst = props.row.original;
|
2023-08-13 13:18:50 +03:00
|
|
|
|
return (<>
|
|
|
|
|
<div
|
|
|
|
|
id={`${prefixes.cst_list}${cst.alias}`}
|
2023-09-15 23:29:52 +03:00
|
|
|
|
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
|
2023-09-09 20:36:55 +03:00
|
|
|
|
style={{
|
|
|
|
|
borderWidth: '1px',
|
2023-09-21 14:58:01 +03:00
|
|
|
|
borderColor: colorfgCstStatus(cst.status, colors),
|
|
|
|
|
color: colorfgCstStatus(cst.status, colors),
|
2023-09-09 20:36:55 +03:00
|
|
|
|
fontWeight: 600,
|
|
|
|
|
backgroundColor: isMockCst(cst) ? colors.bgWarning : colors.bgInput
|
|
|
|
|
}}
|
2023-08-13 13:18:50 +03:00
|
|
|
|
>
|
|
|
|
|
{cst.alias}
|
|
|
|
|
</div>
|
|
|
|
|
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
|
|
|
|
|
</>);
|
2023-09-09 20:36:55 +03:00
|
|
|
|
}
|
|
|
|
|
}),
|
2023-09-21 14:58:01 +03:00
|
|
|
|
columnHelper.accessor(cst => describeConstituenta(cst), {
|
2023-08-13 13:18:50 +03:00
|
|
|
|
id: 'description',
|
2023-09-09 20:36:55 +03:00
|
|
|
|
header: 'Описание',
|
2023-10-04 18:46:52 +03:00
|
|
|
|
size: 1000,
|
2023-09-09 20:36:55 +03:00
|
|
|
|
minSize: 350,
|
2023-10-04 18:46:52 +03:00
|
|
|
|
maxSize: 1000,
|
2023-09-09 20:36:55 +03:00
|
|
|
|
cell: props =>
|
|
|
|
|
<div style={{
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
color: isMockCst(props.row.original) ? colors.fgWarning : undefined
|
|
|
|
|
}}>
|
|
|
|
|
{props.getValue()}
|
|
|
|
|
</div>
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor('definition_formal', {
|
2023-08-13 13:18:50 +03:00
|
|
|
|
id: 'expression',
|
2023-09-09 20:36:55 +03:00
|
|
|
|
header: 'Выражение',
|
2023-10-04 18:46:52 +03:00
|
|
|
|
size: 2000,
|
2023-09-09 20:36:55 +03:00
|
|
|
|
minSize: 0,
|
2023-10-04 18:46:52 +03:00
|
|
|
|
maxSize: 2000,
|
2023-09-10 20:17:18 +03:00
|
|
|
|
enableHiding: true,
|
2023-09-09 20:36:55 +03:00
|
|
|
|
cell: props =>
|
|
|
|
|
<div style={{
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
color: isMockCst(props.row.original) ? colors.fgWarning : undefined
|
|
|
|
|
}}>
|
|
|
|
|
{props.getValue()}
|
|
|
|
|
</div>
|
|
|
|
|
})
|
2023-08-27 00:19:19 +03:00
|
|
|
|
], [colors]);
|
2023-08-13 13:18:50 +03:00
|
|
|
|
|
2023-09-10 20:17:18 +03:00
|
|
|
|
const conditionalRowStyles = useMemo(
|
|
|
|
|
(): IConditionalStyle<IConstituenta>[] => [
|
|
|
|
|
{
|
|
|
|
|
when: (cst: IConstituenta) => cst.id === activeID,
|
|
|
|
|
style: {
|
|
|
|
|
backgroundColor: colors.bgSelected
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
], [activeID, colors]);
|
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
const maxHeight = useMemo(
|
|
|
|
|
() => {
|
|
|
|
|
const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}`
|
|
|
|
|
return (noNavigation ?
|
2023-10-14 21:17:21 +03:00
|
|
|
|
`calc(min(100vh - 8.2rem, ${siblingHeight}))`
|
|
|
|
|
: `calc(min(100vh - 11.7rem, ${siblingHeight}))`);
|
2023-08-13 13:18:50 +03:00
|
|
|
|
}, [noNavigation, baseHeight]);
|
|
|
|
|
|
|
|
|
|
return (<>
|
2023-10-14 23:46:36 +03:00
|
|
|
|
<div className='sticky top-0 left-0 right-0 flex items-stretch justify-between w-full gap-1 pl-2 border-b clr-input'>
|
2023-10-04 20:38:58 +03:00
|
|
|
|
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
|
|
|
|
|
<MagnifyingGlassIcon />
|
|
|
|
|
</div>
|
2023-08-13 13:18:50 +03:00
|
|
|
|
<input type='text'
|
2023-10-04 20:38:58 +03:00
|
|
|
|
className='w-[14rem] pr-2 pl-8 py-1 outline-none select-none hover:text-clip clr-input'
|
2023-10-06 14:39:32 +03:00
|
|
|
|
placeholder='Поиск'
|
2023-08-13 13:18:50 +03:00
|
|
|
|
value={filterText}
|
|
|
|
|
onChange={event => setFilterText(event.target.value)}
|
|
|
|
|
/>
|
2023-10-14 23:46:36 +03:00
|
|
|
|
<div className='flex'>
|
|
|
|
|
<div ref={matchModeMenu.ref}>
|
|
|
|
|
<SelectorButton
|
|
|
|
|
tooltip='Настройка атрибутов для фильтрации'
|
|
|
|
|
dimensions='w-fit h-full'
|
|
|
|
|
transparent
|
2023-10-16 02:17:31 +03:00
|
|
|
|
icon={<FilterIcon size={5} />}
|
2023-10-14 23:46:36 +03:00
|
|
|
|
text={labelCstMathchMode(filterMatch)}
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
onClick={matchModeMenu.toggle}
|
|
|
|
|
/>
|
|
|
|
|
{ matchModeMenu.isActive &&
|
|
|
|
|
<Dropdown stretchLeft>
|
|
|
|
|
{ Object.values(CstMatchMode).filter(value => !isNaN(Number(value))).map(
|
|
|
|
|
(value, index) => {
|
|
|
|
|
const matchMode = value as CstMatchMode;
|
|
|
|
|
return (
|
|
|
|
|
<DropdownButton
|
|
|
|
|
key={`${prefixes.cst_match_mode_list}${index}`}
|
|
|
|
|
onClick={() => handleMatchModeChange(matchMode)}
|
|
|
|
|
>
|
|
|
|
|
<p><span className='font-semibold'>{labelCstMathchMode(matchMode)}:</span> {describeCstMathchMode(matchMode)}</p>
|
|
|
|
|
</DropdownButton>);
|
|
|
|
|
})}
|
|
|
|
|
</Dropdown>}
|
|
|
|
|
</div>
|
2023-10-06 14:39:32 +03:00
|
|
|
|
|
2023-10-14 23:46:36 +03:00
|
|
|
|
<div ref={sourceMenu.ref}>
|
|
|
|
|
<SelectorButton
|
|
|
|
|
tooltip='Настройка фильтрации по графу термов'
|
|
|
|
|
dimensions='w-fit h-full'
|
|
|
|
|
transparent
|
|
|
|
|
icon={<CogIcon size={4} />}
|
|
|
|
|
text={labelCstSource(filterSource)}
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
onClick={sourceMenu.toggle}
|
|
|
|
|
/>
|
|
|
|
|
{ sourceMenu.isActive &&
|
|
|
|
|
<Dropdown stretchLeft>
|
|
|
|
|
{ Object.values(CstSource).filter(value => !isNaN(Number(value))).map(
|
|
|
|
|
(value, index) => {
|
|
|
|
|
const source = value as CstSource;
|
|
|
|
|
return (
|
|
|
|
|
<DropdownButton
|
|
|
|
|
key={`${prefixes.cst_source_list}${index}`}
|
|
|
|
|
onClick={() => handleSourceChange(source)}
|
|
|
|
|
>
|
|
|
|
|
<p><span className='font-semibold'>{labelCstSource(source)}:</span> {describeCstSource(source)}</p>
|
|
|
|
|
</DropdownButton>);
|
|
|
|
|
})}
|
|
|
|
|
</Dropdown>}
|
|
|
|
|
</div>
|
2023-10-06 14:39:32 +03:00
|
|
|
|
</div>
|
2023-08-13 13:18:50 +03:00
|
|
|
|
</div>
|
2023-09-09 20:36:55 +03:00
|
|
|
|
<div className='overflow-y-auto text-sm' style={{maxHeight : `${maxHeight}`}}>
|
|
|
|
|
<DataTable
|
2023-07-20 17:11:03 +03:00
|
|
|
|
data={filteredData}
|
|
|
|
|
columns={columns}
|
2023-09-10 20:17:18 +03:00
|
|
|
|
conditionalRowStyles={conditionalRowStyles}
|
2023-09-30 12:47:27 +03:00
|
|
|
|
headPosition='0'
|
2023-09-10 20:17:18 +03:00
|
|
|
|
dense
|
2023-10-16 01:22:08 +03:00
|
|
|
|
noFooter
|
2023-09-09 20:36:55 +03:00
|
|
|
|
|
2023-09-10 20:17:18 +03:00
|
|
|
|
enableHiding
|
|
|
|
|
columnVisibility={columnVisibility}
|
|
|
|
|
onColumnVisibilityChange={setColumnVisibility}
|
2023-09-09 20:36:55 +03:00
|
|
|
|
|
2023-08-13 13:18:50 +03:00
|
|
|
|
noDataComponent={
|
2023-10-14 23:56:08 +03:00
|
|
|
|
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem] select-none'>
|
2023-08-13 13:18:50 +03:00
|
|
|
|
<p>Список конституент пуст</p>
|
|
|
|
|
<p>Измените параметры фильтра</p>
|
|
|
|
|
</span>
|
|
|
|
|
}
|
2023-07-20 17:11:03 +03:00
|
|
|
|
|
|
|
|
|
onRowDoubleClicked={handleDoubleClick}
|
|
|
|
|
onRowClicked={handleRowClicked}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-08-13 13:18:50 +03:00
|
|
|
|
</>);
|
2023-07-20 17:11:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-28 00:03:37 +03:00
|
|
|
|
export default ViewSideConstituents;
|