mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-15 05:10:36 +03:00
Refactor data tables and implement argument selection
This commit is contained in:
parent
cefd0d3c40
commit
cc79fffd34
|
@ -7,14 +7,15 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'title
|
|||
}
|
||||
|
||||
function MiniButton({
|
||||
icon, tooltip, noHover, tabIndex, dimensions,
|
||||
icon, tooltip, noHover, tabIndex,
|
||||
dimensions='w-fit h-fit',
|
||||
...props
|
||||
}: MiniButtonProps) {
|
||||
return (
|
||||
<button type='button'
|
||||
title={tooltip}
|
||||
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}
|
||||
>
|
||||
{icon}
|
||||
|
|
|
@ -19,8 +19,8 @@ function TextInput({
|
|||
colors = 'clr-input',
|
||||
...props
|
||||
}: TextInputProps) {
|
||||
const borderClass = noBorder ? '': 'border';
|
||||
const outlineClass = noOutline ? '': 'clr-outline';
|
||||
const borderClass = noBorder ? '' : 'border';
|
||||
const outlineClass = noOutline ? '' : 'clr-outline';
|
||||
return (
|
||||
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
|
||||
{label &&
|
||||
|
|
|
@ -48,6 +48,7 @@ extends Pick<ReactCodeMirrorProps,
|
|||
'id' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
dimensions?: string
|
||||
disabled?: boolean
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
|
@ -55,6 +56,7 @@ extends Pick<ReactCodeMirrorProps,
|
|||
|
||||
function RSInput({
|
||||
id, label, innerref, onChange, disabled,
|
||||
dimensions = 'w-full',
|
||||
...props
|
||||
}: RSInputProps) {
|
||||
const { darkMode, colors } = useConceptTheme();
|
||||
|
@ -126,7 +128,7 @@ function RSInput({
|
|||
}, [thisRef]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col w-full ${cursor}`}>
|
||||
<div className={`flex flex-col ${dimensions} ${cursor}`}>
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
|
|
|
@ -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;
|
108
rsconcept/frontend/src/components/Shared/ConstituentaPicker.tsx
Normal file
108
rsconcept/frontend/src/components/Shared/ConstituentaPicker.tsx
Normal 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;
|
|
@ -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 ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { IArgumentValue } from '../../models/rslang';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
|
||||
interface ArgumentsTabProps {
|
||||
state: IArgumentsState
|
||||
schema: IRSForm
|
||||
partialUpdate: Dispatch<Partial<IArgumentsState>>
|
||||
}
|
||||
|
||||
export interface IArgumentsState {
|
||||
arguments?: IArgumentValue[]
|
||||
filterText: string
|
||||
arguments: IArgumentValue[]
|
||||
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 (
|
||||
<div className='flex flex-col gap-3'>
|
||||
<ConceptSearch
|
||||
value={state.filterText}
|
||||
onChange={newValue => partialUpdate({ filterText: newValue} )}
|
||||
dense
|
||||
|
||||
<div className='overflow-y-auto text-sm border select-none max-h-[7.5rem] min-h-[7.5rem]'>
|
||||
<DataTable 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'
|
||||
placeholder='Итоговое определение'
|
||||
height='4.8rem'
|
||||
|
|
|
@ -1,23 +1,16 @@
|
|||
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import ConceptSearch from '../../components/Common/ConceptSearch';
|
||||
import SelectSingle from '../../components/Common/SelectSingle';
|
||||
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 ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { CstMatchMode } from '../../models/miscelanious';
|
||||
import { applyFilterCategory, CATEGORY_CST_TYPE, IConstituenta, IRSForm, matchConstituenta } from '../../models/rsform';
|
||||
import { colorfgCstStatus } from '../../utils/color';
|
||||
import { applyFilterCategory, CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
|
||||
export interface ITemplateState {
|
||||
templateID?: number
|
||||
prototype?: IConstituenta
|
||||
filterCategory?: IConstituenta
|
||||
filterText: string
|
||||
}
|
||||
|
||||
interface TemplateTabProps {
|
||||
|
@ -25,11 +18,7 @@ interface TemplateTabProps {
|
|||
partialUpdate: Dispatch<Partial<ITemplateState>>
|
||||
}
|
||||
|
||||
const constituentaHelper = createColumnHelper<IConstituenta>();
|
||||
|
||||
function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
||||
const { colors } = useConceptTheme();
|
||||
|
||||
const { templates, retrieveTemplate } = useLibrary();
|
||||
const [ selectedSchema, setSelectedSchema ] = useState<IRSForm | undefined>(undefined);
|
||||
|
||||
|
@ -91,58 +80,8 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
if (state.filterCategory) {
|
||||
data = applyFilterCategory(state.filterCategory, selectedSchema);
|
||||
}
|
||||
if (state.filterText) {
|
||||
data = data.filter(cst => matchConstituenta(state.filterText, cst, CstMatchMode.TERM));
|
||||
}
|
||||
setFilteredData(data);
|
||||
}, [state.filterText, 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]);
|
||||
}, [state.filterCategory, selectedSchema]);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-3'>
|
||||
|
@ -166,32 +105,13 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
onChange={data => partialUpdate({templateID: (data ? data.value : undefined)})}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ConceptSearch
|
||||
value={state.filterText}
|
||||
onChange={newValue => partialUpdate({ filterText: newValue} )}
|
||||
dense
|
||||
/>
|
||||
<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>
|
||||
<ConstituentaPicker
|
||||
value={state.prototype}
|
||||
data={filteredData}
|
||||
onSelectValue={cst => partialUpdate( { prototype: cst } )}
|
||||
prefixID={prefixes.cst_template_ist}
|
||||
rows={9}
|
||||
/>
|
||||
<TextArea id='term'
|
||||
rows={1}
|
||||
disabled
|
||||
|
|
|
@ -27,12 +27,10 @@ export enum TabID {
|
|||
|
||||
function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituentaTemplateProps) {
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({
|
||||
filterText: ''
|
||||
});
|
||||
const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({});
|
||||
const [ argumentsData, updateArgumentsData ] = usePartialUpdate<IArgumentsState>({
|
||||
filterText: '',
|
||||
definition: ''
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
const [cstData, updateCstData] = usePartialUpdate<ICstCreateData>({
|
||||
cst_type: CstType.TERM,
|
||||
|
@ -80,7 +78,13 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituen
|
|||
});
|
||||
updateArgumentsData({
|
||||
definition: templateData.prototype.definition_formal,
|
||||
arguments: []
|
||||
arguments: templateData.prototype.parse.args.map(
|
||||
arg => ({
|
||||
alias: arg.alias,
|
||||
typification: arg.typification,
|
||||
value: ''
|
||||
})
|
||||
)
|
||||
});
|
||||
}
|
||||
}, [templateData.prototype, updateCstData, updateArgumentsData]);
|
||||
|
@ -136,6 +140,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituen
|
|||
|
||||
<TabPanel>
|
||||
<ArgumentsTab
|
||||
schema={schema}
|
||||
state={argumentsData}
|
||||
partialUpdate={updateArgumentsData}
|
||||
/>
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import ConceptSearch from '../../components/Common/ConceptSearch';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import Label from '../../components/Common/Label';
|
||||
import Modal from '../../components/Common/Modal';
|
||||
import SelectMulti from '../../components/Common/SelectMulti';
|
||||
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 { HelpIcon } from '../../components/Icons';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||
import {
|
||||
getCompatibleGrams, Grammeme,
|
||||
parseEntityReference, parseGrammemes,
|
||||
|
@ -19,7 +15,6 @@ import {
|
|||
} from '../../models/language';
|
||||
import { CstMatchMode } from '../../models/miscelanious';
|
||||
import { IConstituenta, matchConstituenta } from '../../models/rsform';
|
||||
import { colorfgCstStatus } from '../../utils/color';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
|
||||
import ReferenceTypeButton from './ReferenceTypeButton';
|
||||
|
@ -40,20 +35,16 @@ interface DlgEditReferenceProps {
|
|||
onSave: (newRef: string) => void
|
||||
}
|
||||
|
||||
const constituentaHelper = createColumnHelper<IConstituenta>();
|
||||
|
||||
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
||||
const { colors } = useConceptTheme();
|
||||
|
||||
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||
|
||||
const [nominal, setNominal] = useState('');
|
||||
const [offset, setOffset] = useState(1);
|
||||
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [alias, setAlias] = useState('');
|
||||
const [term, setTerm] = useState('');
|
||||
const [filter, setFilter] = useState('');
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||
|
||||
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
|
||||
|
||||
|
@ -105,24 +96,9 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
}
|
||||
} else if (initial.text) {
|
||||
setNominal(initial.text ?? '');
|
||||
setFilter(initial.text);
|
||||
}
|
||||
}, [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
|
||||
useEffect(
|
||||
() => {
|
||||
|
@ -145,6 +121,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
|
||||
function handleSelectConstituenta(cst: IConstituenta) {
|
||||
setAlias(cst.alias);
|
||||
setSelectedCst(cst);
|
||||
}
|
||||
|
||||
const handleSelectGrams = useCallback(
|
||||
|
@ -180,49 +157,6 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
</div>);
|
||||
}, [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 (
|
||||
<Modal
|
||||
title='Редактирование ссылки'
|
||||
|
@ -286,32 +220,17 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
</div>}
|
||||
{type === ReferenceType.ENTITY &&
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div>
|
||||
<ConceptSearch
|
||||
value={filter}
|
||||
onChange={newValue => setFilter(newValue)}
|
||||
dense
|
||||
/>
|
||||
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto'>
|
||||
<DataTable
|
||||
data={filteredData}
|
||||
columns={columnsConstituenta}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
<ConstituentaPicker
|
||||
value={selectedCst}
|
||||
data={items}
|
||||
onSelectValue={handleSelectConstituenta}
|
||||
prefixID={prefixes.cst_modal_list}
|
||||
describeFunc={cst => cst.term_resolved}
|
||||
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
|
||||
prefilterFunc={cst => cst.term_resolved !== ''}
|
||||
rows={8}
|
||||
/>
|
||||
|
||||
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'>
|
||||
<TextInput
|
||||
label='Отсылаемая конституента'
|
||||
|
|
|
@ -223,7 +223,7 @@ export function loadRSFormData(schema: IRSFormData): IRSForm {
|
|||
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);
|
||||
if ((mode === CstMatchMode.ALL || mode === CstMatchMode.NAME) &&
|
||||
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 {
|
||||
return {
|
||||
id: id,
|
||||
|
|
|
@ -48,8 +48,7 @@ export interface IArgumentInfo {
|
|||
typification: string
|
||||
}
|
||||
|
||||
export interface IArgumentValue {
|
||||
alias: string
|
||||
export interface IArgumentValue extends IArgumentInfo {
|
||||
value?: string
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
|
||||
import ConstituentaBadge from '../../components/Shared/ConstituentaBadge';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../models/rsform'
|
||||
import { colorfgCstStatus } from '../../utils/color';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { describeExpressionStatus, labelCstTypification } from '../../utils/labels';
|
||||
import { labelCstTypification } from '../../utils/labels';
|
||||
import RSItemsMenu from './elements/RSItemsMenu';
|
||||
|
||||
// Window width cutoff for columns
|
||||
|
@ -239,30 +238,13 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edit
|
|||
size: 65,
|
||||
minSize: 65,
|
||||
maxSize: 65,
|
||||
cell: props => {
|
||||
const cst = props.row.original;
|
||||
return (<>
|
||||
<div
|
||||
id={`${prefixes.cst_list}${cst.alias}`}
|
||||
className='w-full 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: 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>
|
||||
</>);
|
||||
}
|
||||
cell: props =>
|
||||
<ConstituentaBadge
|
||||
theme={colors}
|
||||
value={props.row.original}
|
||||
prefixID={prefixes.cst_list}
|
||||
shortTooltip
|
||||
/>
|
||||
}),
|
||||
columnHelper.accessor(cst => labelCstTypification(cst), {
|
||||
id: 'type',
|
||||
|
|
|
@ -5,6 +5,7 @@ import DropdownButton from '../../../components/Common/DropdownButton';
|
|||
import SelectorButton from '../../../components/Common/SelectorButton';
|
||||
import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable';
|
||||
import { CogIcon, FilterIcon, MagnifyingGlassIcon } from '../../../components/Icons';
|
||||
import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge';
|
||||
import { useRSForm } from '../../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||
import useDropdown from '../../../hooks/useDropdown';
|
||||
|
@ -13,16 +14,14 @@ import useWindowSize from '../../../hooks/useWindowSize';
|
|||
import { DependencyMode as CstSource } from '../../../models/miscelanious';
|
||||
import { CstMatchMode } 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 { colorfgCstStatus } from '../../../utils/color';
|
||||
import { prefixes } from '../../../utils/constants';
|
||||
import {
|
||||
describeConstituenta, describeCstMathchMode,
|
||||
describeCstSource, labelCstMathchMode,
|
||||
labelCstSource
|
||||
} from '../../../utils/labels';
|
||||
import ConstituentaTooltip from '../../../components/Help/ConstituentaTooltip';
|
||||
|
||||
// Height that should be left to accomodate navigation panel + bottom margin
|
||||
const LOCAL_NAVIGATION_H = '2.1rem';
|
||||
|
@ -37,10 +36,6 @@ interface ViewSideConstituentsProps {
|
|||
onOpenEdit: (cstID: number) => void
|
||||
}
|
||||
|
||||
function isMockCst(cst: IConstituenta) {
|
||||
return cst.id <= 0;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<IConstituenta>();
|
||||
|
||||
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
||||
|
@ -102,7 +97,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
filtered = applyGraphFilter(schema, activeID, filterSource);
|
||||
}
|
||||
if (filterText) {
|
||||
filtered = filtered.filter((cst) => matchConstituenta(filterText, cst, filterMatch));
|
||||
filtered = filtered.filter(cst => matchConstituenta(cst, filterText, filterMatch));
|
||||
}
|
||||
setFilteredData(filtered);
|
||||
}, [filterText, setFilteredData, filterSource, expression, schema, filterMatch, activeID]);
|
||||
|
@ -141,25 +136,12 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
|||
size: 65,
|
||||
minSize: 65,
|
||||
footer: undefined,
|
||||
cell: props => {
|
||||
const cst = props.row.original;
|
||||
return (<>
|
||||
<div
|
||||
id={`${prefixes.cst_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,
|
||||
backgroundColor: isMockCst(cst) ? colors.bgWarning : colors.bgInput
|
||||
}}
|
||||
>
|
||||
{cst.alias}
|
||||
</div>
|
||||
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
|
||||
</>);
|
||||
}
|
||||
cell: props =>
|
||||
<ConstituentaBadge
|
||||
theme={colors}
|
||||
value={props.row.original}
|
||||
prefixID={prefixes.cst_list}
|
||||
/>
|
||||
}),
|
||||
columnHelper.accessor(cst => describeConstituenta(cst), {
|
||||
id: 'description',
|
||||
|
|
|
@ -34,6 +34,7 @@ export const globalIDs = {
|
|||
|
||||
export const prefixes = {
|
||||
cst_list: 'cst-list-',
|
||||
cst_modal_list: 'cst-modal-list-',
|
||||
cst_template_ist: 'cst-template-list-',
|
||||
cst_wordform_list: 'cst-wordform-list-',
|
||||
cst_status_list: 'cst-status-list-',
|
||||
|
|
Loading…
Reference in New Issue
Block a user