mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implement TemplateExpression editing
This commit is contained in:
parent
4cd8b31b59
commit
cefd0d3c40
30
rsconcept/frontend/src/components/Common/ConceptSearch.tsx
Normal file
30
rsconcept/frontend/src/components/Common/ConceptSearch.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { MagnifyingGlassIcon } from '../Icons';
|
||||
import TextInput from './TextInput';
|
||||
|
||||
interface ConceptSearchProps {
|
||||
value: string
|
||||
onChange?: (newValue: string) => void
|
||||
dense?: boolean
|
||||
}
|
||||
|
||||
function ConceptSearch({ value, onChange, dense }: ConceptSearchProps) {
|
||||
const borderClass = dense ? 'border-t border-x': '';
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className='absolute inset-y-0 flex items-center pl-3 pointer-events-none text-controls'>
|
||||
<MagnifyingGlassIcon />
|
||||
</div>
|
||||
<TextInput
|
||||
dimensions={`w-full pl-10 ${borderClass} rounded`}
|
||||
placeholder='Поиск'
|
||||
noOutline
|
||||
noBorder={dense}
|
||||
value={value}
|
||||
onChange={event => onChange && onChange(event.target.value)}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default ConceptSearch;
|
||||
|
||||
|
|
@ -2,14 +2,16 @@ import type { TabProps } from 'react-tabs';
|
|||
import { Tab } from 'react-tabs';
|
||||
|
||||
interface ConceptTabProps
|
||||
extends Omit<TabProps, 'className'> {
|
||||
extends Omit<TabProps, 'className' | 'title'> {
|
||||
className?: string
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
function ConceptTab({ children, className, ...otherProps }: ConceptTabProps) {
|
||||
function ConceptTab({ children, tooltip, className, ...otherProps }: ConceptTabProps) {
|
||||
return (
|
||||
<Tab
|
||||
className={`px-2 py-1 h-full text-sm hover:cursor-pointer clr-tab whitespace-nowrap ${className}`}
|
||||
title={tooltip}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -40,18 +40,18 @@ function Modal({
|
|||
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
|
||||
<div
|
||||
ref={ref}
|
||||
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] h-fit z-modal clr-app border shadow-md'
|
||||
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-3 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] h-fit z-modal clr-app border shadow-md'
|
||||
>
|
||||
{ title && <h1 className='mb-2 text-xl select-none'>{title}</h1> }
|
||||
{ title && <h1 className='pb-3 text-xl select-none'>{title}</h1> }
|
||||
<div className='max-h-[calc(100vh-8rem)]'>
|
||||
{children}
|
||||
</div>
|
||||
<div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-2 z-modal-controls'>
|
||||
<div className='flex justify-center w-full gap-4 pt-3 mt-2 border-t-2 z-modal-controls'>
|
||||
{!readonly &&
|
||||
<Button
|
||||
text={submitText}
|
||||
tooltip={!canSubmit ? submitInvalidTooltip: ''}
|
||||
dimensions='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
|
||||
dimensions='min-w-[8rem] min-h-[2.6rem] w-fit h-fit'
|
||||
colors='clr-btn-primary'
|
||||
disabled={!canSubmit}
|
||||
onClick={handleSubmit}
|
||||
|
@ -59,7 +59,7 @@ function Modal({
|
|||
/>}
|
||||
<Button
|
||||
text={readonly ? 'Закрыть' : 'Отмена'}
|
||||
dimensions='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
|
||||
dimensions='min-w-[8rem] min-h-[2.6rem] w-fit h-fit'
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -12,15 +12,14 @@ function SubmitButton({
|
|||
dimensions = 'w-fit h-fit'
|
||||
}: SubmitButtonProps) {
|
||||
return (
|
||||
<button type='submit'
|
||||
title={tooltip}
|
||||
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${dimensions} ${loading ? ' cursor-progress' : ''}`}
|
||||
disabled={disabled ?? loading}
|
||||
>
|
||||
{icon && <span>{icon}</span>}
|
||||
{text && <span>{text}</span>}
|
||||
</button>
|
||||
)
|
||||
<button type='submit'
|
||||
title={tooltip}
|
||||
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${dimensions} ${loading ? ' cursor-progress' : ''}`}
|
||||
disabled={disabled ?? loading}
|
||||
>
|
||||
{icon && <span>{icon}</span>}
|
||||
{text && <span>{text}</span>}
|
||||
</button>);
|
||||
}
|
||||
|
||||
export default SubmitButton;
|
||||
|
|
|
@ -16,16 +16,15 @@ function SwitchButton<ValueType>({
|
|||
isSelected, onSelect, ...props
|
||||
}: SwitchButtonProps<ValueType>) {
|
||||
return (
|
||||
<button type='button' tabIndex={-1}
|
||||
title={tooltip}
|
||||
onClick={() => onSelect(value)}
|
||||
className={`px-2 py-1 border font-semibold small-caps rounded-none cursor-pointer clr-btn-clear clr-hover ${dimensions} ${isSelected ? 'clr-selected': ''}`}
|
||||
{...props}
|
||||
>
|
||||
{icon && icon}
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
<button type='button' tabIndex={-1}
|
||||
title={tooltip}
|
||||
onClick={() => onSelect(value)}
|
||||
className={`px-2 py-1 border font-semibold small-caps rounded-none cursor-pointer clr-btn-clear clr-hover ${dimensions} ${isSelected ? 'clr-selected': ''}`}
|
||||
{...props}
|
||||
>
|
||||
{icon && icon}
|
||||
{label}
|
||||
</button>);
|
||||
}
|
||||
|
||||
export default SwitchButton;
|
||||
|
|
|
@ -27,7 +27,8 @@ extends Pick<TableOptions<TData>,
|
|||
> {
|
||||
dense?: boolean
|
||||
headPosition?: string
|
||||
noFooter?: boolean // Disables footer rendering
|
||||
noHeader?: boolean
|
||||
noFooter?: boolean
|
||||
conditionalRowStyles?: IConditionalStyle<TData>[]
|
||||
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element, MouseEvent>) => void
|
||||
|
@ -55,7 +56,7 @@ extends Pick<TableOptions<TData>,
|
|||
* No sticky header if omitted
|
||||
*/
|
||||
export default function DataTable<TData extends RowData>({
|
||||
dense, headPosition, conditionalRowStyles, noFooter,
|
||||
dense, headPosition, conditionalRowStyles, noFooter, noHeader,
|
||||
onRowClicked, onRowDoubleClicked, noDataComponent,
|
||||
|
||||
enableRowSelection,
|
||||
|
@ -112,6 +113,7 @@ export default function DataTable<TData extends RowData>({
|
|||
<div className='w-full'>
|
||||
<div className='flex flex-col items-stretch'>
|
||||
<table>
|
||||
{ !noHeader &&
|
||||
<thead
|
||||
className={`clr-app shadow-border`}
|
||||
style={{
|
||||
|
@ -147,7 +149,7 @@ export default function DataTable<TData extends RowData>({
|
|||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
</thead>}
|
||||
|
||||
<tbody>
|
||||
{tableImpl.getRowModel().rows.map(
|
||||
|
|
|
@ -45,15 +45,16 @@ const editorSetup: BasicSetupOptions = {
|
|||
|
||||
interface RSInputProps
|
||||
extends Pick<ReactCodeMirrorProps,
|
||||
'id'| 'editable' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
'id' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
}
|
||||
|
||||
function RSInput({
|
||||
id, label, innerref, onChange, editable,
|
||||
id, label, innerref, onChange, disabled,
|
||||
...props
|
||||
}: RSInputProps) {
|
||||
const { darkMode, colors } = useConceptTheme();
|
||||
|
@ -65,13 +66,13 @@ function RSInput({
|
|||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref]);
|
||||
|
||||
const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]);
|
||||
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
|
||||
const customTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: editable ? colors.bgInput : colors.bgDefault,
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover
|
||||
},
|
||||
|
@ -84,7 +85,7 @@ function RSInput({
|
|||
{ tag: tags.controlKeyword, fontWeight: '500'}, // R | I | D
|
||||
{ tag: tags.unit, fontSize: '0.75rem' }, // indicies
|
||||
]
|
||||
}), [editable, colors, darkMode]);
|
||||
}), [disabled, colors, darkMode]);
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
|
@ -139,7 +140,7 @@ function RSInput({
|
|||
extensions={editorExtensions}
|
||||
indentWithTab={false}
|
||||
onChange={onChange}
|
||||
editable={editable}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
@ -50,12 +50,13 @@ const editorSetup: BasicSetupOptions = {
|
|||
|
||||
interface RefsInputInputProps
|
||||
extends Pick<ReactCodeMirrorProps,
|
||||
'id'| 'editable' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
'id'| 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
|
||||
> {
|
||||
label?: string
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
items?: IConstituenta[]
|
||||
disabled?: boolean
|
||||
|
||||
initialValue?: string
|
||||
value?: string
|
||||
|
@ -63,7 +64,7 @@ extends Pick<ReactCodeMirrorProps,
|
|||
}
|
||||
|
||||
function RefsInput({
|
||||
id, label, innerref, editable, items,
|
||||
id, label, innerref, disabled, items,
|
||||
initialValue, value, resolved,
|
||||
onFocus, onBlur, onChange,
|
||||
...props
|
||||
|
@ -89,13 +90,13 @@ function RefsInput({
|
|||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref]);
|
||||
|
||||
const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]);
|
||||
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
|
||||
const customTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
theme: darkMode ? 'dark' : 'light',
|
||||
settings: {
|
||||
fontFamily: 'inherit',
|
||||
background: editable ? colors.bgInput : colors.bgDefault,
|
||||
background: !disabled ? colors.bgInput : colors.bgDefault,
|
||||
foreground: colors.fgDefault,
|
||||
selection: colors.bgHover
|
||||
},
|
||||
|
@ -104,7 +105,7 @@ function RefsInput({
|
|||
{ tag: tags.literal, color: colors.fgTeal, cursor: 'default' }, // SyntacticReference
|
||||
{ tag: tags.comment, color: colors.fgRed }, // Error
|
||||
]
|
||||
}), [editable, colors, darkMode]);
|
||||
}), [disabled, colors, darkMode]);
|
||||
|
||||
const editorExtensions = useMemo(
|
||||
() => [
|
||||
|
@ -216,7 +217,7 @@ function RefsInput({
|
|||
|
||||
indentWithTab={false}
|
||||
onChange={handleChange}
|
||||
editable={editable}
|
||||
editable={!disabled}
|
||||
onKeyDown={handleInput}
|
||||
onFocus={handleFocusIn}
|
||||
onBlur={handleFocusOut}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { Dispatch } from 'react';
|
||||
|
||||
import ConceptSearch from '../../components/Common/ConceptSearch';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { IArgumentValue } from '../../models/rslang';
|
||||
|
||||
interface ArgumentsTabProps {
|
||||
state: IArgumentsState
|
||||
partialUpdate: Dispatch<Partial<IArgumentsState>>
|
||||
}
|
||||
|
||||
export interface IArgumentsState {
|
||||
arguments?: IArgumentValue[]
|
||||
filterText: string
|
||||
definition: string
|
||||
}
|
||||
|
||||
function ArgumentsTab({state, partialUpdate}: ArgumentsTabProps) {
|
||||
return (
|
||||
<div className='flex flex-col gap-3'>
|
||||
<ConceptSearch
|
||||
value={state.filterText}
|
||||
onChange={newValue => partialUpdate({ filterText: newValue} )}
|
||||
dense
|
||||
/>
|
||||
<RSInput id='result'
|
||||
placeholder='Итоговое определение'
|
||||
height='4.8rem'
|
||||
value={state.definition}
|
||||
disabled
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default ArgumentsTab;
|
|
@ -15,10 +15,10 @@ interface ConstituentaTabProps {
|
|||
|
||||
function ConstituentaTab({state, partialUpdate}: ConstituentaTabProps) {
|
||||
return (
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<div className='flex justify-center w-full gap-6'>
|
||||
<SelectSingle
|
||||
className='my-2 min-w-[15rem] self-center'
|
||||
className='min-w-[15rem] self-center'
|
||||
options={SelectorCstType}
|
||||
placeholder='Выберите тип'
|
||||
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
|
||||
|
@ -40,7 +40,6 @@ function ConstituentaTab({state, partialUpdate}: ConstituentaTabProps) {
|
|||
/>
|
||||
<RSInput id='expression' label='Формальное определение'
|
||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||
editable
|
||||
height='4.8rem'
|
||||
value={state.definition_formal}
|
||||
onChange={value => partialUpdate({definition_formal: value})}
|
|
@ -1,11 +1,10 @@
|
|||
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 TextInput from '../../components/Common/TextInput';
|
||||
import DataTable, { createColumnHelper,IConditionalStyle } from '../../components/DataTable';
|
||||
import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip';
|
||||
import { MagnifyingGlassIcon } from '../../components/Icons';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
|
@ -106,7 +105,6 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
() => [
|
||||
constituentaHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Имя',
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
cell: props => {
|
||||
|
@ -130,7 +128,6 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
}),
|
||||
constituentaHelper.accessor('term_resolved', {
|
||||
id: 'term',
|
||||
header: 'Термин',
|
||||
size: 600,
|
||||
minSize: 350,
|
||||
maxSize: 600
|
||||
|
@ -148,8 +145,8 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
], [state.prototype, colors]);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-2 mt-2'>
|
||||
<div className='flex justify-between gap-4'>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<div className='flex justify-between gap-3'>
|
||||
<SelectSingle
|
||||
className='w-full'
|
||||
options={categorySelector}
|
||||
|
@ -170,27 +167,19 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='relative'>
|
||||
<div className='absolute inset-y-0 flex items-center pl-3 pointer-events-none text-controls'>
|
||||
<MagnifyingGlassIcon />
|
||||
</div>
|
||||
<TextInput
|
||||
dimensions='w-full pl-10'
|
||||
placeholder='Поиск'
|
||||
noOutline
|
||||
value={state.filterText}
|
||||
onChange={event => partialUpdate({ filterText: event.target.value} )}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto select-none'>
|
||||
<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}
|
||||
|
||||
headPosition='0'
|
||||
dense
|
||||
noHeader
|
||||
|
||||
noDataComponent={
|
||||
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
|
||||
|
@ -204,7 +193,7 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
</div>
|
||||
</div>
|
||||
<TextArea id='term'
|
||||
rows={2}
|
||||
rows={1}
|
||||
disabled
|
||||
placeholder='Шаблон конституенты не выбран'
|
||||
value={prototypeInfo}
|
||||
|
@ -213,7 +202,7 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
<RSInput id='expression'
|
||||
height='4.8rem'
|
||||
placeholder='Выберите шаблон из списка'
|
||||
editable={false}
|
||||
disabled
|
||||
value={state.prototype?.definition_formal}
|
||||
/>
|
||||
</div>);
|
156
rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/index.tsx
Normal file
156
rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/index.tsx
Normal file
|
@ -0,0 +1,156 @@
|
|||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import ConceptTab from '../../components/Common/ConceptTab';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import Modal, { ModalProps } from '../../components/Common/Modal';
|
||||
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import usePartialUpdate from '../../hooks/usePartialUpdate';
|
||||
import { CstType, ICstCreateData, IRSForm } from '../../models/rsform';
|
||||
import { createAliasFor, validateCstAlias } from '../../utils/misc';
|
||||
import ArgumentsTab, { IArgumentsState } from './ArgumentsTab';
|
||||
import ConstituentaTab from './ConstituentaTab';
|
||||
import TemplateTab, { ITemplateState } from './TemplateTab';
|
||||
|
||||
interface DlgConstituentaTemplateProps
|
||||
extends Pick<ModalProps, 'hideWindow'> {
|
||||
schema: IRSForm
|
||||
onCreate: (data: ICstCreateData) => void
|
||||
}
|
||||
|
||||
export enum TabID {
|
||||
TEMPLATE = 0,
|
||||
ARGUMENTS = 1,
|
||||
CONSTITUENTA = 2
|
||||
}
|
||||
|
||||
function DlgConstituentaTemplate({ hideWindow, schema, onCreate }: DlgConstituentaTemplateProps) {
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({
|
||||
filterText: ''
|
||||
});
|
||||
const [ argumentsData, updateArgumentsData ] = usePartialUpdate<IArgumentsState>({
|
||||
filterText: '',
|
||||
definition: ''
|
||||
});
|
||||
const [cstData, updateCstData] = usePartialUpdate<ICstCreateData>({
|
||||
cst_type: CstType.TERM,
|
||||
insert_after: null,
|
||||
alias: '',
|
||||
convention: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
term_raw: '',
|
||||
term_forms: []
|
||||
});
|
||||
|
||||
const [ activeTab, setActiveTab ] = useState(TabID.TEMPLATE);
|
||||
|
||||
const handleSubmit = () => onCreate(cstData);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
updateCstData({ alias: createAliasFor(cstData.cst_type, schema) });
|
||||
}, [cstData.cst_type, updateCstData, schema]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
setValidated(validateCstAlias(cstData.alias, cstData.cst_type, schema));
|
||||
}, [cstData.alias, cstData.cst_type, schema]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (!templateData.prototype) {
|
||||
updateCstData({
|
||||
definition_raw: '',
|
||||
definition_formal: '',
|
||||
term_raw: ''
|
||||
});
|
||||
updateArgumentsData({
|
||||
definition: '',
|
||||
arguments: []
|
||||
});
|
||||
} else {
|
||||
updateCstData({
|
||||
cst_type: templateData.prototype.cst_type,
|
||||
definition_raw: templateData.prototype.definition_raw,
|
||||
definition_formal: templateData.prototype.definition_formal,
|
||||
term_raw: templateData.prototype.term_raw
|
||||
});
|
||||
updateArgumentsData({
|
||||
definition: templateData.prototype.definition_formal,
|
||||
arguments: []
|
||||
});
|
||||
}
|
||||
}, [templateData.prototype, updateCstData, updateArgumentsData]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Создание конституенты из шаблона'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
submitText='Создать'
|
||||
>
|
||||
<div className='max-w-[40rem] min-w-[40rem] min-h-[35rem] px-2 mb-1'>
|
||||
<Tabs
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
defaultFocus
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col items-center'
|
||||
>
|
||||
<div className='flex gap-1 pl-6 mb-3'>
|
||||
<TabList className='flex items-start font-semibold text-center border select-none clr-controls small-caps'>
|
||||
<ConceptTab tooltip='Выбор шаблона выражения' className='border-r w-[8rem]'>
|
||||
Шаблон
|
||||
</ConceptTab>
|
||||
<ConceptTab tooltip='Подстановка аргументов шаблона' className='border-r w-[8rem]'>
|
||||
Аргументы
|
||||
</ConceptTab>
|
||||
<ConceptTab tooltip='Редактирование атрибутов конституенты' className='w-[8rem]'>
|
||||
Конституента
|
||||
</ConceptTab>
|
||||
</TabList>
|
||||
|
||||
<div id='templates-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
anchorSelect='#templates-help'
|
||||
className='max-w-[30rem] z-modal-tooltip'
|
||||
offset={4}
|
||||
>
|
||||
<HelpRSTemplates />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<TabPanel>
|
||||
<TemplateTab
|
||||
state={templateData}
|
||||
partialUpdate={updateTemplateData}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<ArgumentsTab
|
||||
state={argumentsData}
|
||||
partialUpdate={updateArgumentsData}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<ConstituentaTab
|
||||
state={cstData}
|
||||
partialUpdate={updateCstData}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgConstituentaTemplate;
|
|
@ -79,7 +79,6 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
|
|||
/>
|
||||
<RSInput id='expression' label='Формальное определение'
|
||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||
editable
|
||||
height='4.8rem'
|
||||
value={cstData.definition_formal}
|
||||
onChange={value => updateCstData({definition_formal: value})}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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';
|
||||
|
@ -9,7 +10,7 @@ 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, MagnifyingGlassIcon } from '../../components/Icons';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import {
|
||||
getCompatibleGrams, Grammeme,
|
||||
|
@ -183,7 +184,6 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
() => [
|
||||
constituentaHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Имя',
|
||||
size: 65,
|
||||
minSize: 65,
|
||||
cell: props => {
|
||||
|
@ -207,7 +207,6 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
}),
|
||||
constituentaHelper.accessor('term_resolved', {
|
||||
id: 'term',
|
||||
header: 'Термин',
|
||||
size: 600,
|
||||
minSize: 350,
|
||||
maxSize: 600
|
||||
|
@ -287,35 +286,31 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
</div>}
|
||||
{type === ReferenceType.ENTITY &&
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='relative'>
|
||||
<div className='absolute inset-y-0 flex items-center pl-3 pointer-events-none text-controls'>
|
||||
<MagnifyingGlassIcon />
|
||||
</div>
|
||||
<TextInput
|
||||
dimensions='w-full pl-10'
|
||||
placeholder='Поиск'
|
||||
<div>
|
||||
<ConceptSearch
|
||||
value={filter}
|
||||
onChange={event => setFilter(event.target.value)}
|
||||
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}
|
||||
|
||||
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 className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto'>
|
||||
<DataTable
|
||||
data={filteredData}
|
||||
columns={columnsConstituenta}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
|
||||
headPosition='0'
|
||||
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 className='flex gap-4 flex-start'>
|
||||
<TextInput
|
||||
|
|
|
@ -217,7 +217,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
>
|
||||
<div className='relative w-full'>
|
||||
<div className='absolute top-0 right-0'>
|
||||
<div id='terminology-help' className='px-1 py-1'>
|
||||
<div id='terminology-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import Modal, { ModalProps } from '../../components/Common/Modal';
|
||||
import SwitchButton from '../../components/Common/SwitchButton';
|
||||
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
|
||||
import { HelpIcon } from '../../components/Icons';
|
||||
import usePartialUpdate from '../../hooks/usePartialUpdate';
|
||||
import { CstType, ICstCreateData, IRSForm } from '../../models/rsform';
|
||||
import { createAliasFor, validateCstAlias } from '../../utils/misc';
|
||||
import ConstituentaTab from './ConstituentaTab';
|
||||
import TemplateTab, { ITemplateState } from './TemplateTab';
|
||||
|
||||
interface DlgTemplatesProps
|
||||
extends Pick<ModalProps, 'hideWindow'> {
|
||||
schema: IRSForm
|
||||
onCreate: (data: ICstCreateData) => void
|
||||
}
|
||||
|
||||
function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [cstData, updateCstData] = usePartialUpdate<ICstCreateData>({
|
||||
cst_type: CstType.TERM,
|
||||
insert_after: null,
|
||||
alias: '',
|
||||
convention: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
term_raw: '',
|
||||
term_forms: []
|
||||
});
|
||||
const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({
|
||||
filterText: ''
|
||||
});
|
||||
|
||||
const [ showAttributes, setShowAttributes ] = useState(false);
|
||||
|
||||
const handleSubmit = () => onCreate(cstData);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
updateCstData({ alias: createAliasFor(cstData.cst_type, schema) });
|
||||
}, [cstData.cst_type, updateCstData, schema]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
setValidated(validateCstAlias(cstData.alias, cstData.cst_type, schema));
|
||||
}, [cstData.alias, cstData.cst_type, schema]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (!templateData.prototype) {
|
||||
updateCstData({
|
||||
definition_raw: '',
|
||||
definition_formal: '',
|
||||
term_raw: ''
|
||||
});
|
||||
} else {
|
||||
updateCstData({
|
||||
cst_type: templateData.prototype.cst_type,
|
||||
definition_raw: templateData.prototype.definition_raw,
|
||||
definition_formal: templateData.prototype.definition_formal,
|
||||
term_raw: templateData.prototype.term_raw
|
||||
});
|
||||
}
|
||||
}, [templateData.prototype, updateCstData]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Создание конституенты из шаблона'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
submitText='Создать'
|
||||
>
|
||||
<div className='h-fit max-w-[40rem] min-w-[40rem] min-h-[35rem] px-2 mb-2 flex flex-col justify-stretch gap-3'>
|
||||
<div className='flex items-center self-center flex-start'>
|
||||
<SwitchButton
|
||||
label='Шаблон'
|
||||
tooltip='Выбор шаблона выражения'
|
||||
dimensions='min-w-[10rem] h-fit'
|
||||
value={false}
|
||||
isSelected={!showAttributes}
|
||||
onSelect={(value) => setShowAttributes(value)}
|
||||
/>
|
||||
<SwitchButton
|
||||
label='Конституента'
|
||||
tooltip='Редактирование атрибутов конституенты'
|
||||
dimensions='min-w-[10rem] h-fit'
|
||||
value={true}
|
||||
isSelected={showAttributes}
|
||||
onSelect={(value) => setShowAttributes(value)}
|
||||
/>
|
||||
<div id='templates-help' className='px-1 py-1'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip
|
||||
anchorSelect='#templates-help'
|
||||
className='max-w-[30rem] z-modal-tooltip'
|
||||
offset={4}
|
||||
>
|
||||
<HelpRSTemplates />
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
|
||||
{ !showAttributes &&
|
||||
<TemplateTab
|
||||
state={templateData}
|
||||
partialUpdate={updateTemplateData}
|
||||
/>}
|
||||
|
||||
{ showAttributes &&
|
||||
<ConstituentaTab
|
||||
state={cstData}
|
||||
partialUpdate={updateCstData}
|
||||
/>}
|
||||
</div>
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgTemplates;
|
|
@ -2,14 +2,14 @@ import { useCallback, useState } from 'react'
|
|||
|
||||
import { type ErrorInfo } from '../components/BackendError';
|
||||
import { CstType, IConstituenta, type IRSForm } from '../models/rsform';
|
||||
import { IExpressionParse, IFunctionArg } from '../models/rslang';
|
||||
import { IExpressionParse, IArgumentInfo } from '../models/rslang';
|
||||
import { RSErrorType } from '../models/rslang';
|
||||
import { DataCallback, postCheckExpression } from '../utils/backendAPI';
|
||||
import { getCstExpressionPrefix } from '../utils/misc';
|
||||
|
||||
const LOGIC_TYPIIFCATION = 'LOGIC';
|
||||
|
||||
function checkTypeConsistency(type: CstType, typification: string, args: IFunctionArg[]): boolean {
|
||||
function checkTypeConsistency(type: CstType, typification: string, args: IArgumentInfo[]): boolean {
|
||||
switch (type) {
|
||||
case CstType.BASE:
|
||||
case CstType.CONSTANT:
|
||||
|
|
|
@ -3,7 +3,7 @@ import { TextMatcher } from '../utils/utils'
|
|||
import { ILibraryUpdateData } from './library'
|
||||
import { ILibraryItem } from './library'
|
||||
import { CstMatchMode } from './miscelanious'
|
||||
import { IFunctionArg, ParsingStatus, ValueClass } from './rslang'
|
||||
import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang'
|
||||
|
||||
export enum CstType {
|
||||
BASE = 'basic',
|
||||
|
@ -67,7 +67,7 @@ extends IConstituentaMeta {
|
|||
valueClass: ValueClass
|
||||
typification: string
|
||||
syntaxTree: string
|
||||
args: IFunctionArg[]
|
||||
args: IArgumentInfo[]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,11 +43,16 @@ export interface ISyntaxTreeNode {
|
|||
}
|
||||
export type SyntaxTree = ISyntaxTreeNode[]
|
||||
|
||||
export interface IFunctionArg {
|
||||
export interface IArgumentInfo {
|
||||
alias: string
|
||||
typification: string
|
||||
}
|
||||
|
||||
export interface IArgumentValue {
|
||||
alias: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
export interface IExpressionParse {
|
||||
parseResult: boolean
|
||||
syntax: Syntax
|
||||
|
@ -56,7 +61,7 @@ export interface IExpressionParse {
|
|||
errors: IRSErrorDescription[]
|
||||
astText: string
|
||||
ast: SyntaxTree
|
||||
args: IFunctionArg[]
|
||||
args: IArgumentInfo[]
|
||||
}
|
||||
|
||||
//! RS language token types enumeration
|
||||
|
|
|
@ -235,7 +235,7 @@ function EditorConstituenta({
|
|||
value={term}
|
||||
initialValue={activeCst?.term_raw ?? ''}
|
||||
resolved={activeCst?.term_resolved ?? ''}
|
||||
editable={isEnabled}
|
||||
disabled={!isEnabled}
|
||||
onChange={newValue => setTerm(newValue)}
|
||||
/>
|
||||
<TextArea id='typification' label='Типизация'
|
||||
|
@ -262,7 +262,7 @@ function EditorConstituenta({
|
|||
value={textDefinition}
|
||||
initialValue={activeCst?.definition_raw ?? ''}
|
||||
resolved={activeCst?.definition_resolved ?? ''}
|
||||
editable={isEnabled}
|
||||
disabled={!isEnabled}
|
||||
onChange={newValue => setTextDefinition(newValue)}
|
||||
/>
|
||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||
|
|
|
@ -131,7 +131,7 @@ function EditorRSExpression({
|
|||
<RSInput innerref={rsInput}
|
||||
height='4.8rem'
|
||||
value={value}
|
||||
editable={!disabled}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
@ -14,12 +14,12 @@ import { useConceptNavigation } from '../../context/NagivationContext';
|
|||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import DlgCloneRSForm from '../../dialogs/DlgCloneRSForm';
|
||||
import DlgConstituentaTemplate from '../../dialogs/DlgConstituentaTemplate';
|
||||
import DlgCreateCst from '../../dialogs/DlgCreateCst';
|
||||
import DlgDeleteCst from '../../dialogs/DlgDeleteCst';
|
||||
import DlgEditWordForms from '../../dialogs/DlgEditWordForms';
|
||||
import DlgRenameCst from '../../dialogs/DlgRenameCst';
|
||||
import DlgShowAST from '../../dialogs/DlgShowAST';
|
||||
import DlgTemplates from '../../dialogs/DlgTemplates';
|
||||
import DlgUploadRSForm from '../../dialogs/DlgUploadRSForm';
|
||||
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||
import { ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '../../models/rsform';
|
||||
|
@ -64,7 +64,7 @@ function RSTabs() {
|
|||
|
||||
const { isModified, setIsModified } = useModificationPrompt();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<RSTabID>(RSTabID.CARD);
|
||||
const [activeTab, setActiveTab] = useState(RSTabID.CARD);
|
||||
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
||||
const activeCst = useMemo(
|
||||
() => schema?.items?.find(cst => cst.id === activeID)
|
||||
|
@ -375,7 +375,7 @@ function RSTabs() {
|
|||
target={activeCst!}
|
||||
/>}
|
||||
{showTemplates &&
|
||||
<DlgTemplates
|
||||
<DlgConstituentaTemplate
|
||||
schema={schema}
|
||||
hideWindow={() => setShowTemplates(false)}
|
||||
onCreate={handleCreateCst}
|
||||
|
@ -383,7 +383,7 @@ function RSTabs() {
|
|||
<Tabs
|
||||
selectedIndex={activeTab}
|
||||
onSelect={onSelectTab}
|
||||
defaultFocus={true}
|
||||
defaultFocus
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col items-center w-full'
|
||||
>
|
||||
|
@ -399,21 +399,21 @@ function RSTabs() {
|
|||
showUploadDialog={() => setShowUpload(true)}
|
||||
/>
|
||||
<ConceptTab
|
||||
className='border-x-2 min-w-[7.8rem]'
|
||||
title={`Название схемы: ${schema.title ?? ''}`}
|
||||
className='border-x-2'
|
||||
tooltip={`Название схемы: ${schema.title ?? ''}`}
|
||||
>
|
||||
Паспорт схемы
|
||||
</ConceptTab>
|
||||
<ConceptTab
|
||||
className='flex justify-between gap-2 border-r-2 w-fit'
|
||||
title={`Всего конституент: ${schema.stats?.count_all ?? 0}\nКоличество ошибок: ${schema.stats?.count_errors ?? 0}`}
|
||||
className='border-r-2'
|
||||
tooltip={`Всего конституент: ${schema.stats?.count_all ?? 0}\nКоличество ошибок: ${schema.stats?.count_errors ?? 0}`}
|
||||
>
|
||||
<span>Конституенты</span>
|
||||
Конституенты
|
||||
</ConceptTab>
|
||||
<ConceptTab className='border-r-2 min-w-[5.2rem]'>
|
||||
<ConceptTab className='border-r-2'>
|
||||
Редактор
|
||||
</ConceptTab>
|
||||
<ConceptTab className='min-w-[6.5rem]'>
|
||||
<ConceptTab className=''>
|
||||
Граф термов
|
||||
</ConceptTab>
|
||||
</TabList>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { GramData,Grammeme, ReferenceType } from '../models/language';
|
||||
import { CstMatchMode, DependencyMode, HelpTopic, LibraryFilterStrategy } from '../models/miscelanious';
|
||||
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
||||
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
||||
import { IArgumentInfo, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
||||
|
||||
export function describeConstituenta(cst: IConstituenta): string {
|
||||
if (cst.cst_type === CstType.STRUCTURED) {
|
||||
|
@ -309,7 +309,7 @@ export function describeCstClass(cclass: CstClass): string {
|
|||
export function labelTypification({ isValid, resultType, args }: {
|
||||
isValid: boolean;
|
||||
resultType: string;
|
||||
args: IFunctionArg[];
|
||||
args: IArgumentInfo[];
|
||||
}): string {
|
||||
if (!isValid) {
|
||||
return 'N/A';
|
||||
|
|
|
@ -4,18 +4,6 @@ export function assertIsNode(e: EventTarget | null): asserts e is Node {
|
|||
}
|
||||
}
|
||||
|
||||
export async function delay(ms: number) {
|
||||
return await new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function trimString(target: string, maxLen: number): string {
|
||||
if (target.length < maxLen) {
|
||||
return target;
|
||||
} else {
|
||||
return target.substring(0, maxLen) + '...';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for generalized text matching.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue
Block a user