From 8a4e3a6d15799e202e168335d06a66f3f62181ec Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:27:55 +0300 Subject: [PATCH] F: Rework ChangeInputSchema dialog --- .../frontend/src/features/oss/backend/api.ts | 29 ++- .../oss/backend/useUpdatePositions.tsx | 3 +- .../oss/dialogs/DlgChangeInputSchema.tsx | 63 +++--- .../frontend/src/features/oss/models/oss.ts | 12 -- .../src/features/oss/models/ossAPI.ts | 11 +- .../oss/pages/OssPage/OssEditContext.tsx | 16 +- .../features/rsform/components/PickSchema.tsx | 2 +- .../rsform/components/RSInput/tooltip.ts | 83 +++++++- .../rsform/components/RefsInput/tooltip.ts | 118 ++++++++++- .../features/rsform/dialogs/DlgRenameCst.tsx | 6 +- rsconcept/frontend/src/utils/codemirror.ts | 192 +----------------- 11 files changed, 265 insertions(+), 270 deletions(-) diff --git a/rsconcept/frontend/src/features/oss/backend/api.ts b/rsconcept/frontend/src/features/oss/backend/api.ts index 70e42c6c..35257f5e 100644 --- a/rsconcept/frontend/src/features/oss/backend/api.ts +++ b/rsconcept/frontend/src/features/oss/backend/api.ts @@ -1,4 +1,5 @@ import { queryOptions } from '@tanstack/react-query'; +import { z } from 'zod'; import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport'; import { DELAYS } from '@/backend/configuration'; @@ -8,7 +9,6 @@ import { ICstSubstitute, ICstSubstituteEx, IOperation, - IOperationPosition, OperationID, OperationType } from '@/features/oss/models/oss'; @@ -79,12 +79,33 @@ export interface IInputCreatedResponse { 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; + /** * Represents {@link IOperation} data, used in setInput process. */ -export interface IInputUpdateDTO extends ITargetOperation { - input: LibraryItemID | null; -} +export const InputUpdateSchema = z.object({ + target: z.number(), + positions: z.array(OperationPositionSchema), + input: z.number().nullable() +}); + +/** + * Represents {@link IOperation} data, used in setInput process. + */ +export type IInputUpdateDTO = z.infer; /** * Represents {@link IOperation} data, used in update process. diff --git a/rsconcept/frontend/src/features/oss/backend/useUpdatePositions.tsx b/rsconcept/frontend/src/features/oss/backend/useUpdatePositions.tsx index 125c96b5..a850cd04 100644 --- a/rsconcept/frontend/src/features/oss/backend/useUpdatePositions.tsx +++ b/rsconcept/frontend/src/features/oss/backend/useUpdatePositions.tsx @@ -2,9 +2,8 @@ import { useMutation } from '@tanstack/react-query'; import { useUpdateTimestamp } from '@/features/library/backend/useUpdateTimestamp'; 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 = () => { const { updateTimestamp } = useUpdateTimestamp(); diff --git a/rsconcept/frontend/src/features/oss/dialogs/DlgChangeInputSchema.tsx b/rsconcept/frontend/src/features/oss/dialogs/DlgChangeInputSchema.tsx index 7b71aa98..fafc4498 100644 --- a/rsconcept/frontend/src/features/oss/dialogs/DlgChangeInputSchema.tsx +++ b/rsconcept/frontend/src/features/oss/dialogs/DlgChangeInputSchema.tsx @@ -1,44 +1,51 @@ 'use client'; +import { zodResolver } from '@hookform/resolvers/zod'; import clsx from 'clsx'; -import { useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; import { MiniButton } from '@/components/Control'; import { IconReset } from '@/components/Icons'; import { Label } from '@/components/Input'; import { ModalForm } from '@/components/Modal'; 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 { 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'; export interface DlgChangeInputSchemaProps { oss: IOperationSchema; target: IOperation; - onSubmit: (target: OperationID, newSchema: LibraryItemID | undefined) => void; + positions: IOperationPosition[]; } function DlgChangeInputSchema() { - const { oss, target, onSubmit } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps); - const [selected, setSelected] = useState(target.result ?? undefined); + const { oss, target, positions } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps); + const { inputUpdate } = useInputUpdate(); + + const { setValue, handleSubmit, control } = useForm({ + resolver: zodResolver(InputUpdateSchema), + defaultValues: { + target: target.id, + positions: positions, + input: target.result + } + }); + const { items } = useLibrary(); const sortedItems = sortItemsForOSS(oss, items); - const isValid = target.result !== selected; 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) { - setSelected(newValue); - } - - function handleSubmit() { - onSubmit(target.id, selected); - return true; + function onSubmit(data: IInputUpdateDTO) { + inputUpdate({ itemID: oss.id, data: data }); } return ( @@ -46,8 +53,7 @@ function DlgChangeInputSchema() { overflowVisible header='Выбор концептуальной схемы' submitText='Подтвердить выбор' - canSubmit={isValid} - onSubmit={handleSubmit} + onSubmit={event => void handleSubmit(onSubmit)(event)} className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')} >
@@ -58,18 +64,23 @@ function DlgChangeInputSchema() { noHover noPadding icon={} - onClick={() => setSelected(undefined)} - disabled={selected == undefined} + onClick={() => setValue('input', null)} />
- ( + + )} /> ); diff --git a/rsconcept/frontend/src/features/oss/models/oss.ts b/rsconcept/frontend/src/features/oss/models/oss.ts index b57900c8..f6cefb82 100644 --- a/rsconcept/frontend/src/features/oss/models/oss.ts +++ b/rsconcept/frontend/src/features/oss/models/oss.ts @@ -43,18 +43,6 @@ export interface IOperation { arguments: OperationID[]; } -/** - * Represents {@link IOperation} position. - */ -export interface IOperationPosition extends Pick {} - -/** - * Represents all {@link IOperation} positions in {@link IOperationSchema}. - */ -export interface IPositions { - positions: IOperationPosition[]; -} - /** * Represents {@link IOperation} Argument. */ diff --git a/rsconcept/frontend/src/features/oss/models/ossAPI.ts b/rsconcept/frontend/src/features/oss/models/ossAPI.ts index 2fd28839..ddaeb534 100644 --- a/rsconcept/frontend/src/features/oss/models/ossAPI.ts +++ b/rsconcept/frontend/src/features/oss/models/ossAPI.ts @@ -16,15 +16,8 @@ import { describeSubstitutionError, information } from '@/utils/labels'; import { TextMatcher } from '@/utils/utils'; import { Graph } from '../../../models/Graph'; -import { - ICstSubstitute, - IOperation, - IOperationPosition, - IOperationSchema, - OperationID, - OperationType, - SubstitutionErrorType -} from './oss'; +import { IOperationPosition } from '../backend/api'; +import { ICstSubstitute, IOperation, IOperationSchema, OperationID, OperationType, SubstitutionErrorType } from './oss'; import { Position2D } from './ossLayout'; /** diff --git a/rsconcept/frontend/src/features/oss/pages/OssPage/OssEditContext.tsx b/rsconcept/frontend/src/features/oss/pages/OssPage/OssEditContext.tsx index 57e6561d..4cbfe463 100644 --- a/rsconcept/frontend/src/features/oss/pages/OssPage/OssEditContext.tsx +++ b/rsconcept/frontend/src/features/oss/pages/OssPage/OssEditContext.tsx @@ -15,14 +15,14 @@ import { useRoleStore } from '@/stores/role'; import { PARAMETER } from '@/utils/constants'; import { prompts } from '@/utils/labels'; -import { useInputUpdate } from '../../backend/useInputUpdate'; +import { IOperationPosition } from '../../backend/api'; import { useOperationCreate } from '../../backend/useOperationCreate'; import { useOperationDelete } from '../../backend/useOperationDelete'; import { useOperationUpdate } from '../../backend/useOperationUpdate'; import { useOssSuspense } from '../../backend/useOSS'; import { useRelocateConstituents } from '../../backend/useRelocateConstituents'; 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'; export enum OssTabID { @@ -106,7 +106,6 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren @@ -223,16 +222,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren { - inputUpdate({ - itemID: schema.id, - data: { - target: target, - positions: positions, - input: newInput ?? null - } - }); - } + positions: positions }); } diff --git a/rsconcept/frontend/src/features/rsform/components/PickSchema.tsx b/rsconcept/frontend/src/features/rsform/components/PickSchema.tsx index bae4b858..0035a233 100644 --- a/rsconcept/frontend/src/features/rsform/components/PickSchema.tsx +++ b/rsconcept/frontend/src/features/rsform/components/PickSchema.tsx @@ -18,7 +18,7 @@ import SelectLocation from '../../library/components/SelectLocation'; interface PickSchemaProps extends CProps.Styling { id?: string; - value?: LibraryItemID; + value: LibraryItemID | null; onChange: (newValue: LibraryItemID) => void; initialFilter?: string; diff --git a/rsconcept/frontend/src/features/rsform/components/RSInput/tooltip.ts b/rsconcept/frontend/src/features/rsform/components/RSInput/tooltip.ts index ea38c29f..07e5e76d 100644 --- a/rsconcept/frontend/src/features/rsform/components/RSInput/tooltip.ts +++ b/rsconcept/frontend/src/features/rsform/components/RSInput/tooltip.ts @@ -1,10 +1,12 @@ 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 { 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) => { return hoverTooltip((view, pos) => { @@ -25,3 +27,78 @@ const tooltipProducer = (schema: IRSForm, canClick?: boolean) => { export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension { 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 = `${cst.alias}: ${labelCstTypification(cst)}`; + dom.appendChild(alias); + + if (cst.term_resolved) { + const term = document.createElement('p'); + term.innerHTML = `Термин: ${cst.term_resolved}`; + dom.appendChild(term); + } + + if (cst.definition_formal) { + const expression = document.createElement('p'); + expression.innerHTML = `Выражение: ${cst.definition_formal}`; + dom.appendChild(expression); + } + + if (cst.definition_resolved) { + const definition = document.createElement('p'); + definition.innerHTML = `Определение: ${cst.definition_resolved}`; + dom.appendChild(definition); + } + + if (cst.convention) { + const convention = document.createElement('p'); + if (isBasicConcept(cst.cst_type)) { + convention.innerHTML = `Конвенция: ${cst.convention}`; + } else { + convention.innerHTML = `Комментарий: ${cst.convention}`; + } + dom.appendChild(convention); + } + + if (cst.spawner_alias) { + const derived = document.createElement('p'); + derived.innerHTML = `Основание: ${cst.spawner_alias}`; + dom.appendChild(derived); + } + + if (cst.spawn_alias.length > 0) { + const children = document.createElement('p'); + children.innerHTML = `Порождает: ${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 }; +} diff --git a/rsconcept/frontend/src/features/rsform/components/RefsInput/tooltip.ts b/rsconcept/frontend/src/features/rsform/components/RefsInput/tooltip.ts index 4a91032f..845fae9e 100644 --- a/rsconcept/frontend/src/features/rsform/components/RefsInput/tooltip.ts +++ b/rsconcept/frontend/src/features/rsform/components/RefsInput/tooltip.ts @@ -1,16 +1,15 @@ import { syntaxTree } from '@codemirror/language'; import { Extension } from '@codemirror/state'; -import { hoverTooltip } from '@codemirror/view'; +import { hoverTooltip, TooltipView } from '@codemirror/view'; +import clsx from 'clsx'; -import { - domTooltipEntityReference, - domTooltipSyntacticReference, - findContainedNodes, - findReferenceAt -} from '@/utils/codemirror'; +import { APP_COLORS, colorFgGrammeme } from '@/styling/color'; +import { findContainedNodes, findReferenceAt } from '@/utils/codemirror'; +import { describeConstituentaTerm, labelGrammeme } from '@/utils/labels'; 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'; 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 { 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 = 'Ссылка на конституенту'; + dom.appendChild(header); + + const term = document.createElement('p'); + term.innerHTML = `${ref.entity}: ${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 + клик для перехода
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 = 'Связывание слов'; + dom.appendChild(header); + + const offset = document.createElement('p'); + offset.innerHTML = `Смещение: ${ref.offset}`; + dom.appendChild(offset); + + const master = document.createElement('p'); + master.innerHTML = `Основная ссылка: ${masterRef ?? 'не определена'}`; + dom.appendChild(master); + + const nominal = document.createElement('p'); + nominal.innerHTML = `Начальная форма: ${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 }; +} diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgRenameCst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgRenameCst.tsx index bafc0ef0..22251714 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/DlgRenameCst.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgRenameCst.tsx @@ -34,9 +34,7 @@ function DlgRenameCst() { }); const alias = useWatch({ control, name: 'alias' }); const cst_type = useWatch({ control, name: 'cst_type' }); - - // TODO: validate in ZOD - const validated = alias !== target.alias && validateNewAlias(alias, cst_type, schema); + const isValid = alias !== target.alias && validateNewAlias(alias, cst_type, schema); function onSubmit(data: ICstRenameDTO) { cstRename({ itemID: schema.id, data: data }); @@ -52,7 +50,7 @@ function DlgRenameCst() { header='Переименование конституенты' submitText='Переименовать' submitInvalidTooltip='Введите незанятое имя, соответствующее типу' - canSubmit={validated} + canSubmit={isValid} onSubmit={event => void handleSubmit(onSubmit)(event)} className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex gap-3 justify-center items-center ')} helpTopic={HelpTopic.CC_CONSTITUENTA} diff --git a/rsconcept/frontend/src/utils/codemirror.ts b/rsconcept/frontend/src/utils/codemirror.ts index adf778f2..5d6e6521 100644 --- a/rsconcept/frontend/src/utils/codemirror.ts +++ b/rsconcept/frontend/src/utils/codemirror.ts @@ -3,26 +3,20 @@ */ import { syntaxTree } from '@codemirror/language'; import { NodeType, Tree, TreeCursor } from '@lezer/common'; -import { EditorState, ReactCodeMirrorRef, SelectionRange, TooltipView } from '@uiw/react-codemirror'; -import clsx from 'clsx'; +import { EditorState, ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror'; import { ReferenceTokens } from '@/features/rsform/components/RefsInput/parse'; import { RefEntity } from '@/features/rsform/components/RefsInput/parse/parser.terms'; import { GlobalTokens } from '@/features/rsform/components/RSInput/rslang'; -import { IEntityReference, ISyntacticReference } from '@/features/rsform/models/language'; -import { parseEntityReference, parseGrammemes, parseSyntacticReference } from '@/features/rsform/models/languageAPI'; -import { IConstituenta } from '@/features/rsform/models/rsform'; -import { isBasicConcept } from '@/features/rsform/models/rsformAPI'; +import { parseEntityReference, parseSyntacticReference } from '@/features/rsform/models/languageAPI'; import { SyntaxTree } from '@/features/rsform/models/rslang'; -import { APP_COLORS, colorFgGrammeme } from '@/styling/color'; import { PARAMETER } from './constants'; -import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels'; /** * Represents syntax tree node data. */ -export interface SyntaxNode { +interface SyntaxNode { type: NodeType; from: number; to: number; @@ -31,7 +25,7 @@ export interface SyntaxNode { /** * Represents syntax tree cursor data. */ -export interface CursorNode extends SyntaxNode { +interface CursorNode extends SyntaxNode { 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 = `${cst.alias}: ${labelCstTypification(cst)}`; - dom.appendChild(alias); - - if (cst.term_resolved) { - const term = document.createElement('p'); - term.innerHTML = `Термин: ${cst.term_resolved}`; - dom.appendChild(term); - } - - if (cst.definition_formal) { - const expression = document.createElement('p'); - expression.innerHTML = `Выражение: ${cst.definition_formal}`; - dom.appendChild(expression); - } - - if (cst.definition_resolved) { - const definition = document.createElement('p'); - definition.innerHTML = `Определение: ${cst.definition_resolved}`; - dom.appendChild(definition); - } - - if (cst.convention) { - const convention = document.createElement('p'); - if (isBasicConcept(cst.cst_type)) { - convention.innerHTML = `Конвенция: ${cst.convention}`; - } else { - convention.innerHTML = `Комментарий: ${cst.convention}`; - } - dom.appendChild(convention); - } - - if (cst.spawner_alias) { - const derived = document.createElement('p'); - derived.innerHTML = `Основание: ${cst.spawner_alias}`; - dom.appendChild(derived); - } - - if (cst.spawn_alias.length > 0) { - const children = document.createElement('p'); - children.innerHTML = `Порождает: ${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 = 'Ссылка на конституенту'; - dom.appendChild(header); - - const term = document.createElement('p'); - term.innerHTML = `${ref.entity}: ${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 + клик для перехода
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 = 'Связывание слов'; - dom.appendChild(header); - - const offset = document.createElement('p'); - offset.innerHTML = `Смещение: ${ref.offset}`; - dom.appendChild(offset); - - const master = document.createElement('p'); - master.innerHTML = `Основная ссылка: ${masterRef ?? 'не определена'}`; - dom.appendChild(master); - - const nominal = document.createElement('p'); - nominal.innerHTML = `Начальная форма: ${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. *