Refactor data tables and implement argument selection

This commit is contained in:
IRBorisov 2023-11-06 18:03:23 +03:00
parent cefd0d3c40
commit cc79fffd34
14 changed files with 365 additions and 263 deletions

View File

@ -7,14 +7,15 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'title
} }
function MiniButton({ function MiniButton({
icon, tooltip, noHover, tabIndex, dimensions, icon, tooltip, noHover, tabIndex,
dimensions='w-fit h-fit',
...props ...props
}: MiniButtonProps) { }: MiniButtonProps) {
return ( return (
<button type='button' <button type='button'
title={tooltip} title={tooltip}
tabIndex={tabIndex ?? -1} tabIndex={tabIndex ?? -1}
className={`px-1 py-1 w-fit h-fit rounded-full cursor-pointer disabled:cursor-not-allowed clr-btn-clear ${noHover ? 'outline-none' : 'clr-hover'} ${dimensions}`} className={`px-1 py-1 rounded-full cursor-pointer disabled:cursor-not-allowed clr-btn-clear ${noHover ? 'outline-none' : 'clr-hover'} ${dimensions}`}
{...props} {...props}
> >
{icon} {icon}

View File

@ -19,8 +19,8 @@ function TextInput({
colors = 'clr-input', colors = 'clr-input',
...props ...props
}: TextInputProps) { }: TextInputProps) {
const borderClass = noBorder ? '': 'border'; const borderClass = noBorder ? '' : 'border';
const outlineClass = noOutline ? '': 'clr-outline'; const outlineClass = noOutline ? '' : 'clr-outline';
return ( return (
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}> <div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label && {label &&

View File

@ -48,6 +48,7 @@ extends Pick<ReactCodeMirrorProps,
'id' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder' 'id' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
> { > {
label?: string label?: string
dimensions?: string
disabled?: boolean disabled?: boolean
innerref?: RefObject<ReactCodeMirrorRef> | undefined innerref?: RefObject<ReactCodeMirrorRef> | undefined
onChange?: (newValue: string) => void onChange?: (newValue: string) => void
@ -55,6 +56,7 @@ extends Pick<ReactCodeMirrorProps,
function RSInput({ function RSInput({
id, label, innerref, onChange, disabled, id, label, innerref, onChange, disabled,
dimensions = 'w-full',
...props ...props
}: RSInputProps) { }: RSInputProps) {
const { darkMode, colors } = useConceptTheme(); const { darkMode, colors } = useConceptTheme();
@ -126,7 +128,7 @@ function RSInput({
}, [thisRef]); }, [thisRef]);
return ( return (
<div className={`flex flex-col w-full ${cursor}`}> <div className={`flex flex-col ${dimensions} ${cursor}`}>
{label && {label &&
<Label <Label
text={label} text={label}

View File

@ -0,0 +1,40 @@
import { IConstituenta, isMockCst } from '../../models/rsform';
import { colorfgCstStatus,IColorTheme } from '../../utils/color';
import { describeExpressionStatus } from '../../utils/labels';
import ConceptTooltip from '../Common/ConceptTooltip';
import ConstituentaTooltip from '../Help/ConstituentaTooltip';
interface ConstituentaBadgeProps {
prefixID?: string
shortTooltip?: boolean
value: IConstituenta
theme: IColorTheme
}
function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: ConstituentaBadgeProps) {
return (<div className='w-fit'>
<div
id={`${prefixID}${value.alias}`}
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
style={{
borderWidth: '1px',
borderColor: colorfgCstStatus(value.status, theme),
color: colorfgCstStatus(value.status, theme),
backgroundColor: isMockCst(value) ? theme.bgWarning : theme.bgInput,
fontWeight: 600
}}
>
{value.alias}
</div>
{ !shortTooltip && <ConstituentaTooltip data={value} anchor={`#${prefixID}${value.alias}`} />}
{ shortTooltip &&
<ConceptTooltip
anchorSelect={`#${prefixID}${value.alias}`}
place='right'
>
<p><span className='font-semibold'>Статус</span>: {describeExpressionStatus(value.status)}</p>
</ConceptTooltip>}
</div>);
}
export default ConstituentaBadge;

View File

@ -0,0 +1,108 @@
import { useEffect, useMemo, useState } from 'react';
import { useConceptTheme } from '../../context/ThemeContext';
import { CstMatchMode } from '../../models/miscelanious';
import { IConstituenta, matchConstituenta } from '../../models/rsform';
import { prefixes } from '../../utils/constants';
import { describeConstituenta } from '../../utils/labels';
import ConceptSearch from '../Common/ConceptSearch';
import DataTable, { createColumnHelper,IConditionalStyle } from '../DataTable';
import ConstituentaBadge from './ConstituentaBadge';
interface ConstituentaPickerProps {
prefixID?: string
data?: IConstituenta[]
rows?: number
prefilterFunc?: (cst: IConstituenta) => boolean
describeFunc?: (cst: IConstituenta) => string
matchFunc?: (cst: IConstituenta, filter: string) => boolean
value?: IConstituenta
onSelectValue: (newValue: IConstituenta) => void
}
const columnHelper = createColumnHelper<IConstituenta>();
function ConstituentaPicker({
data, value,
rows = 4,
prefixID = prefixes.cst_list,
describeFunc = describeConstituenta,
matchFunc = (cst, filter) => matchConstituenta(cst, filter, CstMatchMode.ALL),
prefilterFunc,
onSelectValue
} : ConstituentaPickerProps) {
const { colors } = useConceptTheme();
const [ filteredData, setFilteredData ] = useState<IConstituenta[]>([]);
const [ filterText, setFilterText ] = useState('');
useEffect(
() => {
if (!data) {
setFilteredData([]);
} else {
const newData = prefilterFunc ? data.filter(prefilterFunc) : data;
if (filterText) {
setFilteredData(newData.filter(cst => matchFunc(cst, filterText)));
} else {
setFilteredData(newData);
}
}
}, [data, filterText, matchFunc, prefilterFunc]);
const columns = useMemo(
() => [
columnHelper.accessor('alias', {
id: 'alias',
size: 65,
minSize: 65,
maxSize: 65,
cell: props =>
<ConstituentaBadge
theme={colors}
value={props.row.original}
prefixID={prefixID}
/>
}),
columnHelper.accessor(cst => describeFunc(cst), {
id: 'description'
})
], [colors, prefixID, describeFunc]);
const size = useMemo(() => (`${0.125 + 1.94*rows}rem`), [rows]);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IConstituenta>[] => [{
when: (cst: IConstituenta) => cst.id === value?.id,
style: { backgroundColor: colors.bgSelected },
}], [value, colors]);
return (
<div>
<ConceptSearch dense
value={filterText}
onChange={newValue => setFilterText(newValue)}
/>
<div
className='overflow-y-auto text-sm border select-none'
style={{ maxHeight: size, minHeight: size }}
>
<DataTable dense noHeader
data={filteredData}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
</span>
}
onRowClicked={onSelectValue}
/>
</div>
</div>);
}
export default ConstituentaPicker;

View File

@ -1,28 +1,167 @@
import { Dispatch } from 'react'; import { createColumnHelper } from '@tanstack/react-table';
import { Dispatch, useCallback, useMemo, useState } from 'react';
import ConceptSearch from '../../components/Common/ConceptSearch'; import MiniButton from '../../components/Common/MiniButton';
import DataTable, { IConditionalStyle } from '../../components/DataTable';
import { CheckIcon, CrossIcon } from '../../components/Icons';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
import { useConceptTheme } from '../../context/ThemeContext';
import { IConstituenta, IRSForm } from '../../models/rsform';
import { IArgumentValue } from '../../models/rslang'; import { IArgumentValue } from '../../models/rslang';
import { prefixes } from '../../utils/constants';
interface ArgumentsTabProps { interface ArgumentsTabProps {
state: IArgumentsState state: IArgumentsState
schema: IRSForm
partialUpdate: Dispatch<Partial<IArgumentsState>> partialUpdate: Dispatch<Partial<IArgumentsState>>
} }
export interface IArgumentsState { export interface IArgumentsState {
arguments?: IArgumentValue[] arguments: IArgumentValue[]
filterText: string
definition: string definition: string
} }
function ArgumentsTab({state, partialUpdate}: ArgumentsTabProps) { const argumentsHelper = createColumnHelper<IArgumentValue>();
function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
const { colors } = useConceptTheme();
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(undefined);
const [argumentValue, setArgumentValue] = useState('');
const handleSelectArgument = useCallback(
(arg: IArgumentValue) => {
setSelectedArgument(arg);
}, []);
const handleSelectConstituenta = useCallback(
(cst: IConstituenta) => {
setSelectedCst(cst);
setArgumentValue(cst.alias);
}, []);
const handleClearArgument = useCallback(
(target: IArgumentValue) => {
target.value = '';
partialUpdate({
arguments: [
target,
...state.arguments.filter(arg => arg.alias !== target.alias)
]
});
}, [partialUpdate, state.arguments]);
const handleAssignArgument = useCallback(
(target: IArgumentValue, value: string) => {
target.value = value;
partialUpdate({
arguments: [
target,
...state.arguments.filter(arg => arg.alias !== target.alias)
]
});
}, [partialUpdate, state.arguments]);
const columns = useMemo(
() => [
argumentsHelper.accessor('alias', {
id: 'alias',
header: 'Имя',
size: 40,
minSize: 40,
maxSize: 40,
cell: props => <div className='w-full text-center'>{props.getValue()}</div>
}),
argumentsHelper.accessor(arg => arg.value || 'свободный аргумент', {
id: 'value',
header: 'Значение',
size: 200,
minSize: 200,
maxSize: 200,
}),
argumentsHelper.accessor(arg => arg.typification, {
id: 'type',
header: 'Типизация',
size: 150,
minSize: 150,
maxSize: 150,
enableHiding: true,
cell: props => <div className='text-sm min-w-[9.3rem] max-w-[9.3rem] break-words'>{props.getValue()}</div>
}),
argumentsHelper.display({
id: 'actions',
size: 50,
minSize: 50,
maxSize: 50,
cell: props =>
<div className='max-h-[1.2rem]'>
{props.row.original.value &&
<MiniButton
tooltip='Очистить значение'
icon={<CrossIcon size={3} color='text-warning'/>}
noHover
onClick={() => handleClearArgument(props.row.original)}
/>}
</div>
})
], [handleClearArgument]);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IArgumentValue>[] => [{
when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias,
style: { backgroundColor: colors.bgSelected },
}], [selectedArgument, colors]);
return ( return (
<div className='flex flex-col gap-3'> <div className='flex flex-col gap-3'>
<ConceptSearch
value={state.filterText} <div className='overflow-y-auto text-sm border select-none max-h-[7.5rem] min-h-[7.5rem]'>
onChange={newValue => partialUpdate({ filterText: newValue} )} <DataTable dense
dense data={state.arguments}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
<p>Аргументы отсутствуют</p>
</span>
}
onRowClicked={handleSelectArgument}
/>
</div>
<div
title='Выберите аргумент из списка сверху и значение из списка снизу'
className='flex items-center justify-center w-full gap-2 select-none'
>
<span className='font-semibold text-center'>{selectedArgument?.alias || 'ARG'}</span>
<span>=</span>
<RSInput
dimensions='max-w-[12rem] w-full'
value={argumentValue}
onChange={newValue => setArgumentValue(newValue)}
/>
<MiniButton
tooltip='Подставить значение аргумента'
icon={<CheckIcon
size={6}
color={!argumentValue || !selectedArgument ? 'text-disabled' : 'text-success'}
/>}
disabled={!argumentValue || !selectedArgument}
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
/>
</div>
<ConstituentaPicker
value={selectedCst}
data={schema?.items}
onSelectValue={handleSelectConstituenta}
prefixID={prefixes.cst_modal_list}
rows={5}
/> />
<RSInput id='result' <RSInput id='result'
placeholder='Итоговое определение' placeholder='Итоговое определение'
height='4.8rem' height='4.8rem'

View File

@ -1,23 +1,16 @@
import { Dispatch, useEffect, useMemo, useState } from 'react'; import { Dispatch, useEffect, useMemo, useState } from 'react';
import ConceptSearch from '../../components/Common/ConceptSearch';
import SelectSingle from '../../components/Common/SelectSingle'; import SelectSingle from '../../components/Common/SelectSingle';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/Common/TextArea';
import DataTable, { createColumnHelper,IConditionalStyle } from '../../components/DataTable';
import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { applyFilterCategory, CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform';
import { CstMatchMode } from '../../models/miscelanious';
import { applyFilterCategory, CATEGORY_CST_TYPE, IConstituenta, IRSForm, matchConstituenta } from '../../models/rsform';
import { colorfgCstStatus } from '../../utils/color';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
export interface ITemplateState { export interface ITemplateState {
templateID?: number templateID?: number
prototype?: IConstituenta prototype?: IConstituenta
filterCategory?: IConstituenta filterCategory?: IConstituenta
filterText: string
} }
interface TemplateTabProps { interface TemplateTabProps {
@ -25,11 +18,7 @@ interface TemplateTabProps {
partialUpdate: Dispatch<Partial<ITemplateState>> partialUpdate: Dispatch<Partial<ITemplateState>>
} }
const constituentaHelper = createColumnHelper<IConstituenta>(); function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
const { colors } = useConceptTheme();
const { templates, retrieveTemplate } = useLibrary(); const { templates, retrieveTemplate } = useLibrary();
const [ selectedSchema, setSelectedSchema ] = useState<IRSForm | undefined>(undefined); const [ selectedSchema, setSelectedSchema ] = useState<IRSForm | undefined>(undefined);
@ -91,58 +80,8 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
if (state.filterCategory) { if (state.filterCategory) {
data = applyFilterCategory(state.filterCategory, selectedSchema); data = applyFilterCategory(state.filterCategory, selectedSchema);
} }
if (state.filterText) {
data = data.filter(cst => matchConstituenta(state.filterText, cst, CstMatchMode.TERM));
}
setFilteredData(data); setFilteredData(data);
}, [state.filterText, state.filterCategory, selectedSchema]); }, [state.filterCategory, selectedSchema]);
function handleSelectTemplate(cst: IConstituenta) {
partialUpdate( { prototype: cst } );
}
const columns = useMemo(
() => [
constituentaHelper.accessor('alias', {
id: 'alias',
size: 65,
minSize: 65,
cell: props => {
const cst = props.row.original;
return (<>
<div
id={`${prefixes.cst_template_ist}${cst.alias}`}
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
style={{
borderWidth: '1px',
borderColor: colorfgCstStatus(cst.status, colors),
color: colorfgCstStatus(cst.status, colors),
fontWeight: 600
}}
>
{cst.alias}
</div>
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_template_ist}${cst.alias}`} />
</>);
}
}),
constituentaHelper.accessor('term_resolved', {
id: 'term',
size: 600,
minSize: 350,
maxSize: 600
})
], [colors]);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IConstituenta>[] => [
{
when: (cst: IConstituenta) => cst.id === state.prototype?.id,
style: {
backgroundColor: colors.bgSelected
},
}
], [state.prototype, colors]);
return ( return (
<div className='flex flex-col gap-3'> <div className='flex flex-col gap-3'>
@ -166,32 +105,13 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
onChange={data => partialUpdate({templateID: (data ? data.value : undefined)})} onChange={data => partialUpdate({templateID: (data ? data.value : undefined)})}
/> />
</div> </div>
<div> <ConstituentaPicker
<ConceptSearch value={state.prototype}
value={state.filterText} data={filteredData}
onChange={newValue => partialUpdate({ filterText: newValue} )} onSelectValue={cst => partialUpdate( { prototype: cst } )}
dense prefixID={prefixes.cst_template_ist}
/> rows={9}
<div className='border min-h-[17.5rem] max-h-[17.5rem] text-sm overflow-y-auto select-none'> />
<DataTable
data={filteredData}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
dense
noHeader
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
</span>
}
onRowClicked={handleSelectTemplate}
/>
</div>
</div>
<TextArea id='term' <TextArea id='term'
rows={1} rows={1}
disabled disabled

View File

@ -27,12 +27,10 @@ export enum TabID {
function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituentaTemplateProps) { function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituentaTemplateProps) {
const [validated, setValidated] = useState(false); const [validated, setValidated] = useState(false);
const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({ const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({});
filterText: ''
});
const [ argumentsData, updateArgumentsData ] = usePartialUpdate<IArgumentsState>({ const [ argumentsData, updateArgumentsData ] = usePartialUpdate<IArgumentsState>({
filterText: '', definition: '',
definition: '' arguments: []
}); });
const [cstData, updateCstData] = usePartialUpdate<ICstCreateData>({ const [cstData, updateCstData] = usePartialUpdate<ICstCreateData>({
cst_type: CstType.TERM, cst_type: CstType.TERM,
@ -80,7 +78,13 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituen
}); });
updateArgumentsData({ updateArgumentsData({
definition: templateData.prototype.definition_formal, definition: templateData.prototype.definition_formal,
arguments: [] arguments: templateData.prototype.parse.args.map(
arg => ({
alias: arg.alias,
typification: arg.typification,
value: ''
})
)
}); });
} }
}, [templateData.prototype, updateCstData, updateArgumentsData]); }, [templateData.prototype, updateCstData, updateArgumentsData]);
@ -136,6 +140,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituen
<TabPanel> <TabPanel>
<ArgumentsTab <ArgumentsTab
schema={schema}
state={argumentsData} state={argumentsData}
partialUpdate={updateArgumentsData} partialUpdate={updateArgumentsData}
/> />

View File

@ -1,17 +1,13 @@
import { createColumnHelper } from '@tanstack/react-table';
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import ConceptSearch from '../../components/Common/ConceptSearch';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Label from '../../components/Common/Label'; import Label from '../../components/Common/Label';
import Modal from '../../components/Common/Modal'; import Modal from '../../components/Common/Modal';
import SelectMulti from '../../components/Common/SelectMulti'; import SelectMulti from '../../components/Common/SelectMulti';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/Common/TextInput';
import DataTable, { IConditionalStyle } from '../../components/DataTable';
import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip';
import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl'; import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl';
import { HelpIcon } from '../../components/Icons'; import { HelpIcon } from '../../components/Icons';
import { useConceptTheme } from '../../context/ThemeContext'; import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
import { import {
getCompatibleGrams, Grammeme, getCompatibleGrams, Grammeme,
parseEntityReference, parseGrammemes, parseEntityReference, parseGrammemes,
@ -19,7 +15,6 @@ import {
} from '../../models/language'; } from '../../models/language';
import { CstMatchMode } from '../../models/miscelanious'; import { CstMatchMode } from '../../models/miscelanious';
import { IConstituenta, matchConstituenta } from '../../models/rsform'; import { IConstituenta, matchConstituenta } from '../../models/rsform';
import { colorfgCstStatus } from '../../utils/color';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors'; import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
import ReferenceTypeButton from './ReferenceTypeButton'; import ReferenceTypeButton from './ReferenceTypeButton';
@ -40,20 +35,16 @@ interface DlgEditReferenceProps {
onSave: (newRef: string) => void onSave: (newRef: string) => void
} }
const constituentaHelper = createColumnHelper<IConstituenta>();
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) { function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
const { colors } = useConceptTheme();
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY); const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
const [nominal, setNominal] = useState(''); const [nominal, setNominal] = useState('');
const [offset, setOffset] = useState(1); const [offset, setOffset] = useState(1);
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
const [alias, setAlias] = useState(''); const [alias, setAlias] = useState('');
const [term, setTerm] = useState(''); const [term, setTerm] = useState('');
const [filter, setFilter] = useState('');
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]); const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]); const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
@ -105,24 +96,9 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
} }
} else if (initial.text) { } else if (initial.text) {
setNominal(initial.text ?? ''); setNominal(initial.text ?? '');
setFilter(initial.text);
} }
}, [initial, items]); }, [initial, items]);
// Filter constituents
useEffect(
() => {
if (filter === '') {
setFilteredData(items.filter(
(cst) => cst.term_resolved !== '')
);
} else {
setFilteredData(items.filter(
(cst) => matchConstituenta(filter, cst, CstMatchMode.TERM))
);
}
}, [filter, items]);
// Filter grammemes when input changes // Filter grammemes when input changes
useEffect( useEffect(
() => { () => {
@ -145,6 +121,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
function handleSelectConstituenta(cst: IConstituenta) { function handleSelectConstituenta(cst: IConstituenta) {
setAlias(cst.alias); setAlias(cst.alias);
setSelectedCst(cst);
} }
const handleSelectGrams = useCallback( const handleSelectGrams = useCallback(
@ -179,49 +156,6 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
</div>); </div>);
}, [handleSelectGrams, selectedGrams]); }, [handleSelectGrams, selectedGrams]);
const columnsConstituenta = useMemo(
() => [
constituentaHelper.accessor('alias', {
id: 'alias',
size: 65,
minSize: 65,
cell: props => {
const cst = props.row.original;
return (<>
<div
id={`${prefixes.cst_wordform_list}${cst.alias}`}
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
style={{
borderWidth: '1px',
borderColor: colorfgCstStatus(cst.status, colors),
color: colorfgCstStatus(cst.status, colors),
fontWeight: 600
}}
>
{cst.alias}
</div>
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_wordform_list}${cst.alias}`} />
</>);
}
}),
constituentaHelper.accessor('term_resolved', {
id: 'term',
size: 600,
minSize: 350,
maxSize: 600
})
], [colors]);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IConstituenta>[] => [
{
when: (cst: IConstituenta) => cst.alias === alias,
style: {
backgroundColor: colors.bgSelected
},
}
], [alias, colors]);
return ( return (
<Modal <Modal
@ -286,32 +220,17 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
</div>} </div>}
{type === ReferenceType.ENTITY && {type === ReferenceType.ENTITY &&
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
<div> <ConstituentaPicker
<ConceptSearch value={selectedCst}
value={filter} data={items}
onChange={newValue => setFilter(newValue)} onSelectValue={handleSelectConstituenta}
dense prefixID={prefixes.cst_modal_list}
/> describeFunc={cst => cst.term_resolved}
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto'> matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
<DataTable prefilterFunc={cst => cst.term_resolved !== ''}
data={filteredData} rows={8}
columns={columnsConstituenta} />
conditionalRowStyles={conditionalRowStyles}
noHeader
dense
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
</span>
}
onRowClicked={handleSelectConstituenta}
/>
</div>
</div>
<div className='flex gap-4 flex-start'> <div className='flex gap-4 flex-start'>
<TextInput <TextInput
label='Отсылаемая конституента' label='Отсылаемая конституента'

View File

@ -223,7 +223,7 @@ export function loadRSFormData(schema: IRSFormData): IRSForm {
return result; return result;
} }
export function matchConstituenta(query: string, target: IConstituenta, mode: CstMatchMode): boolean { export function matchConstituenta(target: IConstituenta, query: string, mode: CstMatchMode): boolean {
const matcher = new TextMatcher(query); const matcher = new TextMatcher(query);
if ((mode === CstMatchMode.ALL || mode === CstMatchMode.NAME) && if ((mode === CstMatchMode.ALL || mode === CstMatchMode.NAME) &&
matcher.test(target.alias)) { matcher.test(target.alias)) {
@ -283,6 +283,10 @@ export function inferClass(type: CstType, isTemplate: boolean): CstClass {
} }
} }
export function isMockCst(cst: IConstituenta) {
return cst.id <= 0;
}
export function createMockConstituenta(schema: number, id: number, alias: string, type: CstType, comment: string): IConstituenta { export function createMockConstituenta(schema: number, id: number, alias: string, type: CstType, comment: string): IConstituenta {
return { return {
id: id, id: id,

View File

@ -48,8 +48,7 @@ export interface IArgumentInfo {
typification: string typification: string
} }
export interface IArgumentValue { export interface IArgumentValue extends IArgumentInfo {
alias: string
value?: string value?: string
} }

View File

@ -1,15 +1,14 @@
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable'; import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
import ConstituentaBadge from '../../components/Shared/ConstituentaBadge';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useWindowSize from '../../hooks/useWindowSize'; import useWindowSize from '../../hooks/useWindowSize';
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../models/rsform' import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../models/rsform'
import { colorfgCstStatus } from '../../utils/color';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { describeExpressionStatus, labelCstTypification } from '../../utils/labels'; import { labelCstTypification } from '../../utils/labels';
import RSItemsMenu from './elements/RSItemsMenu'; import RSItemsMenu from './elements/RSItemsMenu';
// Window width cutoff for columns // Window width cutoff for columns
@ -239,30 +238,13 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edit
size: 65, size: 65,
minSize: 65, minSize: 65,
maxSize: 65, maxSize: 65,
cell: props => { cell: props =>
const cst = props.row.original; <ConstituentaBadge
return (<> theme={colors}
<div value={props.row.original}
id={`${prefixes.cst_list}${cst.alias}`} prefixID={prefixes.cst_list}
className='w-full min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap' shortTooltip
style={{ />
borderWidth: "1px",
borderColor: colorfgCstStatus(cst.status, colors),
color: colorfgCstStatus(cst.status, colors),
fontWeight: 600,
backgroundColor: colors.bgInput
}}
>
{cst.alias}
</div>
<ConceptTooltip
anchorSelect={`#${prefixes.cst_list}${cst.alias}`}
place='right'
>
<p><span className='font-semibold'>Статус</span>: {describeExpressionStatus(cst.status)}</p>
</ConceptTooltip>
</>);
}
}), }),
columnHelper.accessor(cst => labelCstTypification(cst), { columnHelper.accessor(cst => labelCstTypification(cst), {
id: 'type', id: 'type',

View File

@ -5,6 +5,7 @@ import DropdownButton from '../../../components/Common/DropdownButton';
import SelectorButton from '../../../components/Common/SelectorButton'; import SelectorButton from '../../../components/Common/SelectorButton';
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
import { CogIcon, FilterIcon, MagnifyingGlassIcon } from '../../../components/Icons'; import { CogIcon, FilterIcon, MagnifyingGlassIcon } from '../../../components/Icons';
import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge';
import { useRSForm } from '../../../context/RSFormContext'; import { useRSForm } from '../../../context/RSFormContext';
import { useConceptTheme } from '../../../context/ThemeContext'; import { useConceptTheme } from '../../../context/ThemeContext';
import useDropdown from '../../../hooks/useDropdown'; import useDropdown from '../../../hooks/useDropdown';
@ -13,16 +14,14 @@ import useWindowSize from '../../../hooks/useWindowSize';
import { DependencyMode as CstSource } from '../../../models/miscelanious'; import { DependencyMode as CstSource } from '../../../models/miscelanious';
import { CstMatchMode } from '../../../models/miscelanious'; import { CstMatchMode } from '../../../models/miscelanious';
import { applyGraphFilter } from '../../../models/miscelanious'; import { applyGraphFilter } from '../../../models/miscelanious';
import { CstType, extractGlobals, IConstituenta, matchConstituenta } from '../../../models/rsform'; import { CstType, extractGlobals, IConstituenta, isMockCst, matchConstituenta } from '../../../models/rsform';
import { createMockConstituenta } from '../../../models/rsform'; import { createMockConstituenta } from '../../../models/rsform';
import { colorfgCstStatus } from '../../../utils/color';
import { prefixes } from '../../../utils/constants'; import { prefixes } from '../../../utils/constants';
import { import {
describeConstituenta, describeCstMathchMode, describeConstituenta, describeCstMathchMode,
describeCstSource, labelCstMathchMode, describeCstSource, labelCstMathchMode,
labelCstSource labelCstSource
} from '../../../utils/labels'; } from '../../../utils/labels';
import ConstituentaTooltip from '../../../components/Help/ConstituentaTooltip';
// Height that should be left to accomodate navigation panel + bottom margin // Height that should be left to accomodate navigation panel + bottom margin
const LOCAL_NAVIGATION_H = '2.1rem'; const LOCAL_NAVIGATION_H = '2.1rem';
@ -37,10 +36,6 @@ interface ViewSideConstituentsProps {
onOpenEdit: (cstID: number) => void onOpenEdit: (cstID: number) => void
} }
function isMockCst(cst: IConstituenta) {
return cst.id <= 0;
}
const columnHelper = createColumnHelper<IConstituenta>(); const columnHelper = createColumnHelper<IConstituenta>();
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) { function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
@ -102,7 +97,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
filtered = applyGraphFilter(schema, activeID, filterSource); filtered = applyGraphFilter(schema, activeID, filterSource);
} }
if (filterText) { if (filterText) {
filtered = filtered.filter((cst) => matchConstituenta(filterText, cst, filterMatch)); filtered = filtered.filter(cst => matchConstituenta(cst, filterText, filterMatch));
} }
setFilteredData(filtered); setFilteredData(filtered);
}, [filterText, setFilteredData, filterSource, expression, schema, filterMatch, activeID]); }, [filterText, setFilteredData, filterSource, expression, schema, filterMatch, activeID]);
@ -141,25 +136,12 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
size: 65, size: 65,
minSize: 65, minSize: 65,
footer: undefined, footer: undefined,
cell: props => { cell: props =>
const cst = props.row.original; <ConstituentaBadge
return (<> theme={colors}
<div value={props.row.original}
id={`${prefixes.cst_list}${cst.alias}`} prefixID={prefixes.cst_list}
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap' />
style={{
borderWidth: '1px',
borderColor: colorfgCstStatus(cst.status, colors),
color: colorfgCstStatus(cst.status, colors),
fontWeight: 600,
backgroundColor: isMockCst(cst) ? colors.bgWarning : colors.bgInput
}}
>
{cst.alias}
</div>
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
</>);
}
}), }),
columnHelper.accessor(cst => describeConstituenta(cst), { columnHelper.accessor(cst => describeConstituenta(cst), {
id: 'description', id: 'description',

View File

@ -34,6 +34,7 @@ export const globalIDs = {
export const prefixes = { export const prefixes = {
cst_list: 'cst-list-', cst_list: 'cst-list-',
cst_modal_list: 'cst-modal-list-',
cst_template_ist: 'cst-template-list-', cst_template_ist: 'cst-template-list-',
cst_wordform_list: 'cst-wordform-list-', cst_wordform_list: 'cst-wordform-list-',
cst_status_list: 'cst-status-list-', cst_status_list: 'cst-status-list-',