mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implementing templated expressions pt1
This commit is contained in:
parent
90c8303ee7
commit
6e610b5806
|
@ -9,15 +9,17 @@ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'>
|
||||||
colorClass?: string
|
colorClass?: string
|
||||||
dense?: boolean
|
dense?: boolean
|
||||||
noBorder?: boolean
|
noBorder?: boolean
|
||||||
|
noOutline?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextInput({
|
function TextInput({
|
||||||
id, required, label, dense, tooltip, noBorder,
|
id, required, label, dense, tooltip, noBorder, noOutline,
|
||||||
dimensions = 'w-full',
|
dimensions = 'w-full',
|
||||||
colorClass = 'clr-input',
|
colorClass = 'clr-input',
|
||||||
...props
|
...props
|
||||||
}: TextInputProps) {
|
}: TextInputProps) {
|
||||||
const borderClass = noBorder ? '': 'border';
|
const borderClass = noBorder ? '': 'border';
|
||||||
|
const outlineClass = noOutline ? '': 'clr-outline';
|
||||||
return (
|
return (
|
||||||
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
|
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
|
||||||
{label &&
|
{label &&
|
||||||
|
@ -27,7 +29,7 @@ function TextInput({
|
||||||
/>}
|
/>}
|
||||||
<input id={id}
|
<input id={id}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={`px-3 py-2 leading-tight ${borderClass} truncate hover:text-clip clr-outline ${colorClass} ${dense ? 'w-full' : dimensions}`}
|
className={`px-3 py-2 leading-tight truncate hover:text-clip ${outlineClass} ${borderClass} ${colorClass} ${dense ? 'w-full' : dimensions}`}
|
||||||
required={required}
|
required={required}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,7 @@ function HelpConstituenta() {
|
||||||
<div>
|
<div>
|
||||||
<h1>Подсказки</h1>
|
<h1>Подсказки</h1>
|
||||||
<p><b className='text-warning'>Изменения сохраняются ПОСЛЕ нажатия на соответствующую кнопку снизу или по центру</b></p>
|
<p><b className='text-warning'>Изменения сохраняются ПОСЛЕ нажатия на соответствующую кнопку снизу или по центру</b></p>
|
||||||
<p><b>Клик на формальное выражение</b> - обратите внимание на кнопки снизу<br/>Горячие клавиши указаны в подсказках при наведении</p>
|
<p><b>Формальное определение</b> - обратите внимание на кнопки снизу<br/>Горячие клавиши указаны в подсказках при наведении</p>
|
||||||
<p><b>Поля Термин и Определение</b> - Ctrl + Пробел открывает диалог редактирования отсылок<br/>Перед открытием диалога переместите текстовый курсор на заменяемое слово или ссылку</p>
|
<p><b>Поля Термин и Определение</b> - Ctrl + Пробел открывает диалог редактирования отсылок<br/>Перед открытием диалога переместите текстовый курсор на заменяемое слово или ссылку</p>
|
||||||
<p><b>Список конституент справа</b> - обратите внимание на настройки фильтрации</p>
|
<p><b>Список конституент справа</b> - обратите внимание на настройки фильтрации</p>
|
||||||
<p>- слева от ввода текста настраивается набор атрибутов конституенты</p>
|
<p>- слева от ввода текста настраивается набор атрибутов конституенты</p>
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { ErrorInfo } from '../components/BackendError';
|
||||||
import { matchLibraryItem } from '../models/library';
|
import { matchLibraryItem } from '../models/library';
|
||||||
import { ILibraryItem } from '../models/library';
|
import { ILibraryItem } from '../models/library';
|
||||||
import { ILibraryFilter } from '../models/miscelanious';
|
import { ILibraryFilter } from '../models/miscelanious';
|
||||||
import { IRSFormCreateData, IRSFormData } from '../models/rsform';
|
import { IRSForm, IRSFormCreateData, IRSFormData, loadRSFormData } from '../models/rsform';
|
||||||
import { DataCallback, deleteLibraryItem, getLibrary, getTemplates, postCloneLibraryItem, postNewRSForm } from '../utils/backendAPI';
|
import { DataCallback, deleteLibraryItem, getLibrary, getRSFormDetails, getTemplates, postCloneLibraryItem, postNewRSForm } from '../utils/backendAPI';
|
||||||
import { useAuth } from './AuthContext';
|
import { useAuth } from './AuthContext';
|
||||||
|
|
||||||
interface ILibraryContext {
|
interface ILibraryContext {
|
||||||
|
@ -15,8 +15,9 @@ interface ILibraryContext {
|
||||||
processing: boolean
|
processing: boolean
|
||||||
error: ErrorInfo
|
error: ErrorInfo
|
||||||
setError: (error: ErrorInfo) => void
|
setError: (error: ErrorInfo) => void
|
||||||
|
|
||||||
applyFilter: (params: ILibraryFilter) => ILibraryItem[]
|
applyFilter: (params: ILibraryFilter) => ILibraryItem[]
|
||||||
|
retrieveTemplate: (templateID: number, callback: (schema: IRSForm) => void) => void
|
||||||
createItem: (data: IRSFormCreateData, callback?: DataCallback<ILibraryItem>) => void
|
createItem: (data: IRSFormCreateData, callback?: DataCallback<ILibraryItem>) => void
|
||||||
cloneItem: (target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => void
|
cloneItem: (target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => void
|
||||||
destroyItem: (target: number, callback?: () => void) => void
|
destroyItem: (target: number, callback?: () => void) => void
|
||||||
|
@ -38,13 +39,15 @@ interface LibraryStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LibraryState = ({ children }: LibraryStateProps) => {
|
export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
const [ items, setItems ] = useState<ILibraryItem[]>([])
|
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||||
const [ templates, setTemplates ] = useState<ILibraryItem[]>([])
|
const [ templates, setTemplates ] = useState<ILibraryItem[]>([]);
|
||||||
const [ loading, setLoading ] = useState(false);
|
const [ loading, setLoading ] = useState(false);
|
||||||
const [ processing, setProcessing ] = useState(false);
|
const [ processing, setProcessing ] = useState(false);
|
||||||
const [ error, setError ] = useState<ErrorInfo>(undefined);
|
const [ error, setError ] = useState<ErrorInfo>(undefined);
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
const [ cachedTemplates, setCachedTemplates ] = useState<IRSForm[]>([]);
|
||||||
|
|
||||||
const applyFilter = useCallback(
|
const applyFilter = useCallback(
|
||||||
(params: ILibraryFilter) => {
|
(params: ILibraryFilter) => {
|
||||||
let result = items;
|
let result = items;
|
||||||
|
@ -69,6 +72,26 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
return result;
|
return result;
|
||||||
}, [items, user]);
|
}, [items, user]);
|
||||||
|
|
||||||
|
const retrieveTemplate = useCallback(
|
||||||
|
(templateID: number, callback: (schema: IRSForm) => void) => {
|
||||||
|
const cached = cachedTemplates.find(schema => schema.id == templateID);
|
||||||
|
if (cached) {
|
||||||
|
callback(cached);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setError(undefined);
|
||||||
|
getRSFormDetails(String(templateID), {
|
||||||
|
showError: true,
|
||||||
|
setLoading: setLoading,
|
||||||
|
onError: error => setError(error),
|
||||||
|
onSuccess: data => {
|
||||||
|
const schema = loadRSFormData(data);
|
||||||
|
setCachedTemplates(prev => ([...prev, schema]));
|
||||||
|
callback(schema);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [cachedTemplates]);
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
(callback?: () => void) => {
|
(callback?: () => void) => {
|
||||||
setItems([]);
|
setItems([]);
|
||||||
|
@ -150,7 +173,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
return (
|
return (
|
||||||
<LibraryContext.Provider value={{
|
<LibraryContext.Provider value={{
|
||||||
items, templates, loading, processing, error, setError,
|
items, templates, loading, processing, error, setError,
|
||||||
applyFilter, createItem, cloneItem, destroyItem
|
applyFilter, createItem, cloneItem, destroyItem, retrieveTemplate
|
||||||
}}>
|
}}>
|
||||||
{ children }
|
{ children }
|
||||||
</LibraryContext.Provider>
|
</LibraryContext.Provider>
|
||||||
|
|
|
@ -77,7 +77,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => updateCstData({ term_raw: event.target.value })}
|
onChange={event => updateCstData({ term_raw: event.target.value })}
|
||||||
/>
|
/>
|
||||||
<RSInput id='expression' label='Формальное выражение'
|
<RSInput id='expression' label='Формальное определение'
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
editable
|
editable
|
||||||
height='4.8rem'
|
height='4.8rem'
|
||||||
|
|
|
@ -179,7 +179,6 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
||||||
</div>);
|
</div>);
|
||||||
}, [handleSelectGrams, selectedGrams]);
|
}, [handleSelectGrams, selectedGrams]);
|
||||||
|
|
||||||
|
|
||||||
const columnsConstituenta = useMemo(
|
const columnsConstituenta = useMemo(
|
||||||
() => [
|
() => [
|
||||||
constituentaHelper.accessor('alias', {
|
constituentaHelper.accessor('alias', {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Dispatch } from 'react';
|
||||||
|
|
||||||
|
import SelectSingle from '../../components/Common/SelectSingle';
|
||||||
|
import TextArea from '../../components/Common/TextArea';
|
||||||
|
import TextInput from '../../components/Common/TextInput';
|
||||||
|
import RSInput from '../../components/RSInput';
|
||||||
|
import { CstType, ICstCreateData } from '../../models/rsform';
|
||||||
|
import { labelCstType } from '../../utils/labels';
|
||||||
|
import { SelectorCstType } from '../../utils/selectors';
|
||||||
|
|
||||||
|
interface ConstituentaTabProps {
|
||||||
|
state: ICstCreateData
|
||||||
|
partialUpdate: Dispatch<Partial<ICstCreateData>>
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConstituentaTab({state, partialUpdate}: ConstituentaTabProps) {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='flex justify-center w-full gap-6'>
|
||||||
|
<SelectSingle
|
||||||
|
className='my-2 min-w-[15rem] self-center'
|
||||||
|
options={SelectorCstType}
|
||||||
|
placeholder='Выберите тип'
|
||||||
|
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
|
||||||
|
onChange={data => partialUpdate({ cst_type: data?.value ?? CstType.TERM})}
|
||||||
|
/>
|
||||||
|
<TextInput id='alias' label='Имя'
|
||||||
|
dense
|
||||||
|
dimensions='w-[7rem]'
|
||||||
|
value={state.alias}
|
||||||
|
onChange={event => partialUpdate({ alias: event.target.value})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<TextArea id='term' label='Термин'
|
||||||
|
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
||||||
|
rows={2}
|
||||||
|
value={state.term_raw}
|
||||||
|
spellCheck
|
||||||
|
onChange={event => partialUpdate({ term_raw: event.target.value })}
|
||||||
|
/>
|
||||||
|
<RSInput id='expression' label='Формальное определение'
|
||||||
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
|
editable
|
||||||
|
height='4.8rem'
|
||||||
|
value={state.definition_formal}
|
||||||
|
onChange={value => partialUpdate({definition_formal: value})}
|
||||||
|
/>
|
||||||
|
<TextArea id='definition' label='Текстовое определение'
|
||||||
|
placeholder='Лингвистическая интерпретация формального выражения'
|
||||||
|
rows={2}
|
||||||
|
value={state.definition_raw}
|
||||||
|
spellCheck
|
||||||
|
onChange={event => partialUpdate({ definition_raw: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||||
|
rows={2}
|
||||||
|
value={state.convention}
|
||||||
|
spellCheck
|
||||||
|
onChange={event => partialUpdate({ convention: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConstituentaTab;
|
222
rsconcept/frontend/src/dialogs/DlgTemplates/TemplateTab.tsx
Normal file
222
rsconcept/frontend/src/dialogs/DlgTemplates/TemplateTab.tsx
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
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';
|
||||||
|
import { CstMatchMode } from '../../models/miscelanious';
|
||||||
|
import { applyFilterCategory, CATEGORY_CST_TYPE, IConstituenta, IRSForm, matchConstituenta } from '../../models/rsform';
|
||||||
|
import { colorfgCstStatus } from '../../utils/color';
|
||||||
|
import { prefixes } from '../../utils/constants';
|
||||||
|
|
||||||
|
export interface ITemplateState {
|
||||||
|
templateID?: number
|
||||||
|
prototype?: IConstituenta
|
||||||
|
filterCategory?: IConstituenta
|
||||||
|
filterText: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TemplateTabProps {
|
||||||
|
state: ITemplateState
|
||||||
|
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);
|
||||||
|
|
||||||
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||||
|
|
||||||
|
const prototypeInfo = useMemo(
|
||||||
|
() => {
|
||||||
|
if (!state.prototype) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
return `${state.prototype?.term_raw}${state.prototype?.definition_raw ? ` — ${state.prototype?.definition_raw}` : ''}`;
|
||||||
|
}
|
||||||
|
}, [state.prototype]);
|
||||||
|
|
||||||
|
const templateSelector = useMemo(
|
||||||
|
() => templates.map(
|
||||||
|
(template) => ({
|
||||||
|
value: template.id,
|
||||||
|
label: template.title
|
||||||
|
})
|
||||||
|
), [templates]);
|
||||||
|
|
||||||
|
const categorySelector = useMemo(
|
||||||
|
(): {value: number, label: string}[] => {
|
||||||
|
if (!selectedSchema) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return selectedSchema.items
|
||||||
|
.filter(cst => cst.cst_type === CATEGORY_CST_TYPE)
|
||||||
|
.map(cst => ({
|
||||||
|
value: cst.id,
|
||||||
|
label: cst.term_raw
|
||||||
|
}));
|
||||||
|
}, [selectedSchema]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (templates.length > 0 && !state.templateID) {
|
||||||
|
partialUpdate({ templateID: templates[0].id });
|
||||||
|
}
|
||||||
|
}, [templates, state.templateID, partialUpdate]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (!state.templateID) {
|
||||||
|
setSelectedSchema(undefined);
|
||||||
|
} else {
|
||||||
|
retrieveTemplate(state.templateID, setSelectedSchema);
|
||||||
|
}
|
||||||
|
}, [state.templateID, retrieveTemplate]);
|
||||||
|
|
||||||
|
// Filter constituents
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (!selectedSchema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = selectedSchema.items;
|
||||||
|
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',
|
||||||
|
header: 'Имя',
|
||||||
|
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',
|
||||||
|
header: 'Термин',
|
||||||
|
size: 600,
|
||||||
|
minSize: 350,
|
||||||
|
maxSize: 600
|
||||||
|
})
|
||||||
|
], [colors]);
|
||||||
|
|
||||||
|
const conditionalRowStyles = useMemo(
|
||||||
|
(): IConditionalStyle<IConstituenta>[] => [
|
||||||
|
{
|
||||||
|
when: (cst: IConstituenta) => cst.id === state.prototype?.id,
|
||||||
|
style: {
|
||||||
|
backgroundColor: colors.bgSelected
|
||||||
|
},
|
||||||
|
}
|
||||||
|
], [state.prototype, colors]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-2 mt-2'>
|
||||||
|
<div className='flex justify-between gap-4'>
|
||||||
|
<SelectSingle
|
||||||
|
className='w-full'
|
||||||
|
options={categorySelector}
|
||||||
|
placeholder='Выберите категорию'
|
||||||
|
value={state.filterCategory && selectedSchema ? {
|
||||||
|
value: state.filterCategory.id,
|
||||||
|
label: state.filterCategory.term_raw
|
||||||
|
} : null}
|
||||||
|
onChange={data => partialUpdate({filterCategory: selectedSchema?.items.find(cst => cst.id === data?.value) })}
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
<SelectSingle
|
||||||
|
className='min-w-[15rem]'
|
||||||
|
options={templateSelector}
|
||||||
|
placeholder='Выберите источник'
|
||||||
|
value={state.templateID ? { value: state.templateID, label: templates.find(item => item.id == state.templateID)!.title }: null}
|
||||||
|
onChange={data => partialUpdate({templateID: (data ? data.value : undefined)})}
|
||||||
|
/>
|
||||||
|
</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'>
|
||||||
|
<DataTable
|
||||||
|
data={filteredData}
|
||||||
|
columns={columns}
|
||||||
|
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={handleSelectTemplate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TextArea id='term'
|
||||||
|
rows={2}
|
||||||
|
disabled
|
||||||
|
placeholder='Шаблон конституенты не выбран'
|
||||||
|
value={prototypeInfo}
|
||||||
|
spellCheck
|
||||||
|
/>
|
||||||
|
<RSInput id='expression'
|
||||||
|
height='4.8rem'
|
||||||
|
placeholder='Выберите шаблон из списка'
|
||||||
|
editable={false}
|
||||||
|
value={state.prototype?.definition_formal}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TemplateTab;
|
|
@ -2,18 +2,14 @@ import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||||
import Modal, { ModalProps } from '../../components/Common/Modal';
|
import Modal, { ModalProps } from '../../components/Common/Modal';
|
||||||
import SelectSingle from '../../components/Common/SelectSingle';
|
|
||||||
import SwitchButton from '../../components/Common/SwitchButton';
|
import SwitchButton from '../../components/Common/SwitchButton';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
|
||||||
import TextInput from '../../components/Common/TextInput';
|
|
||||||
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
|
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
|
||||||
import { HelpIcon } from '../../components/Icons';
|
import { HelpIcon } from '../../components/Icons';
|
||||||
import RSInput from '../../components/RSInput';
|
|
||||||
import usePartialUpdate from '../../hooks/usePartialUpdate';
|
import usePartialUpdate from '../../hooks/usePartialUpdate';
|
||||||
import { CstType,ICstCreateData, IRSForm } from '../../models/rsform';
|
import { CstType, ICstCreateData, IRSForm } from '../../models/rsform';
|
||||||
import { labelCstType } from '../../utils/labels';
|
|
||||||
import { createAliasFor, validateCstAlias } from '../../utils/misc';
|
import { createAliasFor, validateCstAlias } from '../../utils/misc';
|
||||||
import { SelectorCstType } from '../../utils/selectors';
|
import ConstituentaTab from './ConstituentaTab';
|
||||||
|
import TemplateTab, { ITemplateState } from './TemplateTab';
|
||||||
|
|
||||||
interface DlgTemplatesProps
|
interface DlgTemplatesProps
|
||||||
extends Pick<ModalProps, 'hideWindow'> {
|
extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
@ -23,7 +19,7 @@ extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
|
||||||
function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
|
function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
|
||||||
const [validated, setValidated] = useState(false);
|
const [validated, setValidated] = useState(false);
|
||||||
const [cstData, updateCstData] = usePartialUpdate({
|
const [cstData, updateCstData] = usePartialUpdate<ICstCreateData>({
|
||||||
cst_type: CstType.TERM,
|
cst_type: CstType.TERM,
|
||||||
insert_after: null,
|
insert_after: null,
|
||||||
alias: '',
|
alias: '',
|
||||||
|
@ -33,7 +29,10 @@ function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
|
||||||
term_raw: '',
|
term_raw: '',
|
||||||
term_forms: []
|
term_forms: []
|
||||||
});
|
});
|
||||||
|
const [ templateData, updateTemplateData ] = usePartialUpdate<ITemplateState>({
|
||||||
|
filterText: ''
|
||||||
|
});
|
||||||
|
|
||||||
const [ showAttributes, setShowAttributes ] = useState(false);
|
const [ showAttributes, setShowAttributes ] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = () => onCreate(cstData);
|
const handleSubmit = () => onCreate(cstData);
|
||||||
|
@ -48,6 +47,24 @@ function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
|
||||||
setValidated(validateCstAlias(cstData.alias, cstData.cst_type, schema));
|
setValidated(validateCstAlias(cstData.alias, cstData.cst_type, schema));
|
||||||
}, [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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Создание конституенты из шаблона'
|
title='Создание конституенты из шаблона'
|
||||||
|
@ -87,56 +104,16 @@ function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ !showAttributes &&
|
{ !showAttributes &&
|
||||||
<div>
|
<TemplateTab
|
||||||
Выбор шаблона и параметров
|
state={templateData}
|
||||||
</div>}
|
partialUpdate={updateTemplateData}
|
||||||
|
/>}
|
||||||
|
|
||||||
{ showAttributes &&
|
{ showAttributes &&
|
||||||
<div>
|
<ConstituentaTab
|
||||||
<div className='flex justify-center w-full gap-6'>
|
state={cstData}
|
||||||
<SelectSingle
|
partialUpdate={updateCstData}
|
||||||
className='my-2 min-w-[15rem] self-center'
|
/>}
|
||||||
options={SelectorCstType}
|
|
||||||
placeholder='Выберите тип'
|
|
||||||
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
|
|
||||||
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.TERM})}
|
|
||||||
/>
|
|
||||||
<TextInput id='alias' label='Имя'
|
|
||||||
dense
|
|
||||||
dimensions='w-[7rem]'
|
|
||||||
value={cstData.alias}
|
|
||||||
onChange={event => updateCstData({ alias: event.target.value})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TextArea id='term' label='Термин'
|
|
||||||
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
|
||||||
rows={2}
|
|
||||||
value={cstData.term_raw}
|
|
||||||
spellCheck
|
|
||||||
onChange={event => updateCstData({ term_raw: event.target.value })}
|
|
||||||
/>
|
|
||||||
<RSInput id='expression' label='Формальное выражение'
|
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
|
||||||
editable
|
|
||||||
height='4.8rem'
|
|
||||||
value={cstData.definition_formal}
|
|
||||||
onChange={value => updateCstData({definition_formal: value})}
|
|
||||||
/>
|
|
||||||
<TextArea id='definition' label='Текстовое определение'
|
|
||||||
placeholder='Лингвистическая интерпретация формального выражения'
|
|
||||||
rows={2}
|
|
||||||
value={cstData.definition_raw}
|
|
||||||
spellCheck
|
|
||||||
onChange={event => updateCstData({ definition_raw: event.target.value })}
|
|
||||||
/>
|
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
|
||||||
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
|
||||||
rows={2}
|
|
||||||
value={cstData.convention}
|
|
||||||
spellCheck
|
|
||||||
onChange={event => updateCstData({ convention: event.target.value })}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>);
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { IRSForm, IRSFormData,loadRSFormData } from '../models/rsform'
|
import { IRSForm, IRSFormData, loadRSFormData } from '../models/rsform'
|
||||||
import { getRSFormDetails } from '../utils/backendAPI';
|
import { getRSFormDetails } from '../utils/backendAPI';
|
||||||
|
|
||||||
export function useRSFormDetails({ target }: { target?: string }) {
|
export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
|
@ -19,24 +19,24 @@ export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
(setCustomLoading?: typeof setLoading, callback?: () => void) => {
|
(setCustomLoading?: typeof setLoading, callback?: () => void) => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
getRSFormDetails(target, {
|
||||||
|
showError: true,
|
||||||
|
setLoading: setCustomLoading ?? setLoading,
|
||||||
|
onError: error => {
|
||||||
|
setInnerSchema(undefined);
|
||||||
|
setError(error);
|
||||||
|
},
|
||||||
|
onSuccess: schema => {
|
||||||
|
setSchema(schema);
|
||||||
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
getRSFormDetails(target, {
|
});
|
||||||
showError: true,
|
}, [target]);
|
||||||
setLoading: setCustomLoading ?? setLoading,
|
|
||||||
onError: error => {
|
|
||||||
setInnerSchema(undefined);
|
|
||||||
setError(error);
|
|
||||||
},
|
|
||||||
onSuccess: schema => {
|
|
||||||
setSchema(schema);
|
|
||||||
if (callback) callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [target]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload();
|
||||||
|
|
|
@ -16,6 +16,9 @@ export enum CstType {
|
||||||
THEOREM = 'theorem'
|
THEOREM = 'theorem'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CstType constant for category dividers in TemplateSchemas. TODO: create separate sctructure for templates
|
||||||
|
export const CATEGORY_CST_TYPE = CstType.THEOREM;
|
||||||
|
|
||||||
export enum CstClass {
|
export enum CstClass {
|
||||||
BASIC = 'basic',
|
BASIC = 'basic',
|
||||||
DERIVED = 'derived',
|
DERIVED = 'derived',
|
||||||
|
@ -307,3 +310,18 @@ export function createMockConstituenta(schema: number, id: number, alias: string
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyFilterCategory(target: IConstituenta, schema: IRSFormData): IConstituenta[] {
|
||||||
|
const nextCategory = schema.items.find(
|
||||||
|
cst => (
|
||||||
|
cst.order > target.order &&
|
||||||
|
cst.cst_type === CATEGORY_CST_TYPE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return schema.items.filter(
|
||||||
|
cst => (
|
||||||
|
cst.order > target.order &&
|
||||||
|
(!nextCategory || cst.order <= nextCategory.order)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,7 +229,7 @@ function EditorConstituenta({
|
||||||
colorClass='clr-app'
|
colorClass='clr-app'
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<EditorRSExpression id='expression' label='Формальное выражение'
|
<EditorRSExpression id='expression' label='Формальное определение'
|
||||||
activeCst={activeCst}
|
activeCst={activeCst}
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
value={expression}
|
value={expression}
|
||||||
|
|
|
@ -118,7 +118,7 @@ function EditorRSExpression({
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start w-full'>
|
<div className='flex flex-col items-start w-full'>
|
||||||
<div className='relative w-full'>
|
<div className='relative w-full'>
|
||||||
<div className='absolute top-[-0.2rem] left-[10.5rem]'>
|
<div className='absolute top-[-0.2rem] left-[11rem]'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Дерево разбора выражения'
|
tooltip='Дерево разбора выражения'
|
||||||
noHover
|
noHover
|
||||||
|
@ -141,7 +141,7 @@ function EditorRSExpression({
|
||||||
<div className='w-full max-h-[5rem] min-h-[5rem] flex'>
|
<div className='w-full max-h-[5rem] min-h-[5rem] flex'>
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<Button
|
<Button
|
||||||
tooltip='Проверить формальное выражение'
|
tooltip='Проверить формальное определение'
|
||||||
text='Проверить'
|
text='Проверить'
|
||||||
dimensions='w-fit h-[3rem] z-pop'
|
dimensions='w-fit h-[3rem] z-pop'
|
||||||
colorClass='clr-btn-default'
|
colorClass='clr-btn-default'
|
||||||
|
|
|
@ -34,6 +34,7 @@ export const globalIDs = {
|
||||||
|
|
||||||
export const prefixes = {
|
export const prefixes = {
|
||||||
cst_list: 'cst-list-',
|
cst_list: 'cst-list-',
|
||||||
|
cst_template_ist: 'cst-template-list-',
|
||||||
cst_wordform_list: 'cst-wordform-list-',
|
cst_wordform_list: 'cst-wordform-list-',
|
||||||
cst_status_list: 'cst-status-list-',
|
cst_status_list: 'cst-status-list-',
|
||||||
cst_match_mode_list: 'cst-match-mode-list-',
|
cst_match_mode_list: 'cst-match-mode-list-',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user