Update Constituenta editor UI

This commit is contained in:
IRBorisov 2023-10-23 18:22:55 +03:00
parent ae9964de87
commit 9cd682b19f
26 changed files with 302 additions and 229 deletions

View File

@ -7,18 +7,19 @@ extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'className' | 'title'>
label?: string label?: string
tooltip?: string tooltip?: string
dimensions?: string dimensions?: string
dense?: boolean
colorClass?: string colorClass?: string
} }
function TextArea({ function TextArea({
id, label, required, tooltip, id, label, required, tooltip, dense,
dimensions = 'w-full', dimensions = 'w-full',
colorClass = 'clr-input', colorClass = 'clr-input',
rows = 4, rows = 4,
...props ...props
}: TextAreaProps) { }: TextAreaProps) {
return ( return (
<div className='flex flex-col items-start gap-2'> <div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label && {label &&
<Label <Label
text={label} text={label}
@ -26,7 +27,7 @@ function TextArea({
/>} />}
<textarea id={id} <textarea id={id}
title={tooltip} title={tooltip}
className={`px-3 py-2 leading-tight border shadow clr-outline ${colorClass} ${dimensions}`} className={`px-3 py-2 leading-tight border clr-outline ${colorClass} ${dense ? 'w-full' : dimensions}`}
rows={rows} rows={rows}
required={required} required={required}
{...props} {...props}

View File

@ -7,19 +7,19 @@ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'>
tooltip?: string tooltip?: string
dimensions?: string dimensions?: string
colorClass?: string colorClass?: string
singleRow?: boolean dense?: boolean
noBorder?: boolean noBorder?: boolean
} }
function TextInput({ function TextInput({
id, required, label, singleRow, tooltip, noBorder, id, required, label, dense, tooltip, noBorder,
dimensions = 'w-full', dimensions = 'w-full',
colorClass = 'clr-input', colorClass = 'clr-input',
...props ...props
}: TextInputProps) { }: TextInputProps) {
const borderClass = noBorder ? '': 'border shadow'; const borderClass = noBorder ? '': 'border';
return ( return (
<div className={`flex ${singleRow ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}> <div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label && {label &&
<Label <Label
text={label} text={label}
@ -27,7 +27,7 @@ function TextInput({
/>} />}
<input id={id} <input id={id}
title={tooltip} title={tooltip}
className={`px-3 py-2 leading-tight ${borderClass} truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? 'w-full' : dimensions}`} className={`px-3 py-2 leading-tight ${borderClass} truncate hover:text-clip clr-outline ${colorClass} ${dense ? 'w-full' : dimensions}`}
required={required} required={required}
{...props} {...props}
/> />

View File

@ -1,4 +1,4 @@
import { EducationIcon, EyeIcon, GroupIcon } from '../Icons'; import { EducationIcon, GroupIcon,SubscribedIcon } from '../Icons';
function HelpLibrary() { function HelpLibrary() {
return ( return (
@ -9,7 +9,7 @@ function HelpLibrary() {
<p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p> <p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p>
<p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p> <p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<EyeIcon size={4}/> <SubscribedIcon size={4}/>
<p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p> <p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p>
</div> </div>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>

View File

@ -47,26 +47,49 @@ export function BellIcon(props: IconProps) {
export function EyeIcon(props: IconProps) { export function EyeIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 1024 1024' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z' /> <path d='M14 12c-1.095 0-2-.905-2-2 0-.354.103-.683.268-.973C12.178 9.02 12.092 9 12 9a3.02 3.02 0 00-3 3c0 1.642 1.358 3 3 3 1.641 0 3-1.358 3-3 0-.092-.02-.178-.027-.268-.29.165-.619.268-.973.268z' />
<path d='M12 5c-7.633 0-9.927 6.617-9.948 6.684L1.946 12l.105.316C2.073 12.383 4.367 19 12 19s9.927-6.617 9.948-6.684l.106-.316-.105-.316C21.927 11.617 19.633 5 12 5zm0 12c-5.351 0-7.424-3.846-7.926-5C4.578 10.842 6.652 7 12 7c5.351 0 7.424 3.846 7.926 5-.504 1.158-2.578 5-7.926 5z' />
</IconSVG> </IconSVG>
); );
} }
export function EyeOffIcon(props: IconProps) { export function EyeOffIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='0 0 1024 1024' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z'/> <path d='M12 19c.946 0 1.81-.103 2.598-.281l-1.757-1.757c-.273.021-.55.038-.841.038-5.351 0-7.424-3.846-7.926-5a8.642 8.642 0 011.508-2.297L4.184 8.305c-1.538 1.667-2.121 3.346-2.132 3.379a.994.994 0 000 .633C2.073 12.383 4.367 19 12 19zm0-14c-1.837 0-3.346.396-4.604.981L3.707 2.293 2.293 3.707l18 18 1.414-1.414-3.319-3.319c2.614-1.951 3.547-4.615 3.561-4.657a.994.994 0 000-.633C21.927 11.617 19.633 5 12 5zm4.972 10.558l-2.28-2.28c.19-.39.308-.819.308-1.278 0-1.641-1.359-3-3-3-.459 0-.888.118-1.277.309L8.915 7.501A9.26 9.26 0 0112 7c5.351 0 7.424 3.846 7.926 5-.302.692-1.166 2.342-2.954 3.558z'/>
<path d='M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z' />
</IconSVG> </IconSVG>
); );
} }
export function PenIcon(props: IconProps) { export function SubscribedIcon(props: IconProps) {
return ( return (
<IconSVG viewbox='-3 -3 21 21' {...props}> <IconSVG viewbox='0 0 24 24' {...props}>
<path d='M15.502 1.94a.5.5 0 010 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 01.707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 00-.121.196l-.805 2.414a.25.25 0 00.316.316l2.414-.805a.5.5 0 00.196-.12l6.813-6.814z' /> <path d='M19 13.586V10c0-3.217-2.185-5.927-5.145-6.742C13.562 2.52 12.846 2 12 2s-1.562.52-1.855 1.258C7.185 4.074 5 6.783 5 10v3.586l-1.707 1.707A.996.996 0 003 16v2a1 1 0 001 1h16a1 1 0 001-1v-2a.996.996 0 00-.293-.707L19 13.586zM19 17H5v-.586l1.707-1.707A.996.996 0 007 14v-4c0-2.757 2.243-5 5-5s5 2.243 5 5v4c0 .266.105.52.293.707L19 16.414V17zm-7 5a2.98 2.98 0 002.818-2H9.182A2.98 2.98 0 0012 22z' />
<path d='M1 13.5A1.5 1.5 0 002.5 15h11a1.5 1.5 0 001.5-1.5v-6a.5.5 0 00-1 0v6a.5.5 0 01-.5.5h-11a.5.5 0 01-.5-.5v-11a.5.5 0 01.5-.5H9a.5.5 0 000-1H2.5A1.5 1.5 0 001 2.5v11z' /> </IconSVG>
);
}
export function NotSubscribedIcon(props: IconProps) {
return (
<IconSVG viewbox='0 0 24 24' {...props}>
<path d='M12 22a2.98 2.98 0 002.818-2H9.182A2.98 2.98 0 0012 22zm9-4v-2a.996.996 0 00-.293-.707L19 13.586V10c0-3.217-2.185-5.927-5.145-6.742C13.562 2.52 12.846 2 12 2s-1.562.52-1.855 1.258c-1.323.364-2.463 1.128-3.346 2.127L3.707 2.293 2.293 3.707l18 18 1.414-1.414-1.362-1.362A.993.993 0 0021 18zM12 5c2.757 0 5 2.243 5 5v4c0 .266.105.52.293.707L19 16.414V17h-.586L8.207 6.793C9.12 5.705 10.471 5 12 5zm-5.293 9.707A.996.996 0 007 14v-2.879L5.068 9.189C5.037 9.457 5 9.724 5 10v3.586l-1.707 1.707A.996.996 0 003 16v2a1 1 0 001 1h10.879l-2-2H5v-.586l1.707-1.707z'/>
</IconSVG>
);
}
export function ASTNetworkIcon(props: IconProps) {
return (
<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'/>
</IconSVG>
);
}
export function EditIcon(props: IconProps) {
return (
<IconSVG viewbox='0 0 24 24' {...props}>
<path d='M6.3 12.3l10-10a1 1 0 011.4 0l4 4a1 1 0 010 1.4l-10 10a1 1 0 01-.7.3H7a1 1 0 01-1-1v-4a1 1 0 01.3-.7zM8 16h2.59l9-9L17 4.41l-9 9V16zm10-2a1 1 0 012 0v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6c0-1.1.9-2 2-2h6a1 1 0 010 2H4v14h14v-6z' />
</IconSVG> </IconSVG>
); );
} }

View File

@ -258,7 +258,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
<TextInput id='offset' type='number' <TextInput id='offset' type='number'
label='Смещение' label='Смещение'
dimensions='max-w-[10rem]' dimensions='max-w-[10rem]'
singleRow dense
value={offset} value={offset}
onChange={event => setOffset(event.target.valueAsNumber)} onChange={event => setOffset(event.target.valueAsNumber)}
/> />
@ -266,7 +266,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
Основная ссылка: Основная ссылка:
</div> </div>
<TextInput <TextInput
singleRow dense
disabled disabled
noBorder noBorder
value={mainLink} value={mainLink}
@ -319,7 +319,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
label='Отсылаемая конституента' label='Отсылаемая конституента'
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap' dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
placeholder='Имя' placeholder='Имя'
singleRow dense
value={alias} value={alias}
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}
/> />
@ -328,7 +328,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
Термин: Термин:
</div> </div>
<TextInput <TextInput
singleRow dense
disabled disabled
noBorder noBorder
value={term} value={term}

View File

@ -30,7 +30,7 @@ function checkTypeConsistency(type: CstType, typification: string, args: IFuncti
} }
function adjustResults(parse: IExpressionParse, emptyExpression: boolean, cstType: CstType) { function adjustResults(parse: IExpressionParse, emptyExpression: boolean, cstType: CstType) {
if (!parse.parseResult && parse.errors.length > 0 && parse.errors[0].errorType !== RSErrorType.syntax) { if (!parse.parseResult && parse.errors.length > 0) {
return; return;
} }
if (cstType === CstType.BASE || cstType === CstType.CONSTANT) { if (cstType === CstType.BASE || cstType === CstType.CONSTANT) {

View File

@ -1,16 +1,19 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
function getStorageValue<ValueType>(key: string, defaultValue: ValueType) { function useLocalStorage<ValueType>(
const saved = localStorage.getItem(key); key: string,
const initial = saved ? JSON.parse(saved) as ValueType : undefined; defaultValue: ValueType | (() => ValueType)
return initial || defaultValue; ) {
} const [value, setValue] = useState<ValueType>(
() => {
const useLocalStorage = const loadedJson = localStorage.getItem(key);
<ValueType>(key: string, defaultValue: ValueType): if (loadedJson != null) {
[ValueType, React.Dispatch<React.SetStateAction<ValueType>>] => { return JSON.parse(loadedJson) as ValueType;
const [value, setValue] = useState<ValueType>(() => { } else if (typeof defaultValue === 'function') {
return getStorageValue(key, defaultValue); return (defaultValue as () => ValueType)();
} else {
return defaultValue;
}
}); });
useEffect(() => { useEffect(() => {
@ -21,7 +24,7 @@ const useLocalStorage =
} }
}, [key, value]); }, [key, value]);
return [value, setValue]; return [value, setValue] as [ValueType, typeof setValue];
}; }
export default useLocalStorage; export default useLocalStorage;

View File

@ -315,7 +315,7 @@
.dark & { .dark & {
border-color: var(--cd-bg-40); border-color: var(--cd-bg-40);
} }
@apply border shadow rounded px-1 @apply border rounded px-1
} }
.cm-editor.cm-focused { .cm-editor.cm-focused {
border-color: var(--cl-bg-40); border-color: var(--cl-bg-40);
@ -324,7 +324,7 @@
border-color: var(--cd-bg-40); border-color: var(--cd-bg-40);
outline-color: var(--cd-prim-bg-100); outline-color: var(--cd-prim-bg-100);
} }
@apply border shadow rounded outline-2 outline @apply border rounded outline-2 outline
} }
.rdt_TableCell{ .rdt_TableCell{

View File

@ -2,12 +2,6 @@
import { IConstituenta, IRSForm } from './rsform' import { IConstituenta, IRSForm } from './rsform'
// Constituenta edit mode
export enum EditMode {
TEXT = 'text',
RSLANG = 'rslang'
}
// Dependency mode for schema analysis // Dependency mode for schema analysis
export enum DependencyMode { export enum DependencyMode {
ALL = 0, ALL = 0,

View File

@ -108,7 +108,7 @@ function CreateRSFormPage() {
onChange={event => setTitle(event.target.value)} onChange={event => setTitle(event.target.value)}
/> />
<TextInput id='alias' label='Сокращение' type='text' <TextInput id='alias' label='Сокращение' type='text'
singleRow dense
required={!file} required={!file}
value={alias} value={alias}
placeholder={file && 'Загрузить из файла'} placeholder={file && 'Загрузить из файла'}

View File

@ -5,7 +5,7 @@ import ConceptTooltip from '../../components/Common/ConceptTooltip';
import TextURL from '../../components/Common/TextURL'; import TextURL from '../../components/Common/TextURL';
import DataTable, { createColumnHelper } from '../../components/DataTable'; import DataTable, { createColumnHelper } from '../../components/DataTable';
import HelpLibrary from '../../components/Help/HelpLibrary'; import HelpLibrary from '../../components/Help/HelpLibrary';
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons'; import { EducationIcon, GroupIcon, HelpIcon,SubscribedIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useConceptNavigation } from '../../context/NagivationContext'; import { useConceptNavigation } from '../../context/NagivationContext';
import { useUsers } from '../../context/UsersContext'; import { useUsers } from '../../context/UsersContext';
@ -45,7 +45,7 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
className='flex items-center justify-start gap-1 min-w-[2.75rem]' className='flex items-center justify-start gap-1 min-w-[2.75rem]'
id={`${prefixes.library_list}${item.id}`} id={`${prefixes.library_list}${item.id}`}
> >
{user && user.subscriptions.includes(item.id) && <p title='Отслеживаемая'><EyeIcon size={3}/></p>} {user && user.subscriptions.includes(item.id) && <p title='Отслеживаемая'><SubscribedIcon size={3}/></p>}
{item.is_common && <p title='Общедоступная'><GroupIcon size={3}/></p>} {item.is_common && <p title='Общедоступная'><GroupIcon size={3}/></p>}
{item.is_canonical && <p title='Неизменная'><EducationIcon size={3}/></p>} {item.is_canonical && <p title='Неизменная'><EducationIcon size={3}/></p>}
</div> </div>

View File

@ -72,6 +72,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={validated} canSubmit={validated}
onSubmit={handleSubmit} onSubmit={handleSubmit}
submitText='Создать'
> >
<div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch gap-3'> <div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch gap-3'>
<div className='flex justify-center w-full gap-6'> <div className='flex justify-center w-full gap-6'>
@ -83,7 +84,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
onChange={data => setSelectedType(data?.value ?? CstType.BASE)} onChange={data => setSelectedType(data?.value ?? CstType.BASE)}
/> />
<TextInput id='alias' label='Имя' <TextInput id='alias' label='Имя'
singleRow dense
dimensions='w-[7rem]' dimensions='w-[7rem]'
value={alias} value={alias}
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}

View File

@ -78,7 +78,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
/> />
<div> <div>
<TextInput id='alias' label='Имя' <TextInput id='alias' label='Имя'
singleRow dense
dimensions='w-[7rem]' dimensions='w-[7rem]'
value={alias} value={alias}
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}

View File

@ -6,11 +6,10 @@ import MiniButton from '../../components/Common/MiniButton';
import SubmitButton from '../../components/Common/SubmitButton'; import SubmitButton from '../../components/Common/SubmitButton';
import TextArea from '../../components/Common/TextArea'; import TextArea from '../../components/Common/TextArea';
import HelpConstituenta from '../../components/Help/HelpConstituenta'; import HelpConstituenta from '../../components/Help/HelpConstituenta';
import { CloneIcon, DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons'; import { CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import RefsInput from '../../components/RefsInput'; import RefsInput from '../../components/RefsInput';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import useWindowSize from '../../hooks/useWindowSize'; import useWindowSize from '../../hooks/useWindowSize';
import { EditMode } from '../../models/miscelanious';
import { CstType, IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData } from '../../models/rsform'; import { CstType, IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData } from '../../models/rsform';
import { SyntaxTree } from '../../models/rslang'; import { SyntaxTree } from '../../models/rslang';
import { labelCstTypification } from '../../utils/labels'; import { labelCstTypification } from '../../utils/labels';
@ -20,7 +19,7 @@ import ViewSideConstituents from './elements/ViewSideConstituents';
// Max height of content for left enditor pane // Max height of content for left enditor pane
const UNFOLDED_HEIGHT = '59.1rem'; const UNFOLDED_HEIGHT = '59.1rem';
const SIDELIST_HIDE_THRESHOLD = 1000; const SIDELIST_HIDE_THRESHOLD = 1100;
interface EditorConstituentaProps { interface EditorConstituentaProps {
activeID?: number activeID?: number
@ -41,8 +40,6 @@ function EditorConstituenta({
}: EditorConstituentaProps) { }: EditorConstituentaProps) {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { schema, processing, isEditable, cstUpdate } = useRSForm(); const { schema, processing, isEditable, cstUpdate } = useRSForm();
const [editMode, setEditMode] = useState(EditMode.TEXT);
const [alias, setAlias] = useState(''); const [alias, setAlias] = useState('');
const [term, setTerm] = useState(''); const [term, setTerm] = useState('');
@ -153,7 +150,7 @@ function EditorConstituenta({
return ( return (
<div className='flex max-w-[1500px] gap-2'> <div className='flex max-w-[1500px] gap-2'>
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-[50rem] px-4 py-2'> <form onSubmit={handleSubmit} className='min-w-[50rem] max-w-[50rem] px-4 py-1'>
<div className='relative w-full'> <div className='relative w-full'>
<div className='absolute top-0 right-0 flex items-start justify-between w-full'> <div className='absolute top-0 right-0 flex items-start justify-between w-full'>
{activeCst && {activeCst &&
@ -163,7 +160,7 @@ function EditorConstituenta({
dimensions='w-fit ml-[3.2rem] pt-[0.3rem]' dimensions='w-fit ml-[3.2rem] pt-[0.3rem]'
noHover noHover
onClick={onEditTerm} onClick={onEditTerm}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />} icon={<EditIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>} />}
<div className='flex items-center justify-center w-full pl-[4rem]'> <div className='flex items-center justify-center w-full pl-[4rem]'>
<div className='font-semibold pointer-events-none w-fit'> <div className='font-semibold pointer-events-none w-fit'>
@ -175,7 +172,7 @@ function EditorConstituenta({
disabled={!isEnabled} disabled={!isEnabled}
noHover noHover
onClick={handleRename} onClick={handleRename}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />} icon={<EditIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/> />
</div> </div>
<div className='flex items-center justify-end'> <div className='flex items-center justify-end'>
@ -215,16 +212,16 @@ function EditorConstituenta({
<div className='flex flex-col gap-2 mt-1'> <div className='flex flex-col gap-2 mt-1'>
<RefsInput id='term' label='Термин' <RefsInput id='term' label='Термин'
placeholder='Обозначение, используемое в текстовых определениях данной схемы' placeholder='Обозначение, используемое в текстовых определениях данной схемы'
height='3.5rem' height='2.1rem'
items={schema?.items} items={schema?.items}
value={term} value={term}
initialValue={activeCst?.term_raw ?? ''} initialValue={activeCst?.term_raw ?? ''}
resolved={activeCst?.term_resolved ?? ''} resolved={activeCst?.term_resolved ?? ''}
editable={isEnabled} editable={isEnabled}
onChange={newValue => setTerm(newValue)} onChange={newValue => setTerm(newValue)}
onFocus={() => setEditMode(EditMode.TEXT)}
/> />
<TextArea id='typification' label='Типизация' <TextArea id='typification' label='Типизация'
dense
rows={1} rows={1}
value={typification} value={typification}
colorClass='clr-app' colorClass='clr-app'
@ -235,31 +232,27 @@ function EditorConstituenta({
placeholder='Родоструктурное выражение, задающее формальное определение' placeholder='Родоструктурное выражение, задающее формальное определение'
value={expression} value={expression}
disabled={!isEnabled} disabled={!isEnabled}
isActive={editMode === EditMode.RSLANG}
toggleEditMode={() => setEditMode(EditMode.RSLANG)}
onShowAST={onShowAST} onShowAST={onShowAST}
onChange={newValue => setExpression(newValue)} onChange={newValue => setExpression(newValue)}
setTypification={setTypification} setTypification={setTypification}
/> />
<RefsInput id='definition' label='Текстовое определение' <RefsInput id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения' placeholder='Лингвистическая интерпретация формального выражения'
height='6.3rem' height='4.8rem'
items={schema?.items} items={schema?.items}
value={textDefinition} value={textDefinition}
initialValue={activeCst?.definition_raw ?? ''} initialValue={activeCst?.definition_raw ?? ''}
resolved={activeCst?.definition_resolved ?? ''} resolved={activeCst?.definition_resolved ?? ''}
editable={isEnabled} editable={isEnabled}
onChange={newValue => setTextDefinition(newValue)} onChange={newValue => setTextDefinition(newValue)}
onFocus={() => setEditMode(EditMode.TEXT)}
/> />
<TextArea id='convention' label='Конвенция / Комментарий' <TextArea id='convention' label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию' placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={4} rows={2}
value={convention} value={convention}
disabled={!isEnabled} disabled={!isEnabled}
spellCheck spellCheck
onChange={event => setConvention(event.target.value)} onChange={event => setConvention(event.target.value)}
onFocus={() => setEditMode(EditMode.TEXT)}
/> />
<div className='flex justify-center w-full mt-2'> <div className='flex justify-center w-full mt-2'>
<SubmitButton <SubmitButton
@ -271,7 +264,7 @@ function EditorConstituenta({
</div> </div>
</form> </form>
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD && {(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
<div className='w-full mt-10 border h-fit'> <div className='w-full mt-[2.25rem] border h-fit'>
<ViewSideConstituents <ViewSideConstituents
expression={expression} expression={expression}
baseHeight={UNFOLDED_HEIGHT} baseHeight={UNFOLDED_HEIGHT}

View File

@ -1,39 +1,39 @@
import { ReactCodeMirrorRef } from '@uiw/react-codemirror'; import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import { ConceptLoader } from '../../components/Common/ConceptLoader'; import { ConceptLoader } from '../../components/Common/ConceptLoader';
import MiniButton from '../../components/Common/MiniButton';
import { ASTNetworkIcon } from '../../components/Icons';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import { RSTextWrapper } from '../../components/RSInput/textEditing'; import { RSTextWrapper } from '../../components/RSInput/textEditing';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import useCheckExpression from '../../hooks/useCheckExpression'; import useCheckExpression from '../../hooks/useCheckExpression';
import { IConstituenta } from '../../models/rsform'; import { IConstituenta } from '../../models/rsform';
import { IRSErrorDescription, SyntaxTree } from '../../models/rslang'; import { IExpressionParse, IRSErrorDescription, SyntaxTree } from '../../models/rslang';
import { TokenID } from '../../models/rslang'; import { TokenID } from '../../models/rslang';
import { labelTypification } from '../../utils/labels'; import { labelTypification } from '../../utils/labels';
import { getCstExpressionPrefix } from '../../utils/misc'; import { getCstExpressionPrefix } from '../../utils/misc';
import ParsingResult from './elements/ParsingResult'; import ParsingResult from './elements/ParsingResult';
import RSLocalButton from './elements/RSLocalButton'; import RSEditorControls from './elements/RSEditorControls';
import RSTokenButton from './elements/RSTokenButton';
import StatusBar from './elements/StatusBar'; import StatusBar from './elements/StatusBar';
interface EditorRSExpressionProps { interface EditorRSExpressionProps {
id: string id: string
activeCst?: IConstituenta activeCst?: IConstituenta
label: string label: string
isActive: boolean
disabled?: boolean disabled?: boolean
placeholder?: string placeholder?: string
onShowAST: (expression: string, ast: SyntaxTree) => void onShowAST: (expression: string, ast: SyntaxTree) => void
toggleEditMode: () => void
setTypification: (typificaiton: string) => void setTypification: (typificaiton: string) => void
value: string value: string
onChange: (newValue: string) => void onChange: (newValue: string) => void
} }
function EditorRSExpression({ function EditorRSExpression({
activeCst, disabled, isActive, value, onShowAST, activeCst, disabled, value, onShowAST,
toggleEditMode, setTypification, onChange, ...props setTypification, onChange, ...props
}: EditorRSExpressionProps) { }: EditorRSExpressionProps) {
const { schema } = useRSForm(); const { schema } = useRSForm();
@ -46,16 +46,12 @@ function EditorRSExpression({
resetParse(); resetParse();
}, [activeCst, resetParse]); }, [activeCst, resetParse]);
function handleFocusIn() {
toggleEditMode()
}
function handleChange(newvalue: string) { function handleChange(newvalue: string) {
onChange(newvalue); onChange(newvalue);
setIsModified(newvalue !== activeCst?.definition_formal); setIsModified(newvalue !== activeCst?.definition_formal);
} }
function handleCheckExpression() { function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
if (!activeCst) { if (!activeCst) {
return; return;
} }
@ -73,6 +69,7 @@ function EditorRSExpression({
resultType: parse.typification, resultType: parse.typification,
args: parse.args args: parse.args
})); }));
if (callback) callback(parse);
}); });
} }
@ -107,132 +104,70 @@ function EditorRSExpression({
setIsModified(true); setIsModified(true);
}, []); }, []);
const EditButtons = useMemo(() => { function handleShowAST() {
return ( handleCheckExpression(
<div className='flex items-center justify-between w-full'> (parse) => {
<div className='text-sm w-fit'> if (!parse.astText) {
<div className='flex justify-start'> toast.error('Невозможно построить дерево разбора');
<RSTokenButton token={TokenID.NT_DECLARATIVE_EXPR} onInsert={handleEdit}/> } else {
<RSTokenButton token={TokenID.NT_IMPERATIVE_EXPR} onInsert={handleEdit}/> onShowAST(getCstExpressionPrefix(activeCst!) + value, parse.ast);
<RSTokenButton token={TokenID.NT_RECURSIVE_FULL} onInsert={handleEdit}/> }
<RSTokenButton token={TokenID.BIGPR} onInsert={handleEdit}/> });
<RSTokenButton token={TokenID.SMALLPR} onInsert={handleEdit}/> }
<RSTokenButton token={TokenID.FILTER} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.REDUCE} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.CARD} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.BOOL} onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSTokenButton token={TokenID.BOOLEAN} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.PUNC_PL} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.INTERSECTION} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.LIT_EMPTYSET} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.FORALL} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.NOT} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.IN} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.SUBSET_OR_EQ} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.AND} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.IMPLICATION} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.SET_MINUS} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.PUNC_ITERATE} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.SUBSET} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.DEBOOL} onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSTokenButton token={TokenID.DECART} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.PUNC_SL} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.UNION} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.LIT_INTSET} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.EXISTS} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.NOTEQUAL} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.NOTIN} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.NOTSUBSET} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.OR} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.EQUIVALENT} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.SYMMINUS} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.PUNC_ASSIGN} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.EQUAL} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.GREATER_OR_EQ} onInsert={handleEdit}/>
<RSTokenButton token={TokenID.LESSER_OR_EQ} onInsert={handleEdit}/>
</div>
</div>
<div className='text-sm w-fit'>
<div className='flex justify-start'>
<RSLocalButton text='μ' tooltip='q' onInsert={handleEdit}/>
<RSLocalButton text='ω' tooltip='w' onInsert={handleEdit}/>
<RSLocalButton text='ε' tooltip='e' onInsert={handleEdit}/>
<RSLocalButton text='ρ' tooltip='r' onInsert={handleEdit}/>
<RSLocalButton text='τ' tooltip='t' onInsert={handleEdit}/>
<RSLocalButton text='π' tooltip='y' onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSLocalButton text='α' tooltip='a' onInsert={handleEdit}/>
<RSLocalButton text='σ' tooltip='s' onInsert={handleEdit}/>
<RSLocalButton text='δ' tooltip='d' onInsert={handleEdit}/>
<RSLocalButton text='φ' tooltip='f' onInsert={handleEdit}/>
<RSLocalButton text='γ' tooltip='g' onInsert={handleEdit}/>
<RSLocalButton text='λ' tooltip='h' onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSLocalButton text='ζ' tooltip='z' onInsert={handleEdit}/>
<RSLocalButton text='ξ' tooltip='x' onInsert={handleEdit}/>
<RSLocalButton text='ψ' tooltip='c' onInsert={handleEdit}/>
<RSLocalButton text='θ' tooltip='v' onInsert={handleEdit}/>
<RSLocalButton text='β' tooltip='b' onInsert={handleEdit}/>
<RSLocalButton text='η' tooltip='n' onInsert={handleEdit}/>
</div>
</div>
</div>);
}, [handleEdit]);
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.3rem] right-0'> <div className='absolute top-[-0.2rem] left-[10.3rem]'>
<StatusBar <MiniButton
isModified={isModified} tooltip='Дерево разбора выражения'
constituenta={activeCst} noHover
parseData={parseData} onClick={handleShowAST}
/> icon={<ASTNetworkIcon size={5} color='text-primary' />}
</div> />
</div>
</div> </div>
<RSInput innerref={rsInput} <RSInput innerref={rsInput}
height='7.6rem' height='4.8rem'
value={value} value={value}
editable={!disabled} editable={!disabled}
onChange={handleChange} onChange={handleChange}
onFocus={handleFocusIn}
{...props} {...props}
/> />
<div className='flex items-stretch w-full gap-4 py-1 mt-1 justify-stretch'> <RSEditorControls
<div> disabled={disabled}
onEdit={handleEdit}
/>
<div className='w-full mt-1 max-h-[5rem] min-h-[5rem] flex gap-2'>
<div className='flex flex-col gap-1'>
<Button <Button
tooltip='Проверить формальное выражение' tooltip='Проверить формальное выражение'
text='Проверить' text='Проверить'
dimensions='h-full w-fit' dimensions='w-fit h-[3rem]'
colorClass='clr-btn-default' colorClass='clr-btn-default'
onClick={handleCheckExpression} onClick={() => handleCheckExpression()}
/>
<StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/> />
</div> </div>
{isActive && !disabled && EditButtons} <div className='w-full overflow-y-auto text-sm border'>
{ loading && <ConceptLoader size={6} />}
{ !loading && parseData &&
<ParsingResult
data={parseData}
onShowError={onShowError}
/>}
{ !loading && !parseData &&
<input
disabled={true}
className='w-full px-2 py-1 text-base select-none h-fit clr-app'
placeholder='Результаты проверки выражения'
/>}
</div>
</div> </div>
{ (isActive || loading || parseData) &&
<div className='w-full overflow-y-auto border mt-1 max-h-[14rem] min-h-[4.2rem]'>
{ loading && <ConceptLoader size={6} />}
{ !loading && parseData &&
<ParsingResult
data={parseData}
onShowAST={ast => onShowAST(getCstExpressionPrefix(activeCst!) + value, ast)}
onShowError={onShowError}
/>}
{ !loading && !parseData &&
<input
disabled={true}
className='w-full h-full px-2 align-middle select-none clr-app'
placeholder='Результаты проверки выражения'
/>}
</div>}
</div>); </div>);
} }

View File

@ -374,7 +374,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
/>} />}
<div className='relative w-full z-pop'> <div className='relative w-full z-pop'>
<div className='absolute top-0 right-0 flex items-start justify-center w-full'> <div className='absolute right-0 flex items-start justify-center w-full top-1'>
<MiniButton <MiniButton
tooltip='Новая конституента' tooltip='Новая конституента'
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>} icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>}

View File

@ -372,7 +372,7 @@ function RSTabs() {
onSelect={onSelectTab} onSelect={onSelectTab}
defaultFocus={true} defaultFocus={true}
selectedTabClassName='clr-selected' selectedTabClassName='clr-selected'
className='flex flex-col w-full items-center' className='flex flex-col items-center w-full'
> >
<TabList className='flex items-start border-b-2 border-x-2 select-none justify-stretch w-fit clr-controls h-[1.9rem] small-caps font-semibold'> <TabList className='flex items-start border-b-2 border-x-2 select-none justify-stretch w-fit clr-controls h-[1.9rem] small-caps font-semibold'>
<RSTabsMenu <RSTabsMenu
@ -391,11 +391,10 @@ function RSTabs() {
Паспорт схемы Паспорт схемы
</ConceptTab> </ConceptTab>
<ConceptTab <ConceptTab
className='border-r-2 w-fit flex justify-between gap-2' className='flex justify-between gap-2 border-r-2 w-fit'
title={`Всего конституент: ${schema.stats?.count_all ?? 0}\nКоличество ошибок: ${schema.stats?.count_errors ?? 0}`} title={`Всего конституент: ${schema.stats?.count_all ?? 0}\nКоличество ошибок: ${schema.stats?.count_errors ?? 0}`}
> >
<span>Конституенты</span> <span>Конституенты</span>
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>
</ConceptTab> </ConceptTab>
<ConceptTab className='border-r-2 min-w-[5.2rem]'> <ConceptTab className='border-r-2 min-w-[5.2rem]'>
Редактор Редактор

View File

@ -1,21 +1,16 @@
import { IExpressionParse, IRSErrorDescription, SyntaxTree } from '../../../models/rslang'; import { IExpressionParse, IRSErrorDescription } from '../../../models/rslang';
import { describeRSError } from '../../../utils/labels'; import { describeRSError } from '../../../utils/labels';
import { getRSErrorPrefix } from '../../../utils/misc'; import { getRSErrorPrefix } from '../../../utils/misc';
interface ParsingResultProps { interface ParsingResultProps {
data: IExpressionParse data: IExpressionParse
onShowAST: (ast: SyntaxTree) => void
onShowError: (error: IRSErrorDescription) => void onShowError: (error: IRSErrorDescription) => void
} }
function ParsingResult({ data, onShowAST, onShowError }: ParsingResultProps) { function ParsingResult({ data, onShowError }: ParsingResultProps) {
const errorCount = data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0); const errorCount = data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0);
const warningsCount = data.errors.length - errorCount; const warningsCount = data.errors.length - errorCount;
function handleShowAST() {
onShowAST(data.ast);
}
return ( return (
<div className='px-3 py-2'> <div className='px-3 py-2'>
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p> <p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
@ -27,17 +22,6 @@ function ParsingResult({ data, onShowAST, onShowError }: ParsingResultProps) {
</p> </p>
); );
})} })}
{data.astText &&
<p>
<button type='button'
className='font-semibold underline text-primary'
title='отобразить дерево разбора'
onClick={handleShowAST}
tabIndex={-1}
>
Дерево разбора
</button>
</p>}
</div> </div>
) )
} }

View File

@ -0,0 +1,139 @@
import { TokenID } from '../../../models/rslang';
import { prefixes } from '../../../utils/constants';
import RSLocalButton from './RSLocalButton';
import RSTokenButton from './RSTokenButton';
const MAIN_FIRST_ROW: TokenID[] = [
TokenID.NT_DECLARATIVE_EXPR,
TokenID.NT_IMPERATIVE_EXPR,
TokenID.NT_RECURSIVE_FULL,
TokenID.BIGPR,
TokenID.SMALLPR,
TokenID.FILTER,
TokenID.REDUCE,
TokenID.CARD,
TokenID.BOOL
];
const MAIN_SECOND_ROW: TokenID[] = [
TokenID.BOOLEAN,
TokenID.PUNC_PL,
TokenID.INTERSECTION,
TokenID.LIT_EMPTYSET,
TokenID.FORALL,
TokenID.NOT,
TokenID.IN,
TokenID.SUBSET_OR_EQ,
TokenID.AND,
TokenID.IMPLICATION,
TokenID.SET_MINUS,
TokenID.PUNC_ITERATE,
TokenID.SUBSET,
TokenID.DEBOOL
];
const MAIN_THIRD_ROW: TokenID[] = [
TokenID.DECART,
TokenID.PUNC_SL,
TokenID.UNION,
TokenID.LIT_INTSET,
TokenID.EXISTS,
TokenID.NOTEQUAL,
TokenID.NOTIN,
TokenID.NOTSUBSET,
TokenID.OR,
TokenID.EQUIVALENT,
TokenID.SYMMINUS,
TokenID.PUNC_ASSIGN,
TokenID.EQUAL,
TokenID.GREATER_OR_EQ,
TokenID.LESSER_OR_EQ
];
const SECONDARY_FIRST_ROW = [
{text: 'μ', tooltip: 'q'},
{text: 'ω', tooltip: 'w'},
{text: 'ε', tooltip: 'e'},
{text: 'ρ', tooltip: 'r'},
{text: 'τ', tooltip: 't'},
{text: 'π', tooltip: 'y'}
];
const SECONDARY_SECOND_ROW = [
{text: 'α', tooltip: 'a'},
{text: 'σ', tooltip: 's'},
{text: 'δ', tooltip: 'd'},
{text: 'φ', tooltip: 'f'},
{text: 'γ', tooltip: 'g'},
{text: 'λ', tooltip: 'h'}
];
const SECONDARY_THIRD_ROW = [
{text: 'ζ', tooltip: 'z'},
{text: 'ξ', tooltip: 'x'},
{text: 'ψ', tooltip: 'c'},
{text: 'θ', tooltip: 'v'},
{text: 'β', tooltip: 'b'},
{text: 'η', tooltip: 'n'}
];
interface RSEditorControlsProps {
onEdit: (id: TokenID, key?: string) => void
disabled?: boolean
}
function RSEditorControls({ onEdit, disabled }: RSEditorControlsProps) {
return (
<div className='flex items-center justify-between w-full mt-1 text-sm'>
<div className='w-fit'>
<div className='flex justify-start'>
{MAIN_FIRST_ROW.map(
(token) =>
<RSTokenButton key={`${prefixes.rsedit_btn}${token}`}
token={token} onInsert={onEdit} disabled={disabled}
/>)}
</div>
<div className='flex justify-start'>
{MAIN_SECOND_ROW.map(
(token) =>
<RSTokenButton key={`${prefixes.rsedit_btn}${token}`}
token={token} onInsert={onEdit} disabled={disabled}
/>)}
</div>
<div className='flex justify-start'>
{MAIN_THIRD_ROW.map(
(token) =>
<RSTokenButton key={`${prefixes.rsedit_btn}${token}`}
token={token} onInsert={onEdit} disabled={disabled}
/>)}
</div>
</div>
<div className='w-fit'>
<div className='flex justify-start'>
{SECONDARY_FIRST_ROW.map(
({text, tooltip}) =>
<RSLocalButton key={`${prefixes.rsedit_btn}${tooltip}`}
text={text} tooltip={tooltip} onInsert={onEdit} disabled={disabled}
/>)}
</div>
<div className='flex justify-start'>
{SECONDARY_SECOND_ROW.map(
({text, tooltip}) =>
<RSLocalButton key={`${prefixes.rsedit_btn}${tooltip}`}
text={text} tooltip={tooltip} onInsert={onEdit} disabled={disabled}
/>)}
</div>
<div className='flex justify-start'>
{SECONDARY_THIRD_ROW.map(
({text, tooltip}) =>
<RSLocalButton key={`${prefixes.rsedit_btn}${tooltip}`}
text={text} tooltip={tooltip} onInsert={onEdit} disabled={disabled}
/>)}
</div>
</div>
</div>);
}
export default RSEditorControls;

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-[1.5rem] h-7 cursor-pointer border rounded-none clr-hover clr-btn-clear' className='w-[2.25rem] h-6 cursor-pointer disabled:cursor-default border rounded-none clr-hover clr-btn-clear'
> >
{text} {text}
</button> </button>

View File

@ -5,8 +5,8 @@ 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, EyeIcon, EyeOffIcon, CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
MenuIcon, OwnerIcon, PenIcon, ShareIcon, SmallPlusIcon, UploadIcon OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon
} from '../../../components/Icons'; } from '../../../components/Icons';
import { useAuth } from '../../../context/AuthContext'; import { useAuth } from '../../../context/AuthContext';
import { useRSForm } from '../../../context/RSFormContext'; import { useRSForm } from '../../../context/RSFormContext';
@ -76,7 +76,7 @@ function RSTabsMenu({
tooltip='Действия' tooltip='Действия'
icon={<MenuIcon color='text-controls' size={5}/>} icon={<MenuIcon color='text-controls' size={5}/>}
borderClass='' borderClass=''
dimensions='h-full w-fit' dimensions='h-full w-fit pl-2'
style={{outlineColor: 'transparent'}} style={{outlineColor: 'transparent'}}
dense dense
onClick={schemaMenu.toggle} onClick={schemaMenu.toggle}
@ -128,7 +128,7 @@ function RSTabsMenu({
borderClass='' borderClass=''
dimensions='h-full w-fit' dimensions='h-full w-fit'
style={{outlineColor: 'transparent'}} style={{outlineColor: 'transparent'}}
icon={<PenIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>} icon={<EditIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense dense
onClick={editMenu.toggle} onClick={editMenu.toggle}
tabIndex={-1} tabIndex={-1}
@ -169,10 +169,10 @@ function RSTabsMenu({
tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')} tooltip={'отслеживание: ' + (isTracking ? '[включено]' : '[выключено]')}
disabled={processing} disabled={processing}
icon={isTracking icon={isTracking
? <EyeIcon color='text-primary' size={5}/> ? <SubscribedIcon color='text-primary' size={5}/>
: <EyeOffIcon color='text-controls' size={5}/> : <NotSubscribedIcon color='text-controls' size={5}/>
} }
dimensions='h-full w-fit' dimensions='h-full w-fit pr-2'
borderClass='' borderClass=''
style={{outlineColor: 'transparent'}} style={{outlineColor: 'transparent'}}
dense dense

View File

@ -9,7 +9,7 @@ interface RSTokenButtonProps {
function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) { function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
const label = labelToken(token); const label = labelToken(token);
const width = label.length > 3 ? 'w-[4rem]' : 'w-[2rem]'; const width = label.length > 3 ? 'w-[4.5rem]' : 'w-[2.25rem]';
return ( return (
<button <button
type='button' type='button'
@ -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 border rounded-none h-7 ${width} clr-outline clr-hover clr-btn-clear`} className={`px-1 cursor-pointer disabled:cursor-default border rounded-none h-6 ${width} clr-outline 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='text-sm h-[1.6rem] w-[10rem] font-semibold small-caps inline-flex border items-center select-none justify-center align-middle' className='inline-flex items-center justify-center w-full h-full text-sm font-semibold align-middle border select-none small-caps'
style={{backgroundColor: colorbgCstStatus(status, colors)}} style={{backgroundColor: colorbgCstStatus(status, colors)}}
> >
{labelExpressionStatus(status)} {labelExpressionStatus(status)}

View File

@ -165,7 +165,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
id: 'description', id: 'description',
header: 'Описание', header: 'Описание',
size: 1000, size: 1000,
minSize: 350, minSize: 250,
maxSize: 1000, maxSize: 1000,
cell: props => cell: props =>
<div style={{ <div style={{
@ -211,12 +211,12 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
}, [noNavigation, baseHeight]); }, [noNavigation, baseHeight]);
return (<> return (<>
<div className='sticky top-0 left-0 right-0 flex items-stretch justify-between w-full gap-1 pl-2 border-b clr-input'> <div className='sticky top-0 left-0 right-0 flex items-stretch justify-between gap-1 pl-2 border-b clr-input'>
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'> <div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
<MagnifyingGlassIcon /> <MagnifyingGlassIcon />
</div> </div>
<input type='text' <input type='text'
className='w-[14rem] pr-2 pl-8 py-1 outline-none select-none hover:text-clip clr-input' className='w-full min-w-[6rem] pr-2 pl-8 py-1 outline-none select-none hover:text-clip clr-input'
placeholder='Поиск' placeholder='Поиск'
value={filterText} value={filterText}
onChange={event => setFilterText(event.target.value)} onChange={event => setFilterText(event.target.value)}

View File

@ -3,7 +3,7 @@ import { useMemo, useState } from 'react';
import BackendError from '../../components/BackendError'; import BackendError from '../../components/BackendError';
import { ConceptLoader } from '../../components/Common/ConceptLoader'; import { ConceptLoader } from '../../components/Common/ConceptLoader';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/Common/MiniButton';
import { EyeIcon, EyeOffIcon } from '../../components/Icons'; import { NotSubscribedIcon,SubscribedIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { useUserProfile } from '../../context/UserProfileContext'; import { useUserProfile } from '../../context/UserProfileContext';
@ -35,8 +35,8 @@ function UserTabs() {
<MiniButton <MiniButton
tooltip='Показать/Скрыть список отслеживаний' tooltip='Показать/Скрыть список отслеживаний'
icon={showSubs icon={showSubs
? <EyeIcon color='text-primary' size={5}/> ? <SubscribedIcon color='text-primary' size={5}/>
: <EyeOffIcon color='text-primary' size={5}/> : <NotSubscribedIcon color='text-primary' size={5}/>
} }
onClick={() => setShowSubs(prev => !prev)} onClick={() => setShowSubs(prev => !prev)}
/> />

View File

@ -38,8 +38,9 @@ export const prefixes = {
cst_status_list: 'cst-status-list-', cst_status_list: 'cst-status-list-',
cst_match_mode_list: 'cst-match-mode-list-', cst_match_mode_list: 'cst-match-mode-list-',
cst_source_list: 'cst-source-list-', cst_source_list: 'cst-source-list-',
library_filters_list: 'library-filters-list', library_filters_list: 'library-filters-list-',
topic_list: 'topic-list-', topic_list: 'topic-list-',
library_list: 'library-list-', library_list: 'library-list-',
wordform_list: 'wordform-list' wordform_list: 'wordform-list-',
rsedit_btn: 'rsedit-btn-'
} }