F: Rework ChangeInputSchema dialog

This commit is contained in:
Ivan 2025-02-10 13:27:55 +03:00
parent 9f84154237
commit 8a4e3a6d15
11 changed files with 265 additions and 270 deletions

View File

@ -1,4 +1,5 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { z } from 'zod';
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport'; import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS } from '@/backend/configuration'; import { DELAYS } from '@/backend/configuration';
@ -8,7 +9,6 @@ import {
ICstSubstitute, ICstSubstitute,
ICstSubstituteEx, ICstSubstituteEx,
IOperation, IOperation,
IOperationPosition,
OperationID, OperationID,
OperationType OperationType
} from '@/features/oss/models/oss'; } from '@/features/oss/models/oss';
@ -79,12 +79,33 @@ export interface IInputCreatedResponse {
oss: IOperationSchemaDTO; oss: IOperationSchemaDTO;
} }
/**
* Represents {@link IOperation} position.
*/
export const OperationPositionSchema = z.object({
id: z.number(),
position_x: z.number(),
position_y: z.number()
});
/**
* Represents {@link IOperation} position.
*/
export type IOperationPosition = z.infer<typeof OperationPositionSchema>;
/** /**
* Represents {@link IOperation} data, used in setInput process. * Represents {@link IOperation} data, used in setInput process.
*/ */
export interface IInputUpdateDTO extends ITargetOperation { export const InputUpdateSchema = z.object({
input: LibraryItemID | null; target: z.number(),
} positions: z.array(OperationPositionSchema),
input: z.number().nullable()
});
/**
* Represents {@link IOperation} data, used in setInput process.
*/
export type IInputUpdateDTO = z.infer<typeof InputUpdateSchema>;
/** /**
* Represents {@link IOperation} data, used in update process. * Represents {@link IOperation} data, used in update process.

View File

@ -2,9 +2,8 @@ import { useMutation } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/features/library/backend/useUpdateTimestamp'; import { useUpdateTimestamp } from '@/features/library/backend/useUpdateTimestamp';
import { LibraryItemID } from '@/features/library/models/library'; import { LibraryItemID } from '@/features/library/models/library';
import { IOperationPosition } from '@/features/oss/models/oss';
import { ossApi } from './api'; import { IOperationPosition, ossApi } from './api';
export const useUpdatePositions = () => { export const useUpdatePositions = () => {
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();

View File

@ -1,44 +1,51 @@
'use client'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { Controller, useForm } from 'react-hook-form';
import { MiniButton } from '@/components/Control'; import { MiniButton } from '@/components/Control';
import { IconReset } from '@/components/Icons'; import { IconReset } from '@/components/Icons';
import { Label } from '@/components/Input'; import { Label } from '@/components/Input';
import { ModalForm } from '@/components/Modal'; import { ModalForm } from '@/components/Modal';
import { useLibrary } from '@/features/library/backend/useLibrary'; import { useLibrary } from '@/features/library/backend/useLibrary';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/features/library/models/library'; import { ILibraryItem, LibraryItemType } from '@/features/library/models/library';
import PickSchema from '@/features/rsform/components/PickSchema'; import PickSchema from '@/features/rsform/components/PickSchema';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { IOperation, IOperationSchema, OperationID } from '../models/oss'; import { IInputUpdateDTO, InputUpdateSchema, IOperationPosition } from '../backend/api';
import { useInputUpdate } from '../backend/useInputUpdate';
import { IOperation, IOperationSchema } from '../models/oss';
import { sortItemsForOSS } from '../models/ossAPI'; import { sortItemsForOSS } from '../models/ossAPI';
export interface DlgChangeInputSchemaProps { export interface DlgChangeInputSchemaProps {
oss: IOperationSchema; oss: IOperationSchema;
target: IOperation; target: IOperation;
onSubmit: (target: OperationID, newSchema: LibraryItemID | undefined) => void; positions: IOperationPosition[];
} }
function DlgChangeInputSchema() { function DlgChangeInputSchema() {
const { oss, target, onSubmit } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps); const { oss, target, positions } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps);
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined); const { inputUpdate } = useInputUpdate();
const { setValue, handleSubmit, control } = useForm<IInputUpdateDTO>({
resolver: zodResolver(InputUpdateSchema),
defaultValues: {
target: target.id,
positions: positions,
input: target.result
}
});
const { items } = useLibrary(); const { items } = useLibrary();
const sortedItems = sortItemsForOSS(oss, items); const sortedItems = sortItemsForOSS(oss, items);
const isValid = target.result !== selected;
function baseFilter(item: ILibraryItem) { function baseFilter(item: ILibraryItem) {
return !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result; return !oss.schemas.includes(item.id) || item.id === target.result;
} }
function handleSelectLocation(newValue: LibraryItemID) { function onSubmit(data: IInputUpdateDTO) {
setSelected(newValue); inputUpdate({ itemID: oss.id, data: data });
}
function handleSubmit() {
onSubmit(target.id, selected);
return true;
} }
return ( return (
@ -46,8 +53,7 @@ function DlgChangeInputSchema() {
overflowVisible overflowVisible
header='Выбор концептуальной схемы' header='Выбор концептуальной схемы'
submitText='Подтвердить выбор' submitText='Подтвердить выбор'
canSubmit={isValid} onSubmit={event => void handleSubmit(onSubmit)(event)}
onSubmit={handleSubmit}
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')} className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')}
> >
<div className='flex justify-between gap-3 items-center'> <div className='flex justify-between gap-3 items-center'>
@ -58,19 +64,24 @@ function DlgChangeInputSchema() {
noHover noHover
noPadding noPadding
icon={<IconReset size='1.25rem' className='icon-primary' />} icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => setSelected(undefined)} onClick={() => setValue('input', null)}
disabled={selected == undefined}
/> />
</div> </div>
</div> </div>
<Controller
name='input'
control={control}
render={({ field }) => (
<PickSchema <PickSchema
items={sortedItems} items={sortedItems}
itemType={LibraryItemType.RSFORM} itemType={LibraryItemType.RSFORM}
value={selected} // prettier: split-line value={field.value}
onChange={handleSelectLocation} onChange={field.onChange}
rows={14} rows={14}
baseFilter={baseFilter} baseFilter={baseFilter}
/> />
)}
/>
</ModalForm> </ModalForm>
); );
} }

View File

@ -43,18 +43,6 @@ export interface IOperation {
arguments: OperationID[]; arguments: OperationID[];
} }
/**
* Represents {@link IOperation} position.
*/
export interface IOperationPosition extends Pick<IOperation, 'id' | 'position_x' | 'position_y'> {}
/**
* Represents all {@link IOperation} positions in {@link IOperationSchema}.
*/
export interface IPositions {
positions: IOperationPosition[];
}
/** /**
* Represents {@link IOperation} Argument. * Represents {@link IOperation} Argument.
*/ */

View File

@ -16,15 +16,8 @@ import { describeSubstitutionError, information } from '@/utils/labels';
import { TextMatcher } from '@/utils/utils'; import { TextMatcher } from '@/utils/utils';
import { Graph } from '../../../models/Graph'; import { Graph } from '../../../models/Graph';
import { import { IOperationPosition } from '../backend/api';
ICstSubstitute, import { ICstSubstitute, IOperation, IOperationSchema, OperationID, OperationType, SubstitutionErrorType } from './oss';
IOperation,
IOperationPosition,
IOperationSchema,
OperationID,
OperationType,
SubstitutionErrorType
} from './oss';
import { Position2D } from './ossLayout'; import { Position2D } from './ossLayout';
/** /**

View File

@ -15,14 +15,14 @@ import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
import { useInputUpdate } from '../../backend/useInputUpdate'; import { IOperationPosition } from '../../backend/api';
import { useOperationCreate } from '../../backend/useOperationCreate'; import { useOperationCreate } from '../../backend/useOperationCreate';
import { useOperationDelete } from '../../backend/useOperationDelete'; import { useOperationDelete } from '../../backend/useOperationDelete';
import { useOperationUpdate } from '../../backend/useOperationUpdate'; import { useOperationUpdate } from '../../backend/useOperationUpdate';
import { useOssSuspense } from '../../backend/useOSS'; import { useOssSuspense } from '../../backend/useOSS';
import { useRelocateConstituents } from '../../backend/useRelocateConstituents'; import { useRelocateConstituents } from '../../backend/useRelocateConstituents';
import { useUpdatePositions } from '../../backend/useUpdatePositions'; import { useUpdatePositions } from '../../backend/useUpdatePositions';
import { IOperationPosition, IOperationSchema, OperationID, OperationType } from '../../models/oss'; import { IOperationSchema, OperationID, OperationType } from '../../models/oss';
import { calculateInsertPosition } from '../../models/ossAPI'; import { calculateInsertPosition } from '../../models/ossAPI';
export enum OssTabID { export enum OssTabID {
@ -106,7 +106,6 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
const { operationDelete } = useOperationDelete(); const { operationDelete } = useOperationDelete();
const { operationUpdate } = useOperationUpdate(); const { operationUpdate } = useOperationUpdate();
const { relocateConstituents } = useRelocateConstituents(); const { relocateConstituents } = useRelocateConstituents();
const { inputUpdate } = useInputUpdate();
useEffect( useEffect(
() => () =>
@ -223,16 +222,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
showEditInput({ showEditInput({
oss: schema, oss: schema,
target: operation, target: operation,
onSubmit: (target, newInput) => { positions: positions
inputUpdate({
itemID: schema.id,
data: {
target: target,
positions: positions,
input: newInput ?? null
}
});
}
}); });
} }

View File

@ -18,7 +18,7 @@ import SelectLocation from '../../library/components/SelectLocation';
interface PickSchemaProps extends CProps.Styling { interface PickSchemaProps extends CProps.Styling {
id?: string; id?: string;
value?: LibraryItemID; value: LibraryItemID | null;
onChange: (newValue: LibraryItemID) => void; onChange: (newValue: LibraryItemID) => void;
initialFilter?: string; initialFilter?: string;

View File

@ -1,10 +1,12 @@
import { Extension } from '@codemirror/state'; import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view'; import { hoverTooltip, TooltipView } from '@codemirror/view';
import clsx from 'clsx';
import { findAliasAt } from '@/utils/codemirror'; import { findAliasAt } from '@/utils/codemirror';
import { domTooltipConstituenta } from '@/utils/codemirror'; import { labelCstTypification } from '@/utils/labels';
import { IRSForm } from '../../models/rsform'; import { IConstituenta, IRSForm } from '../../models/rsform';
import { isBasicConcept } from '../../models/rsformAPI';
const tooltipProducer = (schema: IRSForm, canClick?: boolean) => { const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
return hoverTooltip((view, pos) => { return hoverTooltip((view, pos) => {
@ -25,3 +27,78 @@ const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension { export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
return [tooltipProducer(schema, canClick)]; return [tooltipProducer(schema, canClick)];
} }
/**
* Create DOM tooltip for {@link Constituenta}.
*/
function domTooltipConstituenta(cst?: IConstituenta, canClick?: boolean): TooltipView {
const dom = document.createElement('div');
dom.className = clsx(
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
'dense',
'p-2',
'border shadow-md',
'cc-scroll-y',
'text-sm font-main'
);
if (!cst) {
const text = document.createElement('p');
text.innerText = 'Конституента не определена';
dom.appendChild(text);
} else {
const alias = document.createElement('p');
alias.className = 'font-math';
alias.style.overflowWrap = 'anywhere';
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(cst)}`;
dom.appendChild(alias);
if (cst.term_resolved) {
const term = document.createElement('p');
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`;
dom.appendChild(term);
}
if (cst.definition_formal) {
const expression = document.createElement('p');
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`;
dom.appendChild(expression);
}
if (cst.definition_resolved) {
const definition = document.createElement('p');
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
dom.appendChild(definition);
}
if (cst.convention) {
const convention = document.createElement('p');
if (isBasicConcept(cst.cst_type)) {
convention.innerHTML = `<b>Конвенция:</b> ${cst.convention}`;
} else {
convention.innerHTML = `<b>Комментарий:</b> ${cst.convention}`;
}
dom.appendChild(convention);
}
if (cst.spawner_alias) {
const derived = document.createElement('p');
derived.innerHTML = `<b>Основание:</b> ${cst.spawner_alias}`;
dom.appendChild(derived);
}
if (cst.spawn_alias.length > 0) {
const children = document.createElement('p');
children.innerHTML = `<b>Порождает:</b> ${cst.spawn_alias.join(', ')}`;
dom.appendChild(children);
}
if (canClick) {
const clickTip = document.createElement('p');
clickTip.className = 'text-center text-xs mt-1';
clickTip.innerText = 'Ctrl + клик для перехода';
dom.appendChild(clickTip);
}
}
return { dom: dom };
}

View File

@ -1,16 +1,15 @@
import { syntaxTree } from '@codemirror/language'; import { syntaxTree } from '@codemirror/language';
import { Extension } from '@codemirror/state'; import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view'; import { hoverTooltip, TooltipView } from '@codemirror/view';
import clsx from 'clsx';
import { import { APP_COLORS, colorFgGrammeme } from '@/styling/color';
domTooltipEntityReference, import { findContainedNodes, findReferenceAt } from '@/utils/codemirror';
domTooltipSyntacticReference, import { describeConstituentaTerm, labelGrammeme } from '@/utils/labels';
findContainedNodes,
findReferenceAt
} from '@/utils/codemirror';
import { IEntityReference, ISyntacticReference } from '../../models/language'; import { IEntityReference, ISyntacticReference } from '../../models/language';
import { IRSForm } from '../../models/rsform'; import { parseGrammemes } from '../../models/languageAPI';
import { IConstituenta, IRSForm } from '../../models/rsform';
import { RefEntity } from './parse/parser.terms'; import { RefEntity } from './parse/parser.terms';
export const tooltipProducer = (schema: IRSForm, canClick?: boolean) => { export const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
@ -56,3 +55,106 @@ export const tooltipProducer = (schema: IRSForm, canClick?: boolean) => {
export function refsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension { export function refsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
return [tooltipProducer(schema, canClick)]; return [tooltipProducer(schema, canClick)];
} }
/**
* Create DOM tooltip for {@link IEntityReference}.
*/
function domTooltipEntityReference(
ref: IEntityReference,
cst: IConstituenta | undefined,
canClick?: boolean
): TooltipView {
const dom = document.createElement('div');
dom.className = clsx(
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
'dense',
'p-2 flex flex-col',
'border shadow-md',
'cc-scroll-y',
'text-sm',
'select-none cursor-auto'
);
const header = document.createElement('p');
header.innerHTML = '<b>Ссылка на конституенту</b>';
dom.appendChild(header);
const term = document.createElement('p');
term.innerHTML = `<b>${ref.entity}:</b> ${describeConstituentaTerm(cst)}`;
dom.appendChild(term);
const grams = document.createElement('div');
grams.className = 'flex flex-wrap gap-1 mt-1';
parseGrammemes(ref.form).forEach(gramStr => {
const gram = document.createElement('div');
gram.id = `tooltip-${gramStr}`;
gram.className = clsx(
'min-w-[3rem]', //
'px-1',
'border rounded-md',
'text-sm text-center whitespace-nowrap'
);
gram.style.borderWidth = '1px';
gram.style.borderColor = colorFgGrammeme(gramStr);
gram.style.color = colorFgGrammeme(gramStr);
gram.style.fontWeight = '600';
gram.style.backgroundColor = APP_COLORS.bgInput;
gram.innerText = labelGrammeme(gramStr);
grams.appendChild(gram);
});
dom.appendChild(grams);
if (canClick) {
const clickTip = document.createElement('p');
clickTip.className = 'text-center text-xs mt-1';
clickTip.innerHTML = 'Ctrl + клик для перехода</br>Ctrl + пробел для редактирования';
dom.appendChild(clickTip);
}
return { dom: dom };
}
/**
* Create DOM tooltip for {@link ISyntacticReference}.
*/
function domTooltipSyntacticReference(
ref: ISyntacticReference,
masterRef: string | undefined,
canClick?: boolean
): TooltipView {
const dom = document.createElement('div');
dom.className = clsx(
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
'dense',
'p-2 flex flex-col',
'border shadow-md',
'cc-scroll-y',
'text-sm',
'select-none cursor-auto'
);
const header = document.createElement('p');
header.innerHTML = '<b>Связывание слов</b>';
dom.appendChild(header);
const offset = document.createElement('p');
offset.innerHTML = `<b>Смещение:</b> ${ref.offset}`;
dom.appendChild(offset);
const master = document.createElement('p');
master.innerHTML = `<b>Основная ссылка: </b> ${masterRef ?? 'не определена'}`;
dom.appendChild(master);
const nominal = document.createElement('p');
nominal.innerHTML = `<b>Начальная форма:</b> ${ref.nominal}`;
dom.appendChild(nominal);
if (canClick) {
const clickTip = document.createElement('p');
clickTip.className = 'text-center text-xs mt-1';
clickTip.innerHTML = 'Ctrl + пробел для редактирования';
dom.appendChild(clickTip);
}
return { dom: dom };
}

View File

@ -34,9 +34,7 @@ function DlgRenameCst() {
}); });
const alias = useWatch({ control, name: 'alias' }); const alias = useWatch({ control, name: 'alias' });
const cst_type = useWatch({ control, name: 'cst_type' }); const cst_type = useWatch({ control, name: 'cst_type' });
const isValid = alias !== target.alias && validateNewAlias(alias, cst_type, schema);
// TODO: validate in ZOD
const validated = alias !== target.alias && validateNewAlias(alias, cst_type, schema);
function onSubmit(data: ICstRenameDTO) { function onSubmit(data: ICstRenameDTO) {
cstRename({ itemID: schema.id, data: data }); cstRename({ itemID: schema.id, data: data });
@ -52,7 +50,7 @@ function DlgRenameCst() {
header='Переименование конституенты' header='Переименование конституенты'
submitText='Переименовать' submitText='Переименовать'
submitInvalidTooltip='Введите незанятое имя, соответствующее типу' submitInvalidTooltip='Введите незанятое имя, соответствующее типу'
canSubmit={validated} canSubmit={isValid}
onSubmit={event => void handleSubmit(onSubmit)(event)} onSubmit={event => void handleSubmit(onSubmit)(event)}
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex gap-3 justify-center items-center ')} className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex gap-3 justify-center items-center ')}
helpTopic={HelpTopic.CC_CONSTITUENTA} helpTopic={HelpTopic.CC_CONSTITUENTA}

View File

@ -3,26 +3,20 @@
*/ */
import { syntaxTree } from '@codemirror/language'; import { syntaxTree } from '@codemirror/language';
import { NodeType, Tree, TreeCursor } from '@lezer/common'; import { NodeType, Tree, TreeCursor } from '@lezer/common';
import { EditorState, ReactCodeMirrorRef, SelectionRange, TooltipView } from '@uiw/react-codemirror'; import { EditorState, ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror';
import clsx from 'clsx';
import { ReferenceTokens } from '@/features/rsform/components/RefsInput/parse'; import { ReferenceTokens } from '@/features/rsform/components/RefsInput/parse';
import { RefEntity } from '@/features/rsform/components/RefsInput/parse/parser.terms'; import { RefEntity } from '@/features/rsform/components/RefsInput/parse/parser.terms';
import { GlobalTokens } from '@/features/rsform/components/RSInput/rslang'; import { GlobalTokens } from '@/features/rsform/components/RSInput/rslang';
import { IEntityReference, ISyntacticReference } from '@/features/rsform/models/language'; import { parseEntityReference, parseSyntacticReference } from '@/features/rsform/models/languageAPI';
import { parseEntityReference, parseGrammemes, parseSyntacticReference } from '@/features/rsform/models/languageAPI';
import { IConstituenta } from '@/features/rsform/models/rsform';
import { isBasicConcept } from '@/features/rsform/models/rsformAPI';
import { SyntaxTree } from '@/features/rsform/models/rslang'; import { SyntaxTree } from '@/features/rsform/models/rslang';
import { APP_COLORS, colorFgGrammeme } from '@/styling/color';
import { PARAMETER } from './constants'; import { PARAMETER } from './constants';
import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels';
/** /**
* Represents syntax tree node data. * Represents syntax tree node data.
*/ */
export interface SyntaxNode { interface SyntaxNode {
type: NodeType; type: NodeType;
from: number; from: number;
to: number; to: number;
@ -31,7 +25,7 @@ export interface SyntaxNode {
/** /**
* Represents syntax tree cursor data. * Represents syntax tree cursor data.
*/ */
export interface CursorNode extends SyntaxNode { interface CursorNode extends SyntaxNode {
isLeaf: boolean; isLeaf: boolean;
} }
@ -214,184 +208,6 @@ export function findReferenceAt(pos: number, state: EditorState) {
} }
} }
/**
* Create DOM tooltip for {@link Constituenta}.
*/
export function domTooltipConstituenta(cst?: IConstituenta, canClick?: boolean): TooltipView {
const dom = document.createElement('div');
dom.className = clsx(
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
'dense',
'p-2',
'border shadow-md',
'cc-scroll-y',
'text-sm font-main'
);
if (!cst) {
const text = document.createElement('p');
text.innerText = 'Конституента не определена';
dom.appendChild(text);
} else {
const alias = document.createElement('p');
alias.className = 'font-math';
alias.style.overflowWrap = 'anywhere';
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(cst)}`;
dom.appendChild(alias);
if (cst.term_resolved) {
const term = document.createElement('p');
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`;
dom.appendChild(term);
}
if (cst.definition_formal) {
const expression = document.createElement('p');
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`;
dom.appendChild(expression);
}
if (cst.definition_resolved) {
const definition = document.createElement('p');
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
dom.appendChild(definition);
}
if (cst.convention) {
const convention = document.createElement('p');
if (isBasicConcept(cst.cst_type)) {
convention.innerHTML = `<b>Конвенция:</b> ${cst.convention}`;
} else {
convention.innerHTML = `<b>Комментарий:</b> ${cst.convention}`;
}
dom.appendChild(convention);
}
if (cst.spawner_alias) {
const derived = document.createElement('p');
derived.innerHTML = `<b>Основание:</b> ${cst.spawner_alias}`;
dom.appendChild(derived);
}
if (cst.spawn_alias.length > 0) {
const children = document.createElement('p');
children.innerHTML = `<b>Порождает:</b> ${cst.spawn_alias.join(', ')}`;
dom.appendChild(children);
}
if (canClick) {
const clickTip = document.createElement('p');
clickTip.className = 'text-center text-xs mt-1';
clickTip.innerText = 'Ctrl + клик для перехода';
dom.appendChild(clickTip);
}
}
return { dom: dom };
}
/**
* Create DOM tooltip for {@link IEntityReference}.
*/
export function domTooltipEntityReference(
ref: IEntityReference,
cst: IConstituenta | undefined,
canClick?: boolean
): TooltipView {
const dom = document.createElement('div');
dom.className = clsx(
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
'dense',
'p-2 flex flex-col',
'border shadow-md',
'cc-scroll-y',
'text-sm',
'select-none cursor-auto'
);
const header = document.createElement('p');
header.innerHTML = '<b>Ссылка на конституенту</b>';
dom.appendChild(header);
const term = document.createElement('p');
term.innerHTML = `<b>${ref.entity}:</b> ${describeConstituentaTerm(cst)}`;
dom.appendChild(term);
const grams = document.createElement('div');
grams.className = 'flex flex-wrap gap-1 mt-1';
parseGrammemes(ref.form).forEach(gramStr => {
const gram = document.createElement('div');
gram.id = `tooltip-${gramStr}`;
gram.className = clsx(
'min-w-[3rem]', //
'px-1',
'border rounded-md',
'text-sm text-center whitespace-nowrap'
);
gram.style.borderWidth = '1px';
gram.style.borderColor = colorFgGrammeme(gramStr);
gram.style.color = colorFgGrammeme(gramStr);
gram.style.fontWeight = '600';
gram.style.backgroundColor = APP_COLORS.bgInput;
gram.innerText = labelGrammeme(gramStr);
grams.appendChild(gram);
});
dom.appendChild(grams);
if (canClick) {
const clickTip = document.createElement('p');
clickTip.className = 'text-center text-xs mt-1';
clickTip.innerHTML = 'Ctrl + клик для перехода</br>Ctrl + пробел для редактирования';
dom.appendChild(clickTip);
}
return { dom: dom };
}
/**
* Create DOM tooltip for {@link ISyntacticReference}.
*/
export function domTooltipSyntacticReference(
ref: ISyntacticReference,
masterRef: string | undefined,
canClick?: boolean
): TooltipView {
const dom = document.createElement('div');
dom.className = clsx(
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
'dense',
'p-2 flex flex-col',
'border shadow-md',
'cc-scroll-y',
'text-sm',
'select-none cursor-auto'
);
const header = document.createElement('p');
header.innerHTML = '<b>Связывание слов</b>';
dom.appendChild(header);
const offset = document.createElement('p');
offset.innerHTML = `<b>Смещение:</b> ${ref.offset}`;
dom.appendChild(offset);
const master = document.createElement('p');
master.innerHTML = `<b>Основная ссылка: </b> ${masterRef ?? 'не определена'}`;
dom.appendChild(master);
const nominal = document.createElement('p');
nominal.innerHTML = `<b>Начальная форма:</b> ${ref.nominal}`;
dom.appendChild(nominal);
if (canClick) {
const clickTip = document.createElement('p');
clickTip.className = 'text-center text-xs mt-1';
clickTip.innerHTML = 'Ctrl + пробел для редактирования';
dom.appendChild(clickTip);
}
return { dom: dom };
}
/** /**
* Wrapper class for CodeMirror editor. * Wrapper class for CodeMirror editor.
* *