Implement TemplateExpression editing

This commit is contained in:
IRBorisov 2023-11-06 02:20:16 +03:00
parent 4cd8b31b59
commit cefd0d3c40
24 changed files with 332 additions and 253 deletions

View 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;

View File

@ -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}

View File

@ -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>

View File

@ -19,8 +19,7 @@ function SubmitButton({
>
{icon && <span>{icon}</span>}
{text && <span>{text}</span>}
</button>
)
</button>);
}
export default SubmitButton;

View File

@ -24,8 +24,7 @@ function SwitchButton<ValueType>({
>
{icon && icon}
{label}
</button>
);
</button>);
}
export default SwitchButton;

View File

@ -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(

View File

@ -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}
/>

View File

@ -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}

View File

@ -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;

View File

@ -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})}

View File

@ -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
<ConceptSearch
value={state.filterText}
onChange={event => partialUpdate({ filterText: event.target.value} )}
onChange={newValue => partialUpdate({ filterText: newValue} )}
dense
/>
</div>
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto select-none'>
<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>);

View 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;

View File

@ -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})}

View File

@ -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,24 +286,19 @@ 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>
<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'
noHeader
dense
noDataComponent={
@ -317,6 +311,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
onRowClicked={handleSelectConstituenta}
/>
</div>
</div>
<div className='flex gap-4 flex-start'>
<TextInput
label='Отсылаемая конституента'

View File

@ -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;

View File

@ -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:

View File

@ -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[]
}
}

View File

@ -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

View File

@ -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='Конвенция / Комментарий'

View File

@ -131,7 +131,7 @@ function EditorRSExpression({
<RSInput innerref={rsInput}
height='4.8rem'
value={value}
editable={!disabled}
disabled={disabled}
onChange={handleChange}
{...props}
/>

View File

@ -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>

View File

@ -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';

View File

@ -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.
*