Improve consituenta editor UI

Started implementing TemplateExpressions
This commit is contained in:
IRBorisov 2023-10-25 21:21:43 +03:00
parent 72039727ff
commit 848617496e
23 changed files with 584 additions and 222 deletions

View File

@ -0,0 +1,31 @@
interface SwitchButtonProps<ValueType> {
id?: string
value: ValueType
label?: string
icon?: React.ReactNode
tooltip?: string
dimensions?: string
isSelected?: boolean
onSelect: (value: ValueType) => void
}
function SwitchButton<ValueType>({
value, icon, label, tooltip,
dimensions='w-fit h-fit',
isSelected, onSelect, ...props
}: SwitchButtonProps<ValueType>) {
return (
<button type='button' tabIndex={-1}
title={tooltip}
onClick={() => onSelect(value)}
className={`px-2 py-1 border font-semibold small-caps rounded-none cursor-pointer clr-btn-clear clr-hover ${dimensions} ${isSelected ? 'clr-selected': ''}`}
{...props}
>
{icon && icon}
{label}
</button>
);
}
export default SwitchButton;

View File

@ -0,0 +1,10 @@
function HelpRSTemplates() {
return (
<div>
<h1>Банк выражений</h1>
<p>Реализовано создание конституент из параметризованных выражений.</p>
<p>Функционал фильтрации и выбор шаблонов выражений находится в активной разработке.</p>
</div>);
}
export default HelpRSTemplates;

View File

@ -81,7 +81,7 @@ export function NotSubscribedIcon(props: IconProps) {
export function ASTNetworkIcon(props: IconProps) { export function ASTNetworkIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 24 24' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M21 6c0-1.654-1.346-3-3-3a2.993 2.993 0 00-2.815 2h-6.37A2.993 2.993 0 006 3C4.346 3 3 4.346 3 6c0 1.302.839 2.401 2 2.815v6.369A2.997 2.997 0 003 18c0 1.654 1.346 3 3 3a2.993 2.993 0 002.815-2h6.369a2.994 2.994 0 002.815 2c1.654 0 3-1.346 3-3a2.997 2.997 0 00-2-2.816V8.816A2.996 2.996 0 0021 6zm-3-1a1.001 1.001 0 11-1 1c0-.551.448-1 1-1zm-2.815 12h-6.37A2.99 2.99 0 007 15.184V8.816A2.99 2.99 0 008.815 7h6.369A2.99 2.99 0 0017 8.815v6.369A2.99 2.99 0 0015.185 17zM6 5a1.001 1.001 0 11-1 1c0-.551.448-1 1-1zm0 14a1.001 1.001 0 010-2 1.001 1.001 0 010 2zm12 0a1.001 1.001 0 010-2 1.001 1.001 0 010 2z'/> <path d='M12 1a2.5 2.5 0 00-2.5 2.5A2.5 2.5 0 0011 5.79V7H7a2 2 0 00-2 2v.71A2.5 2.5 0 003.5 12 2.5 2.5 0 005 14.29V15H4a2 2 0 00-2 2v1.21A2.5 2.5 0 00.5 20.5 2.5 2.5 0 003 23a2.5 2.5 0 002.5-2.5A2.5 2.5 0 004 18.21V17h4v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 009 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17a2 2 0 00-2-2H7v-.71A2.5 2.5 0 008.5 12 2.5 2.5 0 007 9.71V9h10v.71A2.5 2.5 0 0015.5 12a2.5 2.5 0 001.5 2.29V15h-1a2 2 0 00-2 2v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 0015 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17h4v1.21a2.5 2.5 0 00-1.5 2.29A2.5 2.5 0 0021 23a2.5 2.5 0 002.5-2.5 2.5 2.5 0 00-1.5-2.29V17a2 2 0 00-2-2h-1v-.71A2.5 2.5 0 0020.5 12 2.5 2.5 0 0019 9.71V9a2 2 0 00-2-2h-4V5.79a2.5 2.5 0 001.5-2.29A2.5 2.5 0 0012 1m0 1.5a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1M6 11a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m12 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1M3 19.5a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1m6 0a1 1 0 011 1 1 1 0 01-1 1 1 1 0 01-1-1 1 1 0 011-1z'/>
</IconSVG> </IconSVG>
); );
} }
@ -225,6 +225,15 @@ export function SmallPlusIcon(props: IconProps) {
); );
} }
export function ArrowDropdownIcon(props: IconProps) {
return (
<IconSVG viewbox='0 0 24 24' {...props}>
<path d='M12 1.993C6.486 1.994 2 6.48 2 11.994c0 5.513 4.486 9.999 10 10 5.514 0 10-4.486 10-10s-4.485-10-10-10.001zm0 18.001c-4.411-.001-8-3.59-8-8 0-4.411 3.589-8 8-8.001 4.411.001 8 3.59 8 8.001s-3.589 8-8 8z' />
<path d='M13 8h-2v4H7.991l4.005 4.005L16 12h-3z' />
</IconSVG>
);
}
export function UploadIcon(props: IconProps) { export function UploadIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 24 24' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
@ -253,16 +262,16 @@ export function OwnerIcon(props: IconProps) {
export function ArrowUpIcon(props: IconProps) { export function ArrowUpIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 16 16' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M8 12a.5.5 0 00.5-.5V5.707l2.146 2.147a.5.5 0 00.708-.708l-3-3a.5.5 0 00-.708 0l-3 3a.5.5 0 10.708.708L7.5 5.707V11.5a.5.5 0 00.5.5z' /> <path d='M12.781 2.375c-.381-.475-1.181-.475-1.562 0l-8 10A1.001 1.001 0 004 14h4v7a1 1 0 001 1h6a1 1 0 001-1v-7h4a1.001 1.001 0 00.781-1.625l-8-10zM15 12h-1v8h-4v-8H6.081L12 4.601 17.919 12H15z' />
</IconSVG> </IconSVG>
); );
} }
export function ArrowDownIcon(props: IconProps) { export function ArrowDownIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 16 16' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M8 4a.5.5 0 01.5.5v5.793l2.146-2.147a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 11.708-.708L7.5 10.293V4.5A.5.5 0 018 4z' /> <path d='M20.901 10.566A1.001 1.001 0 0020 10h-4V3a1 1 0 00-1-1H9a1 1 0 00-1 1v7H4a1.001 1.001 0 00-.781 1.625l8 10a1 1 0 001.562 0l8-10c.24-.301.286-.712.12-1.059zM12 19.399L6.081 12H10V4h4v8h3.919L12 19.399z' />
</IconSVG> </IconSVG>
); );
} }
@ -293,6 +302,14 @@ export function CloneIcon(props: IconProps) {
); );
} }
export function DiamondIcon(props: IconProps) {
return (
<IconSVG viewbox='0 0 24 24' {...props}>
<path d='M17.813 3.838A2 2 0 0016.187 3H7.813c-.644 0-1.252.313-1.667.899l-4 6.581a.999.999 0 00.111 1.188l9 10a.995.995 0 001.486.001l9-10a.997.997 0 00.111-1.188l-4.041-6.643zM12 19.505L5.245 12h13.509L12 19.505zM4.777 10l3.036-5 8.332-.062L19.222 10H4.777z' />
</IconSVG>
);
}
export function DumpBinIcon(props: IconProps) { export function DumpBinIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 24 24' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
@ -335,10 +352,11 @@ export function GithubIcon(props: IconProps) {
); );
} }
export function MeshIcon(props: IconProps) { export function UpdateIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 1024 1024' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M872 394c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H400V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v236H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h228v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h164c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V394h164zM628 630H400V394h228v236z' /> <path d='M2 12h2a7.986 7.986 0 012.337-5.663 7.91 7.91 0 012.542-1.71 8.12 8.12 0 016.13-.041A2.488 2.488 0 0017.5 7C18.886 7 20 5.886 20 4.5S18.886 2 17.5 2c-.689 0-1.312.276-1.763.725-2.431-.973-5.223-.958-7.635.059a9.928 9.928 0 00-3.18 2.139 9.92 9.92 0 00-2.14 3.179A10.005 10.005 0 002 12zm17.373 3.122c-.401.952-.977 1.808-1.71 2.541s-1.589 1.309-2.542 1.71a8.12 8.12 0 01-6.13.041A2.488 2.488 0 006.5 17C5.114 17 4 18.114 4 19.5S5.114 22 6.5 22c.689 0 1.312-.276 1.763-.725A9.965 9.965 0 0012 22a9.983 9.983 0 009.217-6.102A9.992 9.992 0 0022 12h-2a7.993 7.993 0 01-.627 3.122z' />
<path d='M12 7.462c-2.502 0-4.538 2.036-4.538 4.538S9.498 16.538 12 16.538s4.538-2.036 4.538-4.538S14.502 7.462 12 7.462zm0 7.076c-1.399 0-2.538-1.139-2.538-2.538S10.601 9.462 12 9.462s2.538 1.139 2.538 2.538-1.139 2.538-2.538 2.538z' />
</IconSVG> </IconSVG>
); );
} }

View File

@ -2,7 +2,11 @@ import { createColumnHelper } from '@tanstack/react-table';
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { getCompatibleGrams, Grammeme, parseEntityReference,parseGrammemes,parseSyntacticReference,ReferenceType } from '../../models/language'; import {
getCompatibleGrams, Grammeme,
parseEntityReference, parseGrammemes,
parseSyntacticReference, ReferenceType
} from '../../models/language';
import { CstMatchMode } from '../../models/miscelanious'; import { CstMatchMode } from '../../models/miscelanious';
import { IConstituenta, matchConstituenta } from '../../models/rsform'; import { IConstituenta, matchConstituenta } from '../../models/rsform';
import ConstituentaTooltip from '../../pages/RSFormPage/elements/ConstituentaTooltip'; import ConstituentaTooltip from '../../pages/RSFormPage/elements/ConstituentaTooltip';

View File

@ -1,22 +1,22 @@
import { ReferenceType } from '../../models/language'; import { ReferenceType } from '../../models/language';
import { labelReferenceType } from '../../utils/labels'; import { labelReferenceType } from '../../utils/labels';
import SwitchButton from '../Common/SwitchButton';
interface ReferenceTypeButtonProps { interface ReferenceTypeButtonProps {
id?: string
type: ReferenceType type: ReferenceType
isSelected?: boolean isSelected?: boolean
onSelect: (type: ReferenceType) => void onSelect: (type: ReferenceType) => void
} }
function ReferenceTypeButton({ type, isSelected, onSelect, ...props }: ReferenceTypeButtonProps) { function ReferenceTypeButton({ type, isSelected, onSelect }: ReferenceTypeButtonProps) {
return ( return (
<button type='button' tabIndex={-1} <SwitchButton
onClick={() => onSelect(type)} value={type}
className={`min-w-[12rem] px-2 py-1 border font-semibold small-caps rounded-none cursor-pointer clr-btn-clear clr-hover ${isSelected ? 'clr-selected': ''}`} isSelected={isSelected}
{...props} onSelect={onSelect}
> dimensions='min-w-[12rem] h-fit'
{labelReferenceType(type)} label={labelReferenceType(type)}
</button> />
); );
} }

View File

@ -25,7 +25,7 @@
--cl-red-bg-100: hsl(000, 100%, 095%); --cl-red-bg-100: hsl(000, 100%, 095%);
--cl-red-fg-100: hsl(000, 072%, 051%); --cl-red-fg-100: hsl(000, 072%, 051%);
--cl-green-fg-100: hsl(120, 080%, 058%); --cl-green-fg-100: hsl(120, 080%, 37%);
/* Dark Theme */ /* Dark Theme */
--cd-bg-120: hsl(000, 000%, 005%); --cd-bg-120: hsl(000, 000%, 005%);
@ -47,7 +47,7 @@
--cd-red-bg-100: hsl(000, 100%, 015%); --cd-red-bg-100: hsl(000, 100%, 015%);
--cd-red-fg-100: hsl(000, 080%, 055%); --cd-red-fg-100: hsl(000, 080%, 055%);
--cd-green-fg-100: hsl(120, 080%, 040%); --cd-green-fg-100: hsl(120, 080%, 042%);
/* Import overrides */ /* Import overrides */
--toastify-color-dark: var(--cd-bg-60); --toastify-color-dark: var(--cd-bg-60);

View File

@ -20,6 +20,7 @@ export enum HelpTopic {
CSTLIST = 'cstlist', CSTLIST = 'cstlist',
CONSTITUENTA = 'constituenta', CONSTITUENTA = 'constituenta',
GRAPH_TERM = 'graph-term', GRAPH_TERM = 'graph-term',
RSTEMPLATES = 'rstemplates',
RSLANG = 'rslang', RSLANG = 'rslang',
TERM_CONTROL = 'terminology-control', TERM_CONTROL = 'terminology-control',
EXTEOR = 'exteor', EXTEOR = 'exteor',

View File

@ -73,7 +73,11 @@ export interface IConstituentaList {
} }
export interface ICstCreateData export interface ICstCreateData
extends Pick<IConstituentaMeta, 'alias' | 'cst_type' | 'definition_raw' | 'term_raw' | 'convention' | 'definition_formal' > { extends Pick<
IConstituentaMeta,
'alias' | 'cst_type' | 'definition_raw' | 'term_raw' |
'convention' | 'definition_formal' | 'term_forms'
> {
insert_after: number | null insert_after: number | null
} }

View File

@ -6,6 +6,7 @@ import HelpMain from '../../components/Help/HelpMain';
import HelpRSFormItems from '../../components/Help/HelpRSFormItems'; import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
import HelpRSFormMeta from '../../components/Help/HelpRSFormMeta'; import HelpRSFormMeta from '../../components/Help/HelpRSFormMeta';
import HelpRSLang from '../../components/Help/HelpRSLang'; import HelpRSLang from '../../components/Help/HelpRSLang';
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
import HelpTermGraph from '../../components/Help/HelpTermGraph'; import HelpTermGraph from '../../components/Help/HelpTermGraph';
import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl'; import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl';
import { HelpTopic } from '../../models/miscelanious'; import { HelpTopic } from '../../models/miscelanious';
@ -23,6 +24,7 @@ function ViewTopic({ topic }: ViewTopicProps) {
{topic === HelpTopic.CSTLIST && <HelpRSFormItems />} {topic === HelpTopic.CSTLIST && <HelpRSFormItems />}
{topic === HelpTopic.CONSTITUENTA && <HelpConstituenta />} {topic === HelpTopic.CONSTITUENTA && <HelpConstituenta />}
{topic === HelpTopic.GRAPH_TERM && <HelpTermGraph />} {topic === HelpTopic.GRAPH_TERM && <HelpTermGraph />}
{topic === HelpTopic.RSTEMPLATES && <HelpRSTemplates />}
{topic === HelpTopic.RSLANG && <HelpRSLang />} {topic === HelpTopic.RSLANG && <HelpRSLang />}
{topic === HelpTopic.TERM_CONTROL && <HelpTerminologyControl />} {topic === HelpTopic.TERM_CONTROL && <HelpTerminologyControl />}
{topic === HelpTopic.EXTEOR && <HelpExteor />} {topic === HelpTopic.EXTEOR && <HelpExteor />}

View File

@ -35,7 +35,8 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
convention: convention, convention: convention,
definition_formal: expression, definition_formal: expression,
definition_raw: textDefinition, definition_raw: textDefinition,
term_raw: term term_raw: term,
term_forms: []
}; };
} }

View File

@ -0,0 +1,178 @@
import { useEffect, useLayoutEffect, useState } from 'react';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Modal, { ModalProps } from '../../components/Common/Modal';
import SelectSingle from '../../components/Common/SelectSingle';
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 { HelpIcon } from '../../components/Icons';
import RSInput from '../../components/RSInput';
import { CstType,ICstCreateData, IRSForm } from '../../models/rsform';
import { labelCstType } from '../../utils/labels';
import { createAliasFor, getCstTypePrefix } from '../../utils/misc';
import { SelectorCstType } from '../../utils/selectors';
interface DlgTemplatesProps
extends Pick<ModalProps, 'hideWindow'> {
schema: IRSForm
onCreate: (data: ICstCreateData) => void
}
function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
const [validated, setValidated] = useState(false);
const [selectedType, setSelectedType] = useState<CstType>(CstType.TERM);
const [alias, setAlias] = useState('');
const [term, setTerm] = useState('');
const [textDefinition, setTextDefinition] = useState('');
const [expression, setExpression] = useState('');
const [convention, setConvention] = useState('');
const [ showAttributes, setShowAttributes ] = useState(false);
function getData(): ICstCreateData {
return {
cst_type: selectedType,
insert_after: null,
alias: alias,
convention: convention,
definition_formal: expression,
definition_raw: textDefinition,
term_raw: term,
term_forms: []
};
}
const handleSubmit = () => onCreate(getData());
useLayoutEffect(
() => {
setAlias(createAliasFor(selectedType, schema));
}, [selectedType, schema]);
useEffect(
() => {
if(alias.length < 2 || alias[0] !== getCstTypePrefix(selectedType)) {
setValidated(false);
} else {
setValidated(!schema.items.find(cst => cst.alias === alias))
}
}, [alias, selectedType, schema]);
{/* <SwitchButton
value={type}
isSelected={isSelected}
onSelect={onSelect}
dimensions='min-w-[12rem] h-fit'
label={labelReferenceType(type)}
/> */}
// <div className='flex items-center self-center flex-start'>
// <ReferenceTypeButton
// type={ReferenceType.ENTITY}
// onSelect={setType}
// isSelected={type === ReferenceType.ENTITY}
// />
// <ReferenceTypeButton
// type={ReferenceType.SYNTACTIC}
// onSelect={setType}
// isSelected={type === ReferenceType.SYNTACTIC}
// />
// </div>
return (
<Modal
title='Создание конституенты из шаблона'
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
submitText='Создать'
>
<div className='h-fit max-w-[40rem] min-w-[40rem] min-h-[35rem] px-2 mb-2 flex flex-col justify-stretch gap-3'>
<div className='flex items-center self-center flex-start'>
<SwitchButton
label='Шаблон'
tooltip='Выбор шаблона выражения'
dimensions='min-w-[10rem] h-fit'
value={false}
isSelected={!showAttributes}
onSelect={(value) => setShowAttributes(value)}
/>
<SwitchButton
label='Конституента'
tooltip='Редактирование атрибутов конституенты'
dimensions='min-w-[10rem] h-fit'
value={true}
isSelected={showAttributes}
onSelect={(value) => setShowAttributes(value)}
/>
<div id='templates-help' className='px-1 py-1'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip
anchorSelect='#templates-help'
className='max-w-[30rem] z-modal-tooltip'
offset={4}
>
<HelpRSTemplates />
</ConceptTooltip>
</div>
{ !showAttributes &&
<div>
Выбор шаблона и параметров
</div>}
{ showAttributes &&
<div>
<div className='flex justify-center w-full gap-6'>
<SelectSingle
className='my-2 min-w-[15rem] self-center'
options={SelectorCstType}
placeholder='Выберите тип'
value={selectedType ? { value: selectedType, label: labelCstType(selectedType) } : null}
onChange={data => setSelectedType(data?.value ?? CstType.BASE)}
/>
<TextInput id='alias' label='Имя'
dense
dimensions='w-[7rem]'
value={alias}
onChange={event => setAlias(event.target.value)}
/>
</div>
<TextArea id='term' label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
rows={2}
value={term}
spellCheck
onChange={event => setTerm(event.target.value)}
/>
<RSInput id='expression' label='Формальное выражение'
placeholder='Родоструктурное выражение, задающее формальное определение'
editable
height='4.8rem'
value={expression}
onChange={value => setExpression(value)}
/>
<TextArea id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={2}
value={textDefinition}
spellCheck
onChange={event => setTextDefinition(event.target.value)}
/>
<TextArea id='convention' label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={2}
value={convention}
spellCheck
onChange={event => setConvention(event.target.value)}
/>
</div>}
</div>
</Modal>);
}
export default DlgTemplates;

View File

@ -116,6 +116,7 @@ function EditorConstituenta({
definition_formal: '', definition_formal: '',
definition_raw: '', definition_raw: '',
convention: '', convention: '',
term_forms: []
}; };
onCreateCst(data); onCreateCst(data);
} }
@ -132,6 +133,7 @@ function EditorConstituenta({
definition_formal: activeCst.definition_formal, definition_formal: activeCst.definition_formal,
definition_raw: activeCst.definition_raw, definition_raw: activeCst.definition_raw,
convention: activeCst.convention, convention: activeCst.convention,
term_forms: activeCst.term_forms
}; };
onCreateCst(data, true); onCreateCst(data, true);
} }
@ -209,7 +211,7 @@ function EditorConstituenta({
</div> </div>
</div> </div>
</div> </div>
<div className='flex flex-col gap-2 mt-1'> <div className='flex flex-col gap-3 mt-1'>
<RefsInput id='term' label='Термин' <RefsInput id='term' label='Термин'
placeholder='Обозначение, используемое в текстовых определениях данной схемы' placeholder='Обозначение, используемое в текстовых определениях данной схемы'
height='2.1rem' height='2.1rem'

View File

@ -1,11 +1,8 @@
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Button from '../../components/Common/Button';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable'; import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, MeshIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useWindowSize from '../../hooks/useWindowSize'; import useWindowSize from '../../hooks/useWindowSize';
@ -13,30 +10,28 @@ import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../mo
import { colorfgCstStatus } from '../../utils/color'; import { colorfgCstStatus } from '../../utils/color';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
import { describeExpressionStatus, labelCstTypification } from '../../utils/labels'; import { describeExpressionStatus, labelCstTypification } from '../../utils/labels';
import { getCstTypePrefix, getCstTypeShortcut } from '../../utils/misc'; import RSItemsMenu from './elements/RSItemsMenu';
// Window width cutoff for columns // Window width cutoff for columns
const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000; const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000;
const COLUMN_TYPE_HIDE_THRESHOLD = 1200; const COLUMN_TYPE_HIDE_THRESHOLD = 1200;
const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800; const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800;
const EDITOR_BUTTON_DIMENSIONS = 'h-[1.5rem] w-[1.8em]';
const columnHelper = createColumnHelper<IConstituenta>(); const columnHelper = createColumnHelper<IConstituenta>();
interface EditorItemsProps { interface EditorItemsProps {
onOpenEdit: (cstID: number) => void onOpenEdit: (cstID: number) => void
onTemplates: (selected: number[]) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
} }
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) { function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorItemsProps) {
const { colors, mainHeight } = useConceptTheme(); const { colors, mainHeight } = useConceptTheme();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm(); const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
const [selected, setSelected] = useState<number[]>([]); const [selected, setSelected] = useState<number[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({}) const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
@ -130,10 +125,33 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
definition_formal: '', definition_formal: '',
definition_raw: '', definition_raw: '',
convention: '', convention: '',
term_forms: []
}; };
onCreateCst(data, type !== undefined); onCreateCst(data, type !== undefined);
} }
// Clone selected
function handleClone() {
if (selected.length !== 1 || !schema) {
return;
}
const activeCst = schema.items.find(cst => cst.id === selected[0]);
if (!activeCst) {
return;
}
const data: ICstCreateData = {
insert_after: activeCst.id,
cst_type: activeCst.cst_type,
alias: '',
term_raw: activeCst.term_raw,
definition_formal: activeCst.definition_formal,
definition_raw: activeCst.definition_raw,
convention: activeCst.convention,
term_forms: activeCst.term_forms
};
onCreateCst(data, true);
}
// Implement hotkeys for working with constituents table // Implement hotkeys for working with constituents table
function handleTableKey(event: React.KeyboardEvent<HTMLDivElement>) { function handleTableKey(event: React.KeyboardEvent<HTMLDivElement>) {
if (!isEditable) { if (!isEditable) {
@ -297,73 +315,21 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
onKeyDown={handleTableKey} onKeyDown={handleTableKey}
> >
<div className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-b items-center h-[2.2rem] select-none clr-app'> <div className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-b items-center h-[2.2rem] select-none clr-app'>
<div className='mr-3 min-w-[9rem] whitespace-nowrap'> <div className='mr-3 min-w-[9rem] whitespace-nowrap small-caps'>
Выбор {selected.length} из {schema?.stats?.count_all ?? 0} Выбор {selected.length} из {schema?.stats?.count_all ?? 0}
</div> </div>
<div className='flex items-center justify-center w-full gap-1 pr-[9rem]'> <RSItemsMenu
<Button selected={selected}
tooltip='Переместить вверх' onMoveUp={handleMoveUp}
icon={<ArrowUpIcon size={6}/>} onMoveDown={handleMoveDown}
disabled={!isEditable || nothingSelected} onClone={handleClone}
dimensions={EDITOR_BUTTON_DIMENSIONS} onCreate={handleCreateCst}
dense onDelete={handleDelete}
onClick={handleMoveUp} onTemplates={() => onTemplates(selected)}
/> onReindex={handleReindex}
<Button />
tooltip='Переместить вниз'
icon={<ArrowDownIcon size={6}/>}
disabled={!isEditable || nothingSelected}
dimensions={EDITOR_BUTTON_DIMENSIONS}
dense
onClick={handleMoveDown}
/>
<Button
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isEditable || nothingSelected}
dimensions={EDITOR_BUTTON_DIMENSIONS}
dense
onClick={handleDelete}
/>
<Button
tooltip='Сбросить имена'
icon={<MeshIcon color={isEditable ? 'text-primary': ''} size={5}/>}
dimensions={EDITOR_BUTTON_DIMENSIONS}
dense
disabled={!isEditable}
onClick={handleReindex}
/>
<Button
tooltip='Новая конституента'
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>}
dimensions={EDITOR_BUTTON_DIMENSIONS}
dense
disabled={!isEditable}
onClick={() => handleCreateCst()}
/>
{(Object.values(CstType)).map(
(typeStr) => {
const type = typeStr as CstType;
return (
<Button key={type}
text={getCstTypePrefix(type)}
tooltip={getCstTypeShortcut(type)}
dense
dimensions={EDITOR_BUTTON_DIMENSIONS}
disabled={!isEditable}
tabIndex={-1}
onClick={() => handleCreateCst(type)}
/>);
})}
<div id='items-table-help'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#items-table-help' offset={30}>
<HelpRSFormItems />
</ConceptTooltip>
</div>
</div> </div>
<div className='w-full h-full text-sm'> <div className='w-full h-full text-sm'>
<DataTable <DataTable
data={schema?.items ?? []} data={schema?.items ?? []}

View File

@ -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.3rem]'> <div className='absolute top-[-0.2rem] left-[10.5rem]'>
<MiniButton <MiniButton
tooltip='Дерево разбора выражения' tooltip='Дерево разбора выражения'
noHover noHover
@ -138,13 +138,14 @@ function EditorRSExpression({
disabled={disabled} disabled={disabled}
onEdit={handleEdit} onEdit={handleEdit}
/> />
<div className='w-full mt-1 max-h-[5rem] min-h-[5rem] flex gap-2'> <div className='w-full max-h-[5rem] min-h-[5rem] flex'>
<div className='flex flex-col gap-1'> <div className='flex flex-col'>
<Button <Button
tooltip='Проверить формальное выражение' tooltip='Проверить формальное выражение'
text='Проверить' text='Проверить'
dimensions='w-fit h-[3rem]' dimensions='w-fit h-[3rem] z-pop'
colorClass='clr-btn-default' colorClass='clr-btn-default'
borderClass='rounded-none border'
onClick={() => handleCheckExpression()} onClick={() => handleCheckExpression()}
/> />
<StatusBar <StatusBar
@ -153,7 +154,7 @@ function EditorRSExpression({
parseData={parseData} parseData={parseData}
/> />
</div> </div>
<div className='w-full overflow-y-auto text-sm border'> <div className='w-full overflow-y-auto text-sm border rounded-none'>
{ loading && <ConceptLoader size={6} />} { loading && <ConceptLoader size={6} />}
{ !loading && parseData && { !loading && parseData &&
<ParsingResult <ParsingResult

View File

@ -278,6 +278,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '), definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '),
definition_raw: '', definition_raw: '',
convention: '', convention: '',
term_forms: []
}; };
onCreateCst(data); onCreateCst(data);
} }
@ -416,7 +417,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div>} </div>}
<div className='flex items-center justify-between py-1'> <div className='flex items-center justify-between py-1'>
<div className='mr-3 text-base'> <div className='mr-3 text-base small-caps'>
Выбор {allSelected.length} из {schema?.stats?.count_all ?? 0} Выбор {allSelected.length} из {schema?.stats?.count_all ?? 0}
</div> </div>
</div> </div>

View File

@ -24,6 +24,7 @@ import DlgDeleteCst from './DlgDeleteCst';
import DlgEditWordForms from './DlgEditWordForms'; import DlgEditWordForms from './DlgEditWordForms';
import DlgRenameCst from './DlgRenameCst'; import DlgRenameCst from './DlgRenameCst';
import DlgShowAST from './DlgShowAST'; import DlgShowAST from './DlgShowAST';
import DlgTemplates from './DlgTemplates';
import DlgUploadRSForm from './DlgUploadRSForm'; import DlgUploadRSForm from './DlgUploadRSForm';
import EditorConstituenta from './EditorConstituenta'; import EditorConstituenta from './EditorConstituenta';
import EditorItems from './EditorItems'; import EditorItems from './EditorItems';
@ -89,6 +90,8 @@ function RSTabs() {
const [showEditTerm, setShowEditTerm] = useState(false); const [showEditTerm, setShowEditTerm] = useState(false);
const [showTemplates, setShowTemplates] = useState(false);
const panelHeight = useMemo( const panelHeight = useMemo(
() => { () => {
return !noNavigation ? return !noNavigation ?
@ -261,6 +264,11 @@ function RSTabs() {
.catch(console.error); .catch(console.error);
}, []); }, []);
const onShowTemplates = useCallback(
() => {
setShowTemplates(true);
}, []);
const onDownloadSchema = useCallback( const onDownloadSchema = useCallback(
() => { () => {
if (isModified) { if (isModified) {
@ -367,6 +375,12 @@ function RSTabs() {
onSave={handleSaveWordforms} onSave={handleSaveWordforms}
target={activeCst!} target={activeCst!}
/>} />}
{showTemplates &&
<DlgTemplates
schema={schema}
hideWindow={() => setShowTemplates(false)}
onCreate={handleCreateCst}
/>}
<Tabs <Tabs
selectedIndex={activeTab} selectedIndex={activeTab}
onSelect={onSelectTab} onSelect={onSelectTab}
@ -381,6 +395,7 @@ function RSTabs() {
onClaim={onClaimSchema} onClaim={onClaimSchema}
onShare={onShareSchema} onShare={onShareSchema}
onToggleSubscribe={handleToggleSubscribe} onToggleSubscribe={handleToggleSubscribe}
onTemplates={onShowTemplates}
showCloneDialog={promptClone} showCloneDialog={promptClone}
showUploadDialog={() => setShowUpload(true)} showUploadDialog={() => setShowUpload(true)}
/> />
@ -422,6 +437,7 @@ function RSTabs() {
onOpenEdit={onOpenCst} onOpenEdit={onOpenCst}
onCreateCst={promptCreateCst} onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst} onDeleteCst={promptDeleteCst}
onTemplates={() => onShowTemplates()} // TODO: implement insertion point
/> />
</TabPanel> </TabPanel>

View File

@ -85,8 +85,8 @@ interface RSEditorControlsProps {
function RSEditorControls({ onEdit, disabled }: RSEditorControlsProps) { function RSEditorControls({ onEdit, disabled }: RSEditorControlsProps) {
return ( return (
<div className='flex items-center justify-between w-full mt-1 text-sm'> <div className='flex items-center justify-between w-full text-sm'>
<div className='w-fit'> <div className='border-r w-fit'>
<div className='flex justify-start'> <div className='flex justify-start'>
{MAIN_FIRST_ROW.map( {MAIN_FIRST_ROW.map(
(token) => (token) =>
@ -110,7 +110,7 @@ function RSEditorControls({ onEdit, disabled }: RSEditorControlsProps) {
</div> </div>
</div> </div>
<div className='w-fit'> <div className='border-l w-fit'>
<div className='flex justify-start'> <div className='flex justify-start'>
{SECONDARY_FIRST_ROW.map( {SECONDARY_FIRST_ROW.map(
({text, tooltip}) => ({text, tooltip}) =>

View File

@ -0,0 +1,114 @@
import { useMemo } from 'react';
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
import Dropdown from '../../../components/Common/Dropdown';
import DropdownButton from '../../../components/Common/DropdownButton';
import MiniButton from '../../../components/Common/MiniButton';
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons';
import { useRSForm } from '../../../context/RSFormContext';
import useDropdown from '../../../hooks/useDropdown';
import { CstType } from '../../../models/rsform';
import { labelCstType } from '../../../utils/labels';
import { getCstTypePrefix, getCstTypeShortcut } from '../../../utils/misc';
interface RSItemsMenuProps {
selected: number[]
onMoveUp: () => void
onMoveDown: () => void
onDelete: () => void
onClone: () => void
onCreate: (type?: CstType) => void
onTemplates: () => void
onReindex: () => void
}
function RSItemsMenu({
selected,
onMoveUp, onMoveDown, onDelete, onClone, onCreate, onTemplates, onReindex
}: RSItemsMenuProps) {
const { isEditable } = useRSForm();
const insertMenu = useDropdown();
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
return (
<div className='flex items-center justify-center w-full pr-[9rem]'>
<MiniButton
tooltip='Переместить вверх'
icon={<ArrowUpIcon size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={onMoveUp}
/>
<MiniButton
tooltip='Переместить вниз'
icon={<ArrowDownIcon size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={onMoveDown}
/>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={onDelete}
/>
<MiniButton
tooltip='Клонировать конституенту'
icon={<CloneIcon color={isEditable && selected.length === 1 ? 'text-success': ''} size={5}/>}
disabled={!isEditable || selected.length !== 1}
onClick={onClone}
/>
<MiniButton
tooltip='Добавить новую конституенту...'
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>}
disabled={!isEditable}
onClick={() => onCreate()}
/>
<div ref={insertMenu.ref} className='flex justify-center'>
<MiniButton
tooltip='Добавить пустую конституенту'
icon={<ArrowDropdownIcon color={isEditable ? 'text-success': ''} size={5}/>}
disabled={!isEditable}
onClick={insertMenu.toggle}
/>
{ insertMenu.isActive &&
<Dropdown>
{(Object.values(CstType)).map(
(typeStr) => {
const type = typeStr as CstType;
return (
<DropdownButton
onClick={() => onCreate(type)}
tooltip={getCstTypeShortcut(type)}
>
{`${getCstTypePrefix(type)}1 — ${labelCstType(type)}`}
</DropdownButton>);
})}
</Dropdown>}
</div>
<MiniButton
tooltip='Создать конституенту из шаблона'
icon={<DiamondIcon color={isEditable ? 'text-primary': ''} size={5}/>}
disabled={!isEditable}
onClick={onTemplates}
/>
<MiniButton
tooltip='Сброс имен: присвоить порядковые имена'
icon={<UpdateIcon color={isEditable ? 'text-primary': ''} size={5}/>}
disabled={!isEditable}
onClick={onReindex}
/>
<div className='ml-1' id='items-table-help'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#items-table-help' offset={30}>
<HelpRSFormItems />
</ConceptTooltip>
</div>);
}
export default RSItemsMenu;

View File

@ -15,7 +15,7 @@ function RSLocalButton({ text, tooltip, disabled, onInsert }: RSLocalButtonProps
onClick={() => onInsert(TokenID.ID_LOCAL, text)} onClick={() => onInsert(TokenID.ID_LOCAL, text)}
title={tooltip} title={tooltip}
tabIndex={-1} tabIndex={-1}
className='w-[2.25rem] h-6 cursor-pointer disabled:cursor-default border rounded-none clr-hover clr-btn-clear' className='w-[2rem] h-6 cursor-pointer disabled:cursor-default border rounded-none clr-hover clr-btn-clear'
> >
{text} {text}
</button> </button>

View File

@ -5,7 +5,7 @@ import Dropdown from '../../../components/Common/Dropdown';
import DropdownButton from '../../../components/Common/DropdownButton'; import DropdownButton from '../../../components/Common/DropdownButton';
import DropdownCheckbox from '../../../components/Common/DropdownCheckbox'; import DropdownCheckbox from '../../../components/Common/DropdownCheckbox';
import { import {
CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon, CloneIcon, DiamondIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon
} from '../../../components/Icons'; } from '../../../components/Icons';
import { useAuth } from '../../../context/AuthContext'; import { useAuth } from '../../../context/AuthContext';
@ -20,11 +20,12 @@ interface RSTabsMenuProps {
onShare: () => void onShare: () => void
onDownload: () => void onDownload: () => void
onToggleSubscribe: () => void onToggleSubscribe: () => void
onTemplates: () => void
} }
function RSTabsMenu({ function RSTabsMenu({
showUploadDialog, showCloneDialog, showUploadDialog, showCloneDialog,
onDestroy, onShare, onDownload, onClaim, onToggleSubscribe onDestroy, onShare, onDownload, onClaim, onToggleSubscribe, onTemplates
}: RSTabsMenuProps) { }: RSTabsMenuProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useAuth(); const { user } = useAuth();
@ -44,6 +45,11 @@ function RSTabsMenu({
schemaMenu.hide(); schemaMenu.hide();
onDestroy(); onDestroy();
} }
function handleTemplates() {
schemaMenu.hide();
onTemplates();
}
function handleDownload () { function handleDownload () {
schemaMenu.hide(); schemaMenu.hide();
@ -70,118 +76,123 @@ function RSTabsMenu({
} }
return ( return (
<div className='flex items-stretch h-full w-fit'> <div className='flex items-stretch h-full w-fit'>
<div ref={schemaMenu.ref}> <div ref={schemaMenu.ref}>
<Button <Button
tooltip='Действия' tooltip='Действия'
icon={<MenuIcon color='text-controls' size={5}/>} icon={<MenuIcon color='text-controls' size={5}/>}
borderClass='' borderClass=''
dimensions='h-full w-fit pl-2' dimensions='h-full w-fit pl-2'
style={{outlineColor: 'transparent'}} style={{outlineColor: 'transparent'}}
dense dense
onClick={schemaMenu.toggle} onClick={schemaMenu.toggle}
tabIndex={-1} tabIndex={-1}
/> />
{ schemaMenu.isActive && { schemaMenu.isActive &&
<Dropdown> <Dropdown>
<DropdownButton onClick={handleShare}> <DropdownButton onClick={handleShare}>
<div className='inline-flex items-center justify-start gap-2'> <div className='inline-flex items-center justify-start gap-2'>
<ShareIcon color='text-primary' size={4}/> <ShareIcon color='text-primary' size={4}/>
<p>Поделиться</p> <p>Поделиться</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={handleClone} disabled={!user} > <DropdownButton onClick={handleClone} disabled={!user} >
<div className='inline-flex items-center justify-start gap-2'> <div className='inline-flex items-center justify-start gap-2'>
<CloneIcon color='text-primary' size={4}/> <CloneIcon color='text-primary' size={4}/>
<p>Клонировать</p> <p>Клонировать</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={handleDownload}> <DropdownButton onClick={handleTemplates} disabled={!isEditable} >
<div className='inline-flex items-center justify-start gap-2'> <div className='inline-flex items-center justify-start gap-2'>
<DownloadIcon color='text-primary' size={4}/> <DiamondIcon color={isEditable ? 'text-success' : ''} size={4}/>
<p>Выгрузить в Экстеор</p> <p>Банк выражений</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleUpload}> <DropdownButton onClick={handleDownload}>
<div className='inline-flex items-center justify-start gap-2'> <div className='inline-flex items-center justify-start gap-2'>
<UploadIcon color={isEditable ? 'text-warning' : ''} size={4}/> <DownloadIcon color='text-primary' size={4}/>
<p>Загрузить из Экстеора</p> <p>Выгрузить в Экстеор</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleDelete}> <DropdownButton disabled={!isEditable} onClick={handleUpload}>
<span className='inline-flex items-center justify-start gap-2'> <div className='inline-flex items-center justify-start gap-2'>
<DumpBinIcon color={isEditable ? 'text-warning' : ''} size={4} /> <UploadIcon color={isEditable ? 'text-warning' : ''} size={4}/>
<p>Удалить схему</p> <p>Загрузить из Экстеора</p>
</span> </div>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={handleCreateNew}> <DropdownButton disabled={!isEditable} onClick={handleDelete}>
<span className='inline-flex items-center justify-start gap-2'> <span className='inline-flex items-center justify-start gap-2'>
<SmallPlusIcon color='text-url' size={4} /> <DumpBinIcon color={isEditable ? 'text-warning' : ''} size={4} />
<p>Создать новую схему</p> <p>Удалить схему</p>
</span> </span>
</DropdownButton> </DropdownButton>
</Dropdown>} <DropdownButton onClick={handleCreateNew}>
</div> <span className='inline-flex items-center justify-start gap-2'>
<div ref={editMenu.ref}> <SmallPlusIcon color='text-url' size={4} />
<Button <p>Создать новую схему</p>
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')} </span>
borderClass='' </DropdownButton>
dimensions='h-full w-fit' </Dropdown>}
style={{outlineColor: 'transparent'}}
icon={<EditIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense
onClick={editMenu.toggle}
tabIndex={-1}
/>
{ editMenu.isActive &&
<Dropdown>
<DropdownButton
disabled={!user || !isClaimable}
onClick={!isOwned ? handleClaimOwner : undefined}
tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''}
>
<div className='flex items-center gap-2 pl-1'>
<span><OwnerIcon size={5} color={isOwned ? 'text-success' : 'text-controls'} /></span>
<p>
{ isOwned && <b>Владелец схемы</b> }
{ !isOwned && <b>Стать владельцем</b> }
</p>
</div>
</DropdownButton>
{(isOwned || user?.is_staff) &&
<DropdownCheckbox
value={isReadonly}
setValue={toggleReadonly}
label='Я — читатель!'
tooltip='Режим чтения'
/>}
{user?.is_staff &&
<DropdownCheckbox
value={isForceAdmin}
setValue={toggleForceAdmin}
label='Я — администратор!'
tooltip='Режим редактирования для администраторов'
/>}
</Dropdown>}
</div>
<div>
<Button
tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')}
disabled={processing}
icon={isTracking
? <SubscribedIcon color='text-primary' size={5}/>
: <NotSubscribedIcon color='text-controls' size={5}/>
}
dimensions='h-full w-fit pr-2'
borderClass=''
style={{outlineColor: 'transparent'}}
dense
onClick={onToggleSubscribe}
tabIndex={-1}
/>
</div>
</div> </div>
); <div ref={editMenu.ref}>
<Button
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
borderClass=''
dimensions='h-full w-fit'
style={{outlineColor: 'transparent'}}
icon={<EditIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense
onClick={editMenu.toggle}
tabIndex={-1}
/>
{ editMenu.isActive &&
<Dropdown>
<DropdownButton
disabled={!user || !isClaimable}
onClick={!isOwned ? handleClaimOwner : undefined}
tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''}
>
<div className='flex items-center gap-2 pl-1'>
<span><OwnerIcon size={5} color={isOwned ? 'text-success' : 'text-controls'} /></span>
<p>
{ isOwned && <b>Владелец схемы</b> }
{ !isOwned && <b>Стать владельцем</b> }
</p>
</div>
</DropdownButton>
{(isOwned || user?.is_staff) &&
<DropdownCheckbox
value={isReadonly}
setValue={toggleReadonly}
label='Я — читатель!'
tooltip='Режим чтения'
/>}
{user?.is_staff &&
<DropdownCheckbox
value={isForceAdmin}
setValue={toggleForceAdmin}
label='Я — администратор!'
tooltip='Режим редактирования для администраторов'
/>}
</Dropdown>}
</div>
<div>
<Button
tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')}
disabled={processing}
icon={isTracking
? <SubscribedIcon color='text-primary' size={5}/>
: <NotSubscribedIcon color='text-controls' size={5}/>
}
dimensions='h-full w-fit pr-2'
borderClass=''
style={{outlineColor: 'transparent'}}
dense
onClick={onToggleSubscribe}
tabIndex={-1}
/>
</div>
</div>);
} }
export default RSTabsMenu export default RSTabsMenu;

View File

@ -17,7 +17,7 @@ function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
onClick={() => onInsert(token)} onClick={() => onInsert(token)}
title={describeToken(token)} title={describeToken(token)}
tabIndex={-1} tabIndex={-1}
className={`px-1 cursor-pointer disabled:cursor-default border rounded-none h-6 ${width} clr-outline clr-hover clr-btn-clear`} className={`px-1 cursor-pointer disabled:cursor-default border rounded-none h-6 ${width} outline-none clr-hover clr-btn-clear`}
> >
{label && <span className='whitespace-nowrap'>{label}</span>} {label && <span className='whitespace-nowrap'>{label}</span>}
</button> </button>

View File

@ -28,7 +28,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
return ( return (
<div title={describeExpressionStatus(status)} <div title={describeExpressionStatus(status)}
className='inline-flex items-center justify-center w-full h-full text-sm font-semibold align-middle border select-none small-caps' className='inline-flex items-center justify-center w-full h-full text-sm font-semibold align-middle border rounded-none select-none small-caps'
style={{backgroundColor: colorbgCstStatus(status, colors)}} style={{backgroundColor: colorbgCstStatus(status, colors)}}
> >
{labelExpressionStatus(status)} {labelExpressionStatus(status)}

View File

@ -244,6 +244,7 @@ export function labelHelpTopic(topic: HelpTopic): string {
case HelpTopic.CSTLIST: return '- список конституент'; case HelpTopic.CSTLIST: return '- список конституент';
case HelpTopic.CONSTITUENTA: return '- конституента'; case HelpTopic.CONSTITUENTA: return '- конституента';
case HelpTopic.GRAPH_TERM: return '- граф термов'; case HelpTopic.GRAPH_TERM: return '- граф термов';
case HelpTopic.RSTEMPLATES: return '- Банк выражений';
case HelpTopic.RSLANG: return 'Экспликация'; case HelpTopic.RSLANG: return 'Экспликация';
case HelpTopic.TERM_CONTROL: return 'Терминологизация'; case HelpTopic.TERM_CONTROL: return 'Терминологизация';
case HelpTopic.EXTEOR: return 'Экстеор'; case HelpTopic.EXTEOR: return 'Экстеор';
@ -259,6 +260,7 @@ export function describeHelpTopic(topic: HelpTopic): string {
case HelpTopic.CSTLIST: return 'Описание работы со списком конституентт'; case HelpTopic.CSTLIST: return 'Описание работы со списком конституентт';
case HelpTopic.CONSTITUENTA: return 'Описание редактирования конституенты'; case HelpTopic.CONSTITUENTA: return 'Описание редактирования конституенты';
case HelpTopic.GRAPH_TERM: return 'Описание работы с графом термов схемы'; case HelpTopic.GRAPH_TERM: return 'Описание работы с графом термов схемы';
case HelpTopic.RSTEMPLATES: return 'Описание работы с Банком выражений>';
case HelpTopic.RSLANG: return 'Справка по языку родов структур и экспликации'; case HelpTopic.RSLANG: return 'Справка по языку родов структур и экспликации';
case HelpTopic.TERM_CONTROL: return 'Справка по контролю терминов и текстовым отсылкам'; case HelpTopic.TERM_CONTROL: return 'Справка по контролю терминов и текстовым отсылкам';
case HelpTopic.EXTEOR: return 'Справка по программе для экспликации "Экстеор" для Windows'; case HelpTopic.EXTEOR: return 'Справка по программе для экспликации "Экстеор" для Windows';