Improve tooltips

This commit is contained in:
IRBorisov 2024-03-09 16:40:10 +03:00
parent 5b1fe5527f
commit 5e91eccd68
18 changed files with 120 additions and 91 deletions

View File

@ -1,16 +1,15 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
interface NavigationButtonProps { interface NavigationButtonProps extends CProps.Titled {
text?: string; text?: string;
icon: React.ReactNode; icon: React.ReactNode;
title?: string;
titleHtml?: string;
onClick?: () => void; onClick?: () => void;
} }
function NavigationButton({ icon, title, titleHtml, onClick, text }: NavigationButtonProps) { function NavigationButton({ icon, title, titleHtml, hideTitle, onClick, text }: NavigationButtonProps) {
return ( return (
<button <button
type='button' type='button'
@ -18,6 +17,7 @@ function NavigationButton({ icon, title, titleHtml, onClick, text }: NavigationB
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
onClick={onClick} onClick={onClick}
className={clsx( className={clsx(
'mr-1 h-full', // prettier: split lines 'mr-1 h-full', // prettier: split lines

View File

@ -2,8 +2,13 @@
import { HTMLMotionProps } from 'framer-motion'; import { HTMLMotionProps } from 'framer-motion';
export namespace CProps { export namespace CProps {
export type Control = { export type Titled = {
title?: string; title?: string;
titleHtml?: string;
hideTitle?: boolean;
};
export type Control = Titled & {
disabled?: boolean; disabled?: boolean;
noBorder?: boolean; noBorder?: boolean;
noOutline?: boolean; noOutline?: boolean;
@ -23,20 +28,19 @@ export namespace CProps {
}; };
export type Div = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>; export type Div = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
export type Button = Omit< export type Button = Titled &
React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, Omit<
'children' | 'type' React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
>; 'children' | 'type'
>;
export type Label = Omit< export type Label = Omit<
React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>,
'children' 'children'
>; >;
export type TextArea = React.DetailedHTMLProps< export type TextArea = Titled &
React.TextareaHTMLAttributes<HTMLTextAreaElement>, React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>;
HTMLTextAreaElement export type Input = Titled & React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
>;
export type Input = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
export type AnimatedButton = Omit<HTMLMotionProps<'button'>, 'type'>; export type AnimatedButton = Titled & Omit<HTMLMotionProps<'button'>, 'type'>;
export type AnimatedDiv = HTMLMotionProps<'div'>; export type AnimatedDiv = HTMLMotionProps<'div'>;
} }

View File

@ -7,10 +7,8 @@ import { CProps } from '../props';
interface ButtonProps extends CProps.Control, CProps.Colors, CProps.Button { interface ButtonProps extends CProps.Control, CProps.Colors, CProps.Button {
text?: string; text?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
titleHtml?: string;
dense?: boolean; dense?: boolean;
hideTitle?: boolean;
loading?: boolean; loading?: boolean;
} }
@ -19,10 +17,10 @@ function Button({
icon, icon,
title, title,
titleHtml, titleHtml,
hideTitle,
loading, loading,
dense, dense,
disabled, disabled,
hideTitle,
noBorder, noBorder,
noOutline, noOutline,
colors = 'clr-btn-default', colors = 'clr-btn-default',

View File

@ -8,14 +8,24 @@ import { CProps } from '../props';
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> { export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> {
label?: string; label?: string;
titleHtml?: string;
disabled?: boolean; disabled?: boolean;
value: boolean; value: boolean;
setValue?: (newValue: boolean) => void; setValue?: (newValue: boolean) => void;
} }
function Checkbox({ id, disabled, label, title, titleHtml, className, value, setValue, ...restProps }: CheckboxProps) { function Checkbox({
id,
disabled,
label,
title,
titleHtml,
hideTitle,
className,
value,
setValue,
...restProps
}: CheckboxProps) {
const cursor = useMemo(() => { const cursor = useMemo(() => {
if (disabled) { if (disabled) {
return 'cursor-not-allowed'; return 'cursor-not-allowed';
@ -50,6 +60,7 @@ function Checkbox({ id, disabled, label, title, titleHtml, className, value, set
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
{...restProps} {...restProps}
> >
<div <div

View File

@ -7,7 +7,6 @@ import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
import { CheckboxProps } from './Checkbox'; import { CheckboxProps } from './Checkbox';
export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> { export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
titleHtml?: string;
value: boolean | null; value: boolean | null;
setValue?: (newValue: boolean | null) => void; setValue?: (newValue: boolean | null) => void;
} }
@ -18,6 +17,7 @@ function CheckboxTristate({
label, label,
title, title,
titleHtml, titleHtml,
hideTitle,
className, className,
value, value,
setValue, setValue,
@ -62,6 +62,7 @@ function CheckboxTristate({
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
{...restProps} {...restProps}
> >
<div <div

View File

@ -8,7 +8,6 @@ import { CProps } from '../props';
interface DropdownButtonProps extends CProps.AnimatedButton { interface DropdownButtonProps extends CProps.AnimatedButton {
text?: string; text?: string;
titleHtml?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
@ -20,6 +19,7 @@ function DropdownButton({
className, className,
title, title,
titleHtml, titleHtml,
hideTitle,
onClick, onClick,
children, children,
...restProps ...restProps
@ -43,6 +43,7 @@ function DropdownButton({
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
{...restProps} {...restProps}
> >
{children ? children : null} {children ? children : null}

View File

@ -6,18 +6,16 @@ import { CProps } from '../props';
interface MiniButtonProps extends CProps.Button { interface MiniButtonProps extends CProps.Button {
icon: React.ReactNode; icon: React.ReactNode;
titleHtml?: string;
noHover?: boolean; noHover?: boolean;
hideTitle?: boolean;
} }
function MiniButton({ function MiniButton({
icon, icon,
noHover, noHover,
hideTitle,
tabIndex, tabIndex,
title, title,
titleHtml, titleHtml,
hideTitle,
className, className,
...restProps ...restProps
}: MiniButtonProps) { }: MiniButtonProps) {

View File

@ -7,6 +7,7 @@ import { BiX } from 'react-icons/bi';
import useEscapeKey from '@/hooks/useEscapeKey'; import useEscapeKey from '@/hooks/useEscapeKey';
import { animateModal } from '@/styling/animations'; import { animateModal } from '@/styling/animations';
import { prepareTooltip } from '@/utils/labels';
import { CProps } from '../props'; import { CProps } from '../props';
import Button from './Button'; import Button from './Button';
@ -71,7 +72,11 @@ function Modal({
{...restProps} {...restProps}
> >
<Overlay position='right-[0.3rem] top-2'> <Overlay position='right-[0.3rem] top-2'>
<MiniButton title='Закрыть диалоговое окно [ESC]' icon={<BiX size='1.25rem' />} onClick={handleCancel} /> <MiniButton
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<BiX size='1.25rem' />}
onClick={handleCancel}
/>
</Overlay> </Overlay>
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null} {header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}

View File

@ -6,12 +6,10 @@ import { CProps } from '../props';
interface SelectorButtonProps extends CProps.Button { interface SelectorButtonProps extends CProps.Button {
text?: string; text?: string;
titleHtml?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
colors?: string; colors?: string;
transparent?: boolean; transparent?: boolean;
hideTitle?: boolean;
} }
function SelectorButton({ function SelectorButton({

View File

@ -4,12 +4,13 @@ import { Tab as TabImpl } from 'react-tabs';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
interface TabLabelProps extends Omit<TabPropsImpl, 'children'> { import { CProps } from '../props';
interface TabLabelProps extends Omit<TabPropsImpl, 'children'>, CProps.Titled {
label?: string; label?: string;
titleHtml?: string;
} }
function TabLabel({ label, title, titleHtml, className, ...otherProps }: TabLabelProps) { function TabLabel({ label, title, titleHtml, hideTitle, className, ...otherProps }: TabLabelProps) {
return ( return (
<TabImpl <TabImpl
className={clsx( className={clsx(
@ -23,6 +24,7 @@ function TabLabel({ label, title, titleHtml, className, ...otherProps }: TabLabe
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
{...otherProps} {...otherProps}
> >
{label} {label}

View File

@ -6,6 +6,7 @@ import { FiSave } from 'react-icons/fi';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { prepareTooltip } from '@/utils/labels';
interface ConstituentaToolbarProps { interface ConstituentaToolbarProps {
isMutable: boolean; isMutable: boolean;
@ -36,7 +37,7 @@ function ConstituentaToolbar({
return ( return (
<Overlay position='top-1 right-4 sm:right-1/2 sm:translate-x-1/2' className='flex'> <Overlay position='top-1 right-4 sm:right-1/2 sm:translate-x-1/2' className='flex'>
<MiniButton <MiniButton
title='Сохранить изменения [Ctrl + S]' titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
disabled={!canSave} disabled={!canSave}
icon={<FiSave size='1.25rem' className='icon-primary' />} icon={<FiSave size='1.25rem' className='icon-primary' />}
onClick={onSubmit} onClick={onSubmit}
@ -54,7 +55,7 @@ function ConstituentaToolbar({
icon={<BiPlusCircle size={'1.25rem'} className='icon-green' />} icon={<BiPlusCircle size={'1.25rem'} className='icon-green' />}
/> />
<MiniButton <MiniButton
title='Клонировать конституенту [Alt + V]' titleHtml={prepareTooltip('Клонировать конституенту', 'Alt + V]')}
disabled={!isMutable || isModified} disabled={!isMutable || isModified}
onClick={onClone} onClick={onClone}
icon={<BiDuplicate size='1.25rem' className='icon-green' />} icon={<BiDuplicate size='1.25rem' className='icon-green' />}
@ -66,13 +67,13 @@ function ConstituentaToolbar({
icon={<BiTrash size='1.25rem' className='icon-red' />} icon={<BiTrash size='1.25rem' className='icon-red' />}
/> />
<MiniButton <MiniButton
title='Переместить вверх [Alt + вверх]' titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
icon={<BiUpvote size='1.25rem' className='icon-primary' />} icon={<BiUpvote size='1.25rem' className='icon-primary' />}
disabled={!isMutable} disabled={!isMutable}
onClick={onMoveUp} onClick={onMoveUp}
/> />
<MiniButton <MiniButton
title='Переместить вниз [Alt + вниз]' titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
icon={<BiDownvote size='1.25rem' className='icon-primary' />} icon={<BiDownvote size='1.25rem' className='icon-primary' />}
disabled={!isMutable} disabled={!isMutable}
onClick={onMoveDown} onClick={onMoveDown}

View File

@ -1,23 +1,25 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { CProps } from '@/components/props';
import { TokenID } from '@/models/rslang'; import { TokenID } from '@/models/rslang';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
interface RSLocalButtonProps { interface RSLocalButtonProps extends CProps.Titled {
text: string; text: string;
title: string;
disabled?: boolean; disabled?: boolean;
onInsert: (token: TokenID, key?: string) => void; onInsert: (token: TokenID, key?: string) => void;
} }
function RSLocalButton({ text, title, disabled, onInsert }: RSLocalButtonProps) { function RSLocalButton({ text, title, titleHtml, hideTitle, disabled, onInsert }: RSLocalButtonProps) {
return ( return (
<button <button
type='button' type='button'
tabIndex={-1} tabIndex={-1}
disabled={disabled} disabled={disabled}
data-tooltip-id={title ? globalIDs.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
className={clsx( className={clsx(
'w-[1.7rem] sm:w-[2rem] h-5 sm:h-6', 'w-[1.7rem] sm:w-[2rem] h-5 sm:h-6',
'cursor-pointer disabled:cursor-default', 'cursor-pointer disabled:cursor-default',

View File

@ -31,7 +31,7 @@ function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
} }
)} )}
data-tooltip-id={globalIDs.tooltip} data-tooltip-id={globalIDs.tooltip}
data-tooltip-content={describeToken(token)} data-tooltip-html={describeToken(token)}
> >
{label ? <span className='whitespace-nowrap'>{label}</span> : null} {label ? <span className='whitespace-nowrap'>{label}</span> : null}
</button> </button>

View File

@ -12,7 +12,7 @@ import { inferStatus } from '@/models/rsformAPI';
import { IExpressionParse, ParsingStatus } from '@/models/rslang'; import { IExpressionParse, ParsingStatus } from '@/models/rslang';
import { colorBgCstStatus } from '@/styling/color'; import { colorBgCstStatus } from '@/styling/color';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
import { labelExpressionStatus } from '@/utils/labels'; import { labelExpressionStatus, prepareTooltip } from '@/utils/labels';
import StatusIcon from './StatusIcon'; import StatusIcon from './StatusIcon';
@ -49,7 +49,7 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
)} )}
style={{ backgroundColor: processing ? colors.bgDefault : colorBgCstStatus(status, colors) }} style={{ backgroundColor: processing ? colors.bgDefault : colorBgCstStatus(status, colors) }}
data-tooltip-id={globalIDs.tooltip} data-tooltip-id={globalIDs.tooltip}
data-tooltip-content='Проверить определение [Ctrl + Q]' data-tooltip-html={prepareTooltip('Проверить определение', 'Ctrl + Q')}
onClick={onAnalyze} onClick={onAnalyze}
> >
<AnimatePresence mode='wait'> <AnimatePresence mode='wait'>

View File

@ -9,6 +9,7 @@ import HelpButton from '@/components/Help/HelpButton';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { prepareTooltip } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -28,7 +29,7 @@ function RSFormToolbar({ modified, anonymous, subscribed, claimable, onSubmit, o
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'> <Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
{controller.isContentEditable || controller.isProcessing ? ( {controller.isContentEditable || controller.isProcessing ? (
<MiniButton <MiniButton
title='Сохранить изменения [Ctrl + S]' titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
disabled={!canSave} disabled={!canSave}
icon={<FiSave size='1.25rem' className='icon-primary' />} icon={<FiSave size='1.25rem' className='icon-primary' />}
onClick={onSubmit} onClick={onSubmit}
@ -46,7 +47,7 @@ function RSFormToolbar({ modified, anonymous, subscribed, claimable, onSubmit, o
/> />
{!anonymous ? ( {!anonymous ? (
<MiniButton <MiniButton
title={`Отслеживание ${subscribed ? 'включено' : 'выключено'}`} titleHtml={`Отслеживание <b>${subscribed ? 'включено' : 'выключено'}</b>`}
disabled={controller.isProcessing} disabled={controller.isProcessing}
icon={ icon={
subscribed ? ( subscribed ? (

View File

@ -13,7 +13,7 @@ import { HelpTopic } from '@/models/miscellaneous';
import { CstType } from '@/models/rsform'; import { CstType } from '@/models/rsform';
import { getCstTypePrefix } from '@/models/rsformAPI'; import { getCstTypePrefix } from '@/models/rsformAPI';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { getCstTypeShortcut, labelCstType } from '@/utils/labels'; import { getCstTypeShortcut, labelCstType, prepareTooltip } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -29,25 +29,25 @@ function RSListToolbar({ selectedCount }: RSListToolbarProps) {
return ( return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex items-start'> <Overlay position='top-1 right-1/2 translate-x-1/2' className='flex items-start'>
<MiniButton <MiniButton
title='Переместить вверх [Alt + вверх]' titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
icon={<BiUpvote size='1.25rem' className='icon-primary' />} icon={<BiUpvote size='1.25rem' className='icon-primary' />}
disabled={!controller.isMutable || nothingSelected} disabled={!controller.isMutable || nothingSelected}
onClick={controller.moveUp} onClick={controller.moveUp}
/> />
<MiniButton <MiniButton
title='Переместить вниз [Alt + вниз]' titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
icon={<BiDownvote size='1.25rem' className='icon-primary' />} icon={<BiDownvote size='1.25rem' className='icon-primary' />}
disabled={!controller.isMutable || nothingSelected} disabled={!controller.isMutable || nothingSelected}
onClick={controller.moveDown} onClick={controller.moveDown}
/> />
<MiniButton <MiniButton
title='Клонировать конституенту [Alt + V]' titleHtml={prepareTooltip('Клонировать конституенту', 'Alt + V')}
icon={<BiDuplicate size='1.25rem' className='icon-green' />} icon={<BiDuplicate size='1.25rem' className='icon-green' />}
disabled={!controller.isMutable || selectedCount !== 1} disabled={!controller.isMutable || selectedCount !== 1}
onClick={controller.cloneCst} onClick={controller.cloneCst}
/> />
<MiniButton <MiniButton
title='Добавить новую конституенту... [Alt + `]' titleHtml={prepareTooltip('Добавить новую конституенту...', 'Alt + `')}
icon={<BiPlusCircle size='1.25rem' className='icon-green' />} icon={<BiPlusCircle size='1.25rem' className='icon-green' />}
disabled={!controller.isMutable} disabled={!controller.isMutable}
onClick={() => controller.createCst(undefined, false)} onClick={() => controller.createCst(undefined, false)}
@ -66,13 +66,13 @@ function RSListToolbar({ selectedCount }: RSListToolbarProps) {
key={`${prefixes.csttype_list}${typeStr}`} key={`${prefixes.csttype_list}${typeStr}`}
text={`${getCstTypePrefix(typeStr as CstType)}1 — ${labelCstType(typeStr as CstType)}`} text={`${getCstTypePrefix(typeStr as CstType)}1 — ${labelCstType(typeStr as CstType)}`}
onClick={() => controller.createCst(typeStr as CstType, true)} onClick={() => controller.createCst(typeStr as CstType, true)}
title={getCstTypeShortcut(typeStr as CstType)} titleHtml={getCstTypeShortcut(typeStr as CstType)}
/> />
))} ))}
</Dropdown> </Dropdown>
</div> </div>
<MiniButton <MiniButton
title='Удалить выбранные [Delete]' titleHtml={prepareTooltip('Удалить выбранные', 'Delete')}
icon={<BiTrash size='1.25rem' className='icon-red' />} icon={<BiTrash size='1.25rem' className='icon-red' />}
disabled={!controller.isMutable || nothingSelected} disabled={!controller.isMutable || nothingSelected}
onClick={controller.deleteCst} onClick={controller.deleteCst}

View File

@ -38,7 +38,7 @@ function UserTabs() {
<div> <div>
<Overlay position='top-0 right-0'> <Overlay position='top-0 right-0'>
<MiniButton <MiniButton
title='Показать/Скрыть отслеживаемые схемы' title='Отслеживаемые схемы'
icon={ icon={
showSubs ? ( showSubs ? (
<FiBell size='1.25rem' className='icon-primary' /> <FiBell size='1.25rem' className='icon-primary' />

View File

@ -136,50 +136,50 @@ export function getCstTypeShortcut(type: CstType) {
} }
} }
/** /** <b></b><br/>
* Generates description for {@link TokenID}. * Generates description for {@link TokenID}.
*/ */
export function describeToken(id: TokenID): string { export function describeToken(id: TokenID): string {
// prettier-ignore // prettier-ignore
switch (id) { switch (id) {
case TokenID.BOOLEAN: return 'Булеан [Alt + E / Shift + B]'; case TokenID.BOOLEAN: return prepareTooltip('Булеан', 'Alt + E / Shift + B');
case TokenID.DECART: return 'Декартово произведение [Alt + Shift + E / Shift + 8]'; case TokenID.DECART: return prepareTooltip('Декартово произведение', 'Alt + Shift + E / Shift + 8');
case TokenID.PUNCTUATION_PL: return 'Скобки вокруг выражения [Alt + Shift + 9 ]'; case TokenID.PUNCTUATION_PL: return prepareTooltip('Скобки () вокруг выражения', 'Alt + Shift + 9');
case TokenID.PUNCTUATION_SL: return 'Скобки вокруг выражения [Alt + [ ]'; case TokenID.PUNCTUATION_SL: return prepareTooltip('Скобки [] вокруг выражения', 'Alt + [');
case TokenID.QUANTOR_UNIVERSAL: return 'Квантор всеобщности [`]'; case TokenID.QUANTOR_UNIVERSAL: return prepareTooltip('Квантор всеобщности', '`');
case TokenID.QUANTOR_EXISTS: return 'Квантор существования [Shift + `]'; case TokenID.QUANTOR_EXISTS: return prepareTooltip('Квантор существования', 'Shift + `');
case TokenID.LOGIC_NOT: return 'Отрицание [Alt + `]'; case TokenID.LOGIC_NOT: return prepareTooltip('Отрицание', 'Alt + `');
case TokenID.LOGIC_AND: return 'Конъюнкция [Alt + 3 ~ Shift + 7]'; case TokenID.LOGIC_AND: return prepareTooltip('Конъюнкция', 'Alt + 3 ~ Shift + 7');
case TokenID.LOGIC_OR: return 'Дизъюнкция [Alt + Shift + 3]'; case TokenID.LOGIC_OR: return prepareTooltip('Дизъюнкция', 'Alt + Shift + 3');
case TokenID.LOGIC_IMPLICATION: return 'Импликация [Alt + 4]'; case TokenID.LOGIC_IMPLICATION: return prepareTooltip('Импликация', 'Alt + 4');
case TokenID.LOGIC_EQUIVALENT: return 'Эквивалентность [Alt + Shift + 4]'; case TokenID.LOGIC_EQUIVALENT: return prepareTooltip('Эквивалентность', 'Alt + Shift + 4');
case TokenID.LIT_EMPTYSET: return 'Пустое множество [Alt + X]'; case TokenID.LIT_EMPTYSET: return prepareTooltip('Пустое множество', 'Alt + X');
case TokenID.LIT_WHOLE_NUMBERS: return 'Целые числа [Alt + Z]'; case TokenID.LIT_WHOLE_NUMBERS: return prepareTooltip('Целые числа', 'Alt + Z');
case TokenID.EQUAL: return 'Равенство'; case TokenID.EQUAL: return prepareTooltip('Равенство');
case TokenID.NOTEQUAL: return 'Неравенство [Alt + Shift + `]'; case TokenID.NOTEQUAL: return prepareTooltip('Неравенство', 'Alt + Shift + `');
case TokenID.GREATER_OR_EQ: return 'Больше или равно [Alt + Shift + 7]'; case TokenID.GREATER_OR_EQ: return prepareTooltip('Больше или равно', 'Alt + Shift + 7');
case TokenID.LESSER_OR_EQ: return 'Меньше или равно [Alt + Shift + 8]'; case TokenID.LESSER_OR_EQ: return prepareTooltip('Меньше или равно', 'Alt + Shift + 8');
case TokenID.SET_IN: return 'Быть элементом (принадлежит) [Alt + 1]'; case TokenID.SET_IN: return prepareTooltip('Быть элементом (принадлежит)', 'Alt + 1');
case TokenID.SET_NOT_IN: return 'Не принадлежит [Alt + Shift + 1]'; case TokenID.SET_NOT_IN: return prepareTooltip('Не принадлежит', 'Alt + Shift + 1');
case TokenID.SUBSET_OR_EQ: return 'Быть частью (нестрогое подмножество) [Alt + 2]'; case TokenID.SUBSET_OR_EQ: return prepareTooltip('Быть частью (нестрогое подмножество)', 'Alt + 2');
case TokenID.SUBSET: return 'Строгое подмножество [Alt + 7]'; case TokenID.SUBSET: return prepareTooltip('Строгое подмножество', 'Alt + 7');
case TokenID.NOT_SUBSET: return 'Не подмножество [Alt + Shift + 2]'; case TokenID.NOT_SUBSET: return prepareTooltip('Не подмножество', 'Alt + Shift + 2');
case TokenID.SET_INTERSECTION: return 'Пересечение [Alt + A]'; case TokenID.SET_INTERSECTION: return prepareTooltip('Пересечение', 'Alt + A');
case TokenID.SET_UNION: return 'Объединение [Alt + S]'; case TokenID.SET_UNION: return prepareTooltip('Объединение', 'Alt + S');
case TokenID.SET_MINUS: return 'Разность множеств [Alt + 5]'; case TokenID.SET_MINUS: return prepareTooltip('Разность множеств', 'Alt + 5');
case TokenID.SET_SYMMETRIC_MINUS: return 'Симметрическая разность [Alt + Shift + 5]'; case TokenID.SET_SYMMETRIC_MINUS: return prepareTooltip('Симметрическая разность', 'Alt + Shift + 5');
case TokenID.NT_DECLARATIVE_EXPR: return 'Декларативная форма определения терма [Alt + D]'; case TokenID.NT_DECLARATIVE_EXPR: return prepareTooltip('Декларативное определение', 'Alt + D');
case TokenID.NT_IMPERATIVE_EXPR: return 'Императивная форма определения терма [Alt + G]'; case TokenID.NT_IMPERATIVE_EXPR: return prepareTooltip('Императивное определение', 'Alt + G');
case TokenID.NT_RECURSIVE_FULL: return 'Рекурсивная (цикличная) форма определения терма [Alt + T]'; case TokenID.NT_RECURSIVE_FULL: return prepareTooltip('Рекурсивное определение (цикл)', 'Alt + T');
case TokenID.BIGPR: return 'Большая проекция [Alt + Q]'; case TokenID.BIGPR: return prepareTooltip('Большая проекция', 'Alt + Q');
case TokenID.SMALLPR: return 'Малая проекция [Alt + W]'; case TokenID.SMALLPR: return prepareTooltip('Малая проекция', 'Alt + W');
case TokenID.FILTER: return 'Фильтр [Alt + F]'; case TokenID.FILTER: return prepareTooltip('Фильтр', 'Alt + F');
case TokenID.REDUCE: return 'Множество-сумма [Alt + R]'; case TokenID.REDUCE: return prepareTooltip('Множество-сумма', 'Alt + R');
case TokenID.CARD: return 'Мощность [Alt + C]'; case TokenID.CARD: return prepareTooltip('Мощность', 'Alt + C');
case TokenID.BOOL: return 'Синглетон [Alt + B]'; case TokenID.BOOL: return prepareTooltip('Синглетон', 'Alt + B');
case TokenID.DEBOOL: return 'Десинглетон [Alt + V]'; case TokenID.DEBOOL: return prepareTooltip('Десинглетон', 'Alt + V');
case TokenID.PUNCTUATION_ASSIGN: return 'Присвоение (императивный синтаксис) [Alt + Shift + 6]'; case TokenID.PUNCTUATION_ASSIGN: return prepareTooltip('Присвоение', 'Alt + Shift + 6');
case TokenID.PUNCTUATION_ITERATE: return 'Перебор элементов множества (императивный синтаксис) [Alt + 6]'; case TokenID.PUNCTUATION_ITERATE: return prepareTooltip('Перебор элементов множества', 'Alt + 6');
} }
return `no description: ${id}`; return `no description: ${id}`;
} }
@ -743,3 +743,10 @@ export function describeAccessMode(mode: UserAccessMode): string {
return 'Режим редактирования администратором'; return 'Режим редактирования администратором';
} }
} }
/**
* Generate HTML wrapper for control description including hotkey.
*/
export function prepareTooltip(text: string, hotkey?: string) {
return hotkey ? `<b>[${hotkey}]</b><br/>${text}` : text;
}