mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Small UI design improvements
This commit is contained in:
parent
b026a57fad
commit
c2e0cfae07
|
@ -6,11 +6,11 @@ interface DropdownProps {
|
||||||
|
|
||||||
function Dropdown({ children, dimensions = 'w-fit', stretchLeft }: DropdownProps) {
|
function Dropdown({ children, dimensions = 'w-fit', stretchLeft }: DropdownProps) {
|
||||||
return (
|
return (
|
||||||
<div className='relative text-sm'>
|
<div className='relative text-sm'>
|
||||||
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} mt-2 z-tooltip flex flex-col items-stretch justify-start origin-top-right border divide-y divide-inherit rounded-md shadow-lg clr-input ${dimensions}`}>
|
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} mt-3 z-modal-tooltip flex flex-col items-stretch justify-start origin-top-right border rounded-md shadow-lg clr-input ${dimensions}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
import useEscapeKey from '../../hooks/useEscapeKey';
|
import useEscapeKey from '../../hooks/useEscapeKey';
|
||||||
|
import { CrossIcon } from '../Icons';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
import MiniButton from './MiniButton';
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
title?: string
|
title?: string
|
||||||
|
@ -35,16 +37,29 @@ function Modal({
|
||||||
if (onSubmit) onSubmit();
|
if (onSubmit) onSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
|
<>
|
||||||
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
|
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
|
||||||
<div ref={ref}
|
<div ref={ref}
|
||||||
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-4 flex flex-col justify-start w-fit max-w-[calc(100vw-2rem)] overflow-x-auto h-fit z-modal clr-app border shadow-md'
|
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 w-fit max-w-[calc(100vw-2rem)] h-fit z-modal clr-app border shadow-md'
|
||||||
>
|
>
|
||||||
{title ? <h1 className='py-2 text-lg select-none'>{title}</h1> : null}
|
<div className='relative'>
|
||||||
<div className='max-h-[calc(100vh-8rem)] overflow-auto px-2'>
|
<div className='absolute right-[-1rem] top-2 text-disabled'>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Закрыть диалоговое окно [ESC]'
|
||||||
|
icon={<CrossIcon size={5}/>}
|
||||||
|
onClick={handleCancel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{title ? <h1 className='my-2 text-lg select-none'>{title}</h1> : null}
|
||||||
|
|
||||||
|
<div className='max-h-[calc(100vh-8rem)] overflow-auto flex flex-col justify-start '>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-center w-full gap-6 py-3 z-modal-controls'>
|
|
||||||
|
<div className='flex justify-center w-full gap-6 my-3 z-modal-controls'>
|
||||||
{!readonly ?
|
{!readonly ?
|
||||||
<Button autoFocus
|
<Button autoFocus
|
||||||
text={submitText}
|
text={submitText}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
function HelpRSTemplates() {
|
function HelpRSTemplates() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='flex flex-col w-full gap-2 pb-2'>
|
||||||
<h1>Банк выражений</h1>
|
<h1>Банк выражений</h1>
|
||||||
<p>Реализовано создание конституент из параметризованных выражений.</p>
|
<p>Портал предоставляет быстрый доступ к часто используемым выражениям с помощью функции создания конституенты из шаблона.</p>
|
||||||
<p>Функционал фильтрации и выбор шаблонов выражений находится в активной разработке.</p>
|
<p>Источником шаблонов является <b>Банк выражений</b>, содержащий параметризованные понятия и утверждения, сгруппированные по разделам.</p>
|
||||||
|
<p>Сначала выбирается шаблон выражения (вкладка Шаблон).</p>
|
||||||
|
<p>Далее для аргументов можно зафиксировать значения, выбрав из конституент текущей схемы или указав выражения (вкладка Аргументы).</p>
|
||||||
|
<p>Значения аргументов будут подставлены в выражение, включая корректировку перечня аргументов.</p>
|
||||||
|
<p>Если значения указаны для всех аргументов, то тип создаваемой конституенты будет автоматически обновлён.</p>
|
||||||
|
<p>На вкладке Конституента можно скорректировать все атрибуты, создаваемой конституенты.</p>
|
||||||
|
<p>Кнопка <b>Создать</b> инициирует добавление выбранной конституенты в схему.</p>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpRSTemplates;
|
export default HelpRSTemplates;
|
23
rsconcept/frontend/src/components/Shared/SelectedCounter.tsx
Normal file
23
rsconcept/frontend/src/components/Shared/SelectedCounter.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
interface SelectedCounterProps {
|
||||||
|
total: number
|
||||||
|
selected: number
|
||||||
|
position?: string
|
||||||
|
hideZero?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectedCounter({
|
||||||
|
total, selected, hideZero,
|
||||||
|
position = 'top-0 left-0',
|
||||||
|
} : SelectedCounterProps) {
|
||||||
|
if (selected === 0 && hideZero) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='relative w-full z-pop'>
|
||||||
|
<div className={`absolute px-2 select-none whitespace-nowrap small-caps clr-app ${position}`}>
|
||||||
|
Выбор {selected} из {total}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectedCounter;
|
|
@ -26,7 +26,7 @@ interface IRSFormContext {
|
||||||
loading: boolean
|
loading: boolean
|
||||||
processing: boolean
|
processing: boolean
|
||||||
|
|
||||||
editorMode: boolean
|
isMutable: boolean
|
||||||
adminMode: boolean
|
adminMode: boolean
|
||||||
isOwned: boolean
|
isOwned: boolean
|
||||||
isClaimable: boolean
|
isClaimable: boolean
|
||||||
|
@ -88,7 +88,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
return (user?.id !== schema?.owner && schema?.is_common && !schema?.is_canonical) ?? false;
|
return (user?.id !== schema?.owner && schema?.is_common && !schema?.is_canonical) ?? false;
|
||||||
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
|
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
|
||||||
|
|
||||||
const editorMode = useMemo(
|
const isMutable = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
!loading && !processing && !isReadonly &&
|
!loading && !processing && !isReadonly &&
|
||||||
|
@ -322,7 +322,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
<RSFormContext.Provider value={{
|
<RSFormContext.Provider value={{
|
||||||
schema,
|
schema,
|
||||||
error, loading, processing,
|
error, loading, processing,
|
||||||
adminMode, isReadonly, isOwned, editorMode,
|
adminMode, isReadonly, isOwned, isMutable,
|
||||||
isClaimable, isTracking,
|
isClaimable, isTracking,
|
||||||
toggleForceAdmin: () => setAdminMode(prev => !prev),
|
toggleForceAdmin: () => setAdminMode(prev => !prev),
|
||||||
toggleReadonly: () => setIsReadonly(prev => !prev),
|
toggleReadonly: () => setIsReadonly(prev => !prev),
|
||||||
|
|
90
rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx
Normal file
90
rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import Checkbox from '../components/Common/Checkbox';
|
||||||
|
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||||
|
import TextArea from '../components/Common/TextArea';
|
||||||
|
import TextInput from '../components/Common/TextInput';
|
||||||
|
import { useLibrary } from '../context/LibraryContext';
|
||||||
|
import { useConceptNavigation } from '../context/NagivationContext';
|
||||||
|
import { ILibraryItem } from '../models/library';
|
||||||
|
import { IRSFormCreateData } from '../models/rsform';
|
||||||
|
import { cloneTitle } from '../utils/misc';
|
||||||
|
|
||||||
|
interface DlgCloneLibraryItemProps
|
||||||
|
extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
base: ILibraryItem
|
||||||
|
}
|
||||||
|
|
||||||
|
function DlgCloneLibraryItem({ hideWindow, base }: DlgCloneLibraryItemProps) {
|
||||||
|
const { navigateTo } = useConceptNavigation();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [alias, setAlias] = useState('');
|
||||||
|
const [comment, setComment] = useState('');
|
||||||
|
const [common, setCommon] = useState(false);
|
||||||
|
const [canonical, setCanonical] = useState(false);
|
||||||
|
|
||||||
|
const { cloneItem } = useLibrary();
|
||||||
|
|
||||||
|
const canSubmit = useMemo(() => (title !== '' && alias !== ''), [title, alias]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (base) {
|
||||||
|
setTitle(cloneTitle(base));
|
||||||
|
setAlias(base.alias);
|
||||||
|
setComment(base.comment);
|
||||||
|
setCommon(base.is_common);
|
||||||
|
setCanonical(false);
|
||||||
|
}
|
||||||
|
}, [base, base?.title, base?.alias, base?.comment, base?.is_common]);
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
const data: IRSFormCreateData = {
|
||||||
|
item_type: base.item_type,
|
||||||
|
title: title,
|
||||||
|
alias: alias,
|
||||||
|
comment: comment,
|
||||||
|
is_common: common,
|
||||||
|
is_canonical: canonical
|
||||||
|
};
|
||||||
|
cloneItem(base.id, data, newSchema => {
|
||||||
|
toast.success(`Копия создана: ${newSchema.alias}`);
|
||||||
|
navigateTo(`/rsforms/${newSchema.id}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title='Создание копии концептуальной схемы'
|
||||||
|
hideWindow={hideWindow}
|
||||||
|
canSubmit={canSubmit}
|
||||||
|
submitText='Создать'
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<div className='flex flex-col gap-3'>
|
||||||
|
<TextInput
|
||||||
|
label='Полное название'
|
||||||
|
value={title}
|
||||||
|
onChange={event => setTitle(event.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label='Сокращение'
|
||||||
|
value={alias}
|
||||||
|
dimensions='max-w-sm'
|
||||||
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
label='Комментарий'
|
||||||
|
value={comment}
|
||||||
|
onChange={event => setComment(event.target.value)}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Общедоступная схема'
|
||||||
|
value={common}
|
||||||
|
setValue={value => setCommon(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DlgCloneLibraryItem;
|
|
@ -1,89 +0,0 @@
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import Checkbox from '../components/Common/Checkbox';
|
|
||||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
|
||||||
import TextArea from '../components/Common/TextArea';
|
|
||||||
import TextInput from '../components/Common/TextInput';
|
|
||||||
import { useLibrary } from '../context/LibraryContext';
|
|
||||||
import { useConceptNavigation } from '../context/NagivationContext';
|
|
||||||
import { useRSForm } from '../context/RSFormContext';
|
|
||||||
import { IRSFormCreateData } from '../models/rsform';
|
|
||||||
import { cloneTitle } from '../utils/misc';
|
|
||||||
|
|
||||||
interface DlgCloneRSFormProps
|
|
||||||
extends Pick<ModalProps, 'hideWindow'> {}
|
|
||||||
|
|
||||||
function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
|
||||||
const { navigateTo } = useConceptNavigation();
|
|
||||||
const [title, setTitle] = useState('');
|
|
||||||
const [alias, setAlias] = useState('');
|
|
||||||
const [comment, setComment] = useState('');
|
|
||||||
const [common, setCommon] = useState(false);
|
|
||||||
const [canonical, setCanonical] = useState(false);
|
|
||||||
|
|
||||||
const { cloneItem } = useLibrary();
|
|
||||||
const { schema } = useRSForm();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (schema) {
|
|
||||||
setTitle(cloneTitle(schema));
|
|
||||||
setAlias(schema.alias);
|
|
||||||
setComment(schema.comment);
|
|
||||||
setCommon(schema.is_common);
|
|
||||||
setCanonical(false);
|
|
||||||
}
|
|
||||||
}, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common]);
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if (!schema) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data: IRSFormCreateData = {
|
|
||||||
item_type: schema.item_type,
|
|
||||||
title: title,
|
|
||||||
alias: alias,
|
|
||||||
comment: comment,
|
|
||||||
is_common: common,
|
|
||||||
is_canonical: canonical
|
|
||||||
};
|
|
||||||
cloneItem(schema.id, data, newSchema => {
|
|
||||||
toast.success(`Схема создана: ${newSchema.alias}`);
|
|
||||||
navigateTo(`/rsforms/${newSchema.id}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title='Создание копии концептуальной схемы'
|
|
||||||
hideWindow={hideWindow}
|
|
||||||
canSubmit={true}
|
|
||||||
submitText='Создать'
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-3'>
|
|
||||||
<TextInput id='title' label='Полное название' type='text'
|
|
||||||
required
|
|
||||||
value={title}
|
|
||||||
onChange={event => setTitle(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextInput id='alias' label='Сокращение' type='text'
|
|
||||||
required
|
|
||||||
value={alias}
|
|
||||||
dimensions='max-w-sm'
|
|
||||||
onChange={event => setAlias(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextArea id='comment' label='Комментарий'
|
|
||||||
value={comment}
|
|
||||||
onChange={event => setComment(event.target.value)}
|
|
||||||
/>
|
|
||||||
<Checkbox id='common' label='Общедоступная схема'
|
|
||||||
value={common}
|
|
||||||
setValue={value => setCommon(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DlgCloneRSForm;
|
|
|
@ -146,7 +146,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
</div>
|
</div>
|
||||||
<ConceptTooltip
|
<ConceptTooltip
|
||||||
anchorSelect='#templates-help'
|
anchorSelect='#templates-help'
|
||||||
className='max-w-[30rem] z-modal-tooltip'
|
className='max-w-[35rem] z-modal-tooltip'
|
||||||
offset={10}
|
offset={10}
|
||||||
>
|
>
|
||||||
<HelpRSTemplates />
|
<HelpRSTemplates />
|
||||||
|
|
|
@ -47,59 +47,58 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
|
||||||
}, [cstData.alias, cstData.cst_type, schema]);
|
}, [cstData.alias, cstData.cst_type, schema]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Создание конституенты'
|
title='Создание конституенты'
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitText='Создать'
|
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 my-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'>
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
className='my-2 min-w-[15rem] self-center'
|
className='min-w-[15rem] self-center'
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
options={SelectorCstType}
|
options={SelectorCstType}
|
||||||
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
|
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
|
||||||
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.BASE})}
|
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.BASE})}
|
||||||
/>
|
|
||||||
<TextInput id='alias' label='Имя'
|
|
||||||
dense
|
|
||||||
dimensions='w-[7rem]'
|
|
||||||
value={cstData.alias}
|
|
||||||
onChange={event => updateCstData({ alias: event.target.value})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TextArea id='term' label='Термин'
|
|
||||||
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
|
||||||
rows={2}
|
|
||||||
value={cstData.term_raw}
|
|
||||||
spellCheck
|
|
||||||
onChange={event => updateCstData({ term_raw: event.target.value })}
|
|
||||||
/>
|
/>
|
||||||
<RSInput id='expression' label='Формальное определение'
|
<TextInput id='alias' label='Имя'
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
dense
|
||||||
height='5.1rem'
|
dimensions='w-[7rem]'
|
||||||
value={cstData.definition_formal}
|
value={cstData.alias}
|
||||||
onChange={value => updateCstData({definition_formal: value})}
|
onChange={event => updateCstData({ alias: event.target.value})}
|
||||||
/>
|
|
||||||
<TextArea id='definition' label='Текстовое определение'
|
|
||||||
placeholder='Лингвистическая интерпретация формального выражения'
|
|
||||||
rows={2}
|
|
||||||
value={cstData.definition_raw}
|
|
||||||
spellCheck
|
|
||||||
onChange={event => updateCstData({ definition_raw: event.target.value })}
|
|
||||||
/>
|
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
|
||||||
placeholder='Договоренность об интерпретации или пояснение'
|
|
||||||
rows={2}
|
|
||||||
value={cstData.convention}
|
|
||||||
spellCheck
|
|
||||||
onChange={event => updateCstData({ convention: event.target.value })}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
<TextArea id='term' label='Термин'
|
||||||
);
|
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
||||||
|
rows={2}
|
||||||
|
value={cstData.term_raw}
|
||||||
|
spellCheck
|
||||||
|
onChange={event => updateCstData({ term_raw: event.target.value })}
|
||||||
|
/>
|
||||||
|
<RSInput id='expression' label='Формальное определение'
|
||||||
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
|
height='5.1rem'
|
||||||
|
value={cstData.definition_formal}
|
||||||
|
onChange={value => updateCstData({definition_formal: value})}
|
||||||
|
/>
|
||||||
|
<TextArea id='definition' label='Текстовое определение'
|
||||||
|
placeholder='Лингвистическая интерпретация формального выражения'
|
||||||
|
rows={2}
|
||||||
|
value={cstData.definition_raw}
|
||||||
|
spellCheck
|
||||||
|
onChange={event => updateCstData({ definition_raw: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
|
placeholder='Договоренность об интерпретации или пояснение'
|
||||||
|
rows={2}
|
||||||
|
value={cstData.convention}
|
||||||
|
spellCheck
|
||||||
|
onChange={event => updateCstData({ convention: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DlgCreateCst;
|
export default DlgCreateCst;
|
||||||
|
|
|
@ -28,44 +28,43 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Удаление конституент'
|
title='Удаление конституент'
|
||||||
submitText={expandOut ? 'Удалить с зависимыми' : 'Удалить'}
|
submitText={expandOut ? 'Удалить с зависимыми' : 'Удалить'}
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={true}
|
canSubmit={true}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<div className='max-w-[60vw] min-w-[20rem]'>
|
<div className='max-w-[60vw] min-w-[30rem]'>
|
||||||
<p>Выбраны к удалению: <b>{selected.length}</b></p>
|
<p>Выбраны к удалению: <b>{selected.length}</b></p>
|
||||||
<div className='px-3 border h-[9rem] mt-1 overflow-y-auto whitespace-nowrap'>
|
<div className='px-3 border h-[9rem] mt-1 overflow-y-auto whitespace-nowrap'>
|
||||||
{selected.map(
|
{selected.map(
|
||||||
(id) => {
|
(id) => {
|
||||||
const cst = schema!.items.find(cst => cst.id === id);
|
const cst = schema!.items.find(cst => cst.id === id);
|
||||||
return (cst ?
|
return (cst ?
|
||||||
<p key={`${prefixes.cst_delete_list}${cst.id}`}>
|
<p key={`${prefixes.cst_delete_list}${cst.id}`}>
|
||||||
{labelConstituenta(cst)}
|
{labelConstituenta(cst)}
|
||||||
</p> : null);
|
</p> : null);
|
||||||
})}
|
})}
|
||||||
</div>
|
|
||||||
<p className='mt-4'>Зависимые конституенты: <b>{expansion.length}</b></p>
|
|
||||||
<div className='mt-1 mb-3 px-3 border h-[9rem] overflow-y-auto whitespace-nowrap'>
|
|
||||||
{expansion.map(
|
|
||||||
(id) => {
|
|
||||||
const cst = schema!.items.find(cst => cst.id === id);
|
|
||||||
return (cst ?
|
|
||||||
<p key={`${prefixes.cst_dependant_list}${cst.id}`}>
|
|
||||||
{labelConstituenta(cst)}
|
|
||||||
</p> : null);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
label='Удалить зависимые конституенты'
|
|
||||||
value={expandOut}
|
|
||||||
setValue={value => setExpandOut(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
<p className='mt-4'>Зависимые конституенты: <b>{expansion.length}</b></p>
|
||||||
);
|
<div className='mt-1 mb-3 px-3 border h-[9rem] overflow-y-auto whitespace-nowrap'>
|
||||||
|
{expansion.map(
|
||||||
|
(id) => {
|
||||||
|
const cst = schema!.items.find(cst => cst.id === id);
|
||||||
|
return (cst ?
|
||||||
|
<p key={`${prefixes.cst_dependant_list}${cst.id}`}>
|
||||||
|
{labelConstituenta(cst)}
|
||||||
|
</p> : null);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
label='Удалить зависимые конституенты'
|
||||||
|
value={expandOut}
|
||||||
|
setValue={value => setExpandOut(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DlgDeleteCst;
|
export default DlgDeleteCst;
|
||||||
|
|
|
@ -207,7 +207,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
<Modal
|
<Modal
|
||||||
title='Редактирование словоформ'
|
title='Редактирование словоформ'
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
submitText='Сохранить данные'
|
submitText='Сохранить'
|
||||||
canSubmit
|
canSubmit
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
|
@ -237,54 +237,52 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
spellCheck
|
spellCheck
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='mt-4 mb-2 text-sm font-semibold'>
|
<div className='mt-3 mb-2 text-sm font-semibold'>
|
||||||
Параметры словоформы
|
Параметры словоформы
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-start justify-between w-full'>
|
<div className='flex items-start justify-between w-full'>
|
||||||
<TextArea
|
<div className='flex items-center'>
|
||||||
placeholder='Введите текст'
|
<TextArea
|
||||||
rows={2}
|
placeholder='Введите текст'
|
||||||
dimensions='min-w-[18rem] w-full min-h-[4.2rem]'
|
rows={2}
|
||||||
value={inputText}
|
dimensions='min-w-[18rem] w-full min-h-[4.2rem]'
|
||||||
onChange={event => setInputText(event.target.value)}
|
value={inputText}
|
||||||
/>
|
onChange={event => setInputText(event.target.value)}
|
||||||
<div className='max-w-min'>
|
|
||||||
<MiniButton
|
|
||||||
tooltip='Генерировать словоформу'
|
|
||||||
icon={<ArrowLeftIcon
|
|
||||||
size={6}
|
|
||||||
color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}
|
|
||||||
/>}
|
|
||||||
disabled={textProcessor.loading || inputGrams.length == 0}
|
|
||||||
onClick={handleInflect}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
tooltip='Определить граммемы'
|
|
||||||
icon={<ArrowRightIcon
|
|
||||||
size={6}
|
|
||||||
color={!inputText ? 'text-disabled' : 'text-primary'}
|
|
||||||
/>}
|
|
||||||
disabled={textProcessor.loading || !inputText}
|
|
||||||
onClick={handleParse}
|
|
||||||
/>
|
/>
|
||||||
|
<div className='max-w-min'>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Генерировать словоформу'
|
||||||
|
icon={<ArrowLeftIcon size={5} color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'} />}
|
||||||
|
disabled={textProcessor.loading || inputGrams.length == 0}
|
||||||
|
onClick={handleInflect}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Определить граммемы'
|
||||||
|
icon={<ArrowRightIcon
|
||||||
|
size={5}
|
||||||
|
color={!inputText ? 'text-disabled' : 'text-primary'}
|
||||||
|
/>}
|
||||||
|
disabled={textProcessor.loading || !inputText}
|
||||||
|
onClick={handleParse}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SelectMulti
|
<SelectMulti
|
||||||
className='min-w-[20rem] max-w-[20rem] h-full flex-grow'
|
className='min-w-[20rem] max-w-[20rem] h-full flex-grow'
|
||||||
options={options}
|
options={options}
|
||||||
placeholder='Выберите граммемы'
|
placeholder='Выберите граммемы'
|
||||||
|
|
||||||
value={inputGrams}
|
value={inputGrams}
|
||||||
onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
|
onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-between flex-start'>
|
<div className='flex items-center justify-between mt-2 mb-1 flex-start'>
|
||||||
<div className='flex items-center justify-start'>
|
<div className='flex items-center justify-start'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Внести словоформу'
|
tooltip='Внести словоформу'
|
||||||
icon={<CheckIcon
|
icon={<CheckIcon
|
||||||
size={6}
|
size={5}
|
||||||
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}
|
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}
|
||||||
/>}
|
/>}
|
||||||
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
||||||
|
@ -293,25 +291,22 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Генерировать все словоформы'
|
tooltip='Генерировать все словоформы'
|
||||||
icon={<ChevronDoubleDownIcon
|
icon={<ChevronDoubleDownIcon
|
||||||
size={6}
|
size={5}
|
||||||
color={!inputText ? 'text-disabled' : 'text-primary'}
|
color={!inputText ? 'text-disabled' : 'text-primary'}
|
||||||
/>}
|
/>}
|
||||||
disabled={textProcessor.loading || !inputText}
|
disabled={textProcessor.loading || !inputText}
|
||||||
onClick={handleGenerateLexeme}
|
onClick={handleGenerateLexeme}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full mt-2 mb-1 text-sm font-semibold text-center'>
|
<div className='w-full text-sm font-semibold text-center'>
|
||||||
Заданные вручную словоформы: [{forms.length}]
|
Заданные вручную словоформы [{forms.length}]
|
||||||
</div>
|
</div>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сбросить ВСЕ словоформы'
|
tooltip='Сбросить ВСЕ словоформы'
|
||||||
icon={<CrossIcon
|
icon={<CrossIcon size={5} color={forms.length === 0 ? 'text-disabled' : 'text-warning'} />}
|
||||||
size={6}
|
|
||||||
color={forms.length === 0 ? 'text-disabled' : 'text-warning'}
|
|
||||||
/>}
|
|
||||||
disabled={textProcessor.loading || forms.length === 0}
|
disabled={textProcessor.loading || forms.length === 0}
|
||||||
onClick={handleResetAll}
|
onClick={handleResetAll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='border overflow-y-auto max-h-[17.4rem] min-h-[17.4rem] mb-2'>
|
<div className='border overflow-y-auto max-h-[17.4rem] min-h-[17.4rem] mb-2'>
|
||||||
|
|
|
@ -14,10 +14,10 @@ extends Pick<ModalProps, 'hideWindow'> {
|
||||||
function DlgGraphParams({ hideWindow, initial, onConfirm } : DlgGraphParamsProps) {
|
function DlgGraphParams({ hideWindow, initial, onConfirm } : DlgGraphParamsProps) {
|
||||||
const [params, updateParams] = usePartialUpdate(initial);
|
const [params, updateParams] = usePartialUpdate(initial);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
function handleSubmit() {
|
||||||
hideWindow();
|
hideWindow();
|
||||||
onConfirm(params);
|
onConfirm(params);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal canSubmit
|
<Modal canSubmit
|
||||||
|
@ -26,7 +26,7 @@ function DlgGraphParams({ hideWindow, initial, onConfirm } : DlgGraphParamsProps
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitText='Применить'
|
submitText='Применить'
|
||||||
>
|
>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-6 my-2'>
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<h1>Преобразования</h1>
|
<h1>Преобразования</h1>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
@ -40,35 +40,36 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
||||||
}, [cstData.cst_type, cstData.alias, initial, schema]);
|
}, [cstData.cst_type, cstData.alias, initial, schema]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Переименование конституенты'
|
title='Переименование конституенты'
|
||||||
submitText='Переименовать'
|
submitText='Переименовать'
|
||||||
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
|
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<div className='flex items-center gap-4 px-2 my-2 h-fit min-w-[25rem]'>
|
<div className='flex justify-center w-full min-w-[22rem] my-3'>
|
||||||
<SelectSingle
|
<div className='flex items-center gap-6'>
|
||||||
placeholder='Выберите тип'
|
<SelectSingle
|
||||||
className='min-w-[14rem] self-center'
|
placeholder='Выберите тип'
|
||||||
options={SelectorCstType}
|
className='min-w-[14rem] self-center'
|
||||||
value={{
|
options={SelectorCstType}
|
||||||
value: cstData.cst_type,
|
value={{
|
||||||
label: labelCstType(cstData.cst_type)
|
value: cstData.cst_type,
|
||||||
}}
|
label: labelCstType(cstData.cst_type)
|
||||||
onChange={data => updateData({cst_type: data?.value ?? CstType.BASE})}
|
}}
|
||||||
/>
|
onChange={data => updateData({cst_type: data?.value ?? CstType.BASE})}
|
||||||
<div>
|
/>
|
||||||
<TextInput id='alias' label='Имя' dense
|
<div>
|
||||||
dimensions='w-[7rem]'
|
<TextInput id='alias' label='Имя' dense
|
||||||
value={cstData.alias}
|
dimensions='w-[7rem]'
|
||||||
onChange={event => updateData({alias: event.target.value})}
|
value={cstData.alias}
|
||||||
/>
|
onChange={event => updateData({alias: event.target.value})}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DlgRenameCst;
|
export default DlgRenameCst;
|
||||||
|
|
|
@ -53,42 +53,41 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
|
||||||
, []);
|
, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
readonly
|
readonly
|
||||||
|
>
|
||||||
|
<div className='flex flex-col items-start gap-2 mt-2'>
|
||||||
|
<div className='w-full text-lg text-center'>
|
||||||
|
{!hoverNode ? expression : null}
|
||||||
|
{hoverNode ?
|
||||||
|
<div>
|
||||||
|
<span>{expression.slice(0, hoverNode.start)}</span>
|
||||||
|
<span className='clr-selected'>{expression.slice(hoverNode.start, hoverNode.finish)}</span>
|
||||||
|
<span>{expression.slice(hoverNode.finish)}</span>
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
|
<div className='flex-wrap w-full h-full overflow-auto'>
|
||||||
|
<div
|
||||||
|
className='relative'
|
||||||
|
style={{
|
||||||
|
width: 'calc(100vw - 6rem - 2px)',
|
||||||
|
height: 'calc(100vh - 14rem - 2px)'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col items-start gap-2'>
|
<GraphCanvas
|
||||||
<div className='w-full text-lg text-center'>
|
nodes={nodes}
|
||||||
{!hoverNode ? expression : null}
|
edges={edges}
|
||||||
{hoverNode ?
|
layoutType='hierarchicalTd'
|
||||||
<div>
|
labelFontUrl={resources.graph_font}
|
||||||
<span>{expression.slice(0, hoverNode.start)}</span>
|
theme={darkMode ? graphDarkT : graphLightT}
|
||||||
<span className='clr-selected'>{expression.slice(hoverNode.start, hoverNode.finish)}</span>
|
onNodePointerOver={handleHoverIn}
|
||||||
<span>{expression.slice(hoverNode.finish)}</span>
|
onNodePointerOut={handleHoverOut}
|
||||||
</div> : null}
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-wrap w-full h-full overflow-auto'>
|
</div>
|
||||||
<div
|
</div>
|
||||||
className='relative'
|
</Modal>);
|
||||||
style={{
|
|
||||||
width: 'calc(100vw - 6rem - 2px)',
|
|
||||||
height: 'calc(100vh - 14rem - 2px)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GraphCanvas
|
|
||||||
nodes={nodes}
|
|
||||||
edges={edges}
|
|
||||||
layoutType='hierarchicalTd'
|
|
||||||
labelFontUrl={resources.graph_font}
|
|
||||||
theme={darkMode ? graphDarkT : graphLightT}
|
|
||||||
onNodePointerOver={handleHoverIn}
|
|
||||||
onNodePointerOut={handleHoverOut}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DlgShowAST;
|
export default DlgShowAST;
|
||||||
|
|
|
@ -303,6 +303,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-default {
|
||||||
|
color: var(--cl-fg-100);
|
||||||
|
&.dark {
|
||||||
|
color: var(--cd-fg-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.text-warning {
|
.text-warning {
|
||||||
color: var(--cl-red-fg-100);
|
color: var(--cl-red-fg-100);
|
||||||
.dark & {
|
.dark & {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import HelpConstituenta from '../../../components/Help/HelpConstituenta'
|
||||||
import { ArrowsRotateIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../../components/Icons'
|
import { ArrowsRotateIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../../components/Icons'
|
||||||
|
|
||||||
interface ConstituentaToolbarProps {
|
interface ConstituentaToolbarProps {
|
||||||
editorMode: boolean
|
isMutable: boolean
|
||||||
isModified: boolean
|
isModified: boolean
|
||||||
|
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
|
@ -19,11 +19,11 @@ interface ConstituentaToolbarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConstituentaToolbar({
|
function ConstituentaToolbar({
|
||||||
editorMode, isModified,
|
isMutable, isModified,
|
||||||
onSubmit, onReset,
|
onSubmit, onReset,
|
||||||
onDelete, onClone, onCreate, onTemplates
|
onDelete, onClone, onCreate, onTemplates
|
||||||
}: ConstituentaToolbarProps) {
|
}: ConstituentaToolbarProps) {
|
||||||
const canSave = useMemo(() => (isModified && editorMode), [isModified, editorMode]);
|
const canSave = useMemo(() => (isModified && isMutable), [isModified, isMutable]);
|
||||||
return (
|
return (
|
||||||
<div className='relative w-full'>
|
<div className='relative w-full'>
|
||||||
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
|
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
|
||||||
|
@ -42,27 +42,27 @@ function ConstituentaToolbar({
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Создать конституенту после данной'
|
tooltip='Создать конституенту после данной'
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onCreate}
|
onClick={onCreate}
|
||||||
icon={<SmallPlusIcon size={5} color={editorMode ? 'text-success' : ''} />}
|
icon={<SmallPlusIcon size={5} color={isMutable ? 'text-success' : ''} />}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Клонировать конституенту'
|
tooltip='Клонировать конституенту'
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onClone}
|
onClick={onClone}
|
||||||
icon={<CloneIcon size={5} color={editorMode ? 'text-success' : ''} />}
|
icon={<CloneIcon size={5} color={isMutable ? 'text-success' : ''} />}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Создать конституенту из шаблона'
|
tooltip='Создать конституенту из шаблона'
|
||||||
icon={<DiamondIcon color={editorMode ? 'text-primary': ''} size={5}/>}
|
icon={<DiamondIcon color={isMutable ? 'text-primary': ''} size={5}/>}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onTemplates}
|
onClick={onTemplates}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить редактируемую конституенту'
|
tooltip='Удалить редактируемую конституенту'
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
icon={<DumpBinIcon size={5} color={editorMode ? 'text-warning' : ''} />}
|
icon={<DumpBinIcon size={5} color={isMutable ? 'text-warning' : ''} />}
|
||||||
/>
|
/>
|
||||||
<div id='cst-help' className='px-1 py-1'>
|
<div id='cst-help' className='px-1 py-1'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
|
|
|
@ -35,11 +35,11 @@ function EditorConstituenta({
|
||||||
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
|
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
|
||||||
}: EditorConstituentaProps) {
|
}: EditorConstituentaProps) {
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const { schema, editorMode } = useRSForm();
|
const { schema, isMutable } = useRSForm();
|
||||||
|
|
||||||
const [toggleReset, setToggleReset] = useState(false);
|
const [toggleReset, setToggleReset] = useState(false);
|
||||||
|
|
||||||
const readyForEdit = useMemo(() => (!!activeCst && editorMode), [activeCst, editorMode]);
|
const readyForEdit = useMemo(() => (!!activeCst && isMutable), [activeCst, isMutable]);
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!schema || !activeID) {
|
if (!schema || !activeID) {
|
||||||
|
@ -103,7 +103,7 @@ function EditorConstituenta({
|
||||||
onKeyDown={handleInput}
|
onKeyDown={handleInput}
|
||||||
>
|
>
|
||||||
<ConstituentaToolbar
|
<ConstituentaToolbar
|
||||||
editorMode={readyForEdit}
|
isMutable={readyForEdit}
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
|
|
||||||
onSubmit={initiateSubmit}
|
onSubmit={initiateSubmit}
|
||||||
|
|
|
@ -30,9 +30,9 @@ function FormConstituenta({
|
||||||
constituenta, toggleReset,
|
constituenta, toggleReset,
|
||||||
onRenameCst, onShowAST, onEditTerm
|
onRenameCst, onShowAST, onEditTerm
|
||||||
}: FormConstituentaProps) {
|
}: FormConstituentaProps) {
|
||||||
const { schema, cstUpdate, editorMode, processing } = useRSForm();
|
const { schema, cstUpdate, isMutable, processing } = useRSForm();
|
||||||
|
|
||||||
const readyForEdit = useMemo(() => (!!constituenta && editorMode), [constituenta, editorMode]);
|
const readyForEdit = useMemo(() => (!!constituenta && isMutable), [constituenta, isMutable]);
|
||||||
|
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
|
|
|
@ -19,7 +19,7 @@ interface EditorRSFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified, onDownload }: EditorRSFormProps) {
|
function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified, onDownload }: EditorRSFormProps) {
|
||||||
const { schema, editorMode: isEditable, isClaimable } = useRSForm();
|
const { schema, isMutable, isClaimable } = useRSForm();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
function initiateSubmit() {
|
function initiateSubmit() {
|
||||||
|
@ -41,7 +41,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
|
||||||
return (
|
return (
|
||||||
<div tabIndex={-1} onKeyDown={handleInput}>
|
<div tabIndex={-1} onKeyDown={handleInput}>
|
||||||
<RSFormToolbar
|
<RSFormToolbar
|
||||||
editorMode={isEditable}
|
isMutable={isMutable}
|
||||||
modified={isModified}
|
modified={isModified}
|
||||||
claimable={isClaimable}
|
claimable={isClaimable}
|
||||||
anonymous={!user}
|
anonymous={!user}
|
||||||
|
|
|
@ -21,7 +21,7 @@ function FormRSForm({
|
||||||
}: FormRSFormProps) {
|
}: FormRSFormProps) {
|
||||||
const {
|
const {
|
||||||
schema, update, adminMode: adminMode,
|
schema, update, adminMode: adminMode,
|
||||||
editorMode: editorMode, processing
|
isMutable: isMutable, processing
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
|
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
|
@ -82,20 +82,20 @@ function FormRSForm({
|
||||||
<TextInput required
|
<TextInput required
|
||||||
label='Полное название'
|
label='Полное название'
|
||||||
value={title}
|
value={title}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onChange={event => setTitle(event.target.value)}
|
onChange={event => setTitle(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput required dense
|
<TextInput required dense
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
dimensions='w-full'
|
dimensions='w-full'
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextArea
|
<TextArea
|
||||||
label='Комментарий'
|
label='Комментарий'
|
||||||
value={comment}
|
value={comment}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onChange={event => setComment(event.target.value)}
|
onChange={event => setComment(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-between whitespace-nowrap'>
|
<div className='flex justify-between whitespace-nowrap'>
|
||||||
|
@ -103,7 +103,7 @@ function FormRSForm({
|
||||||
label='Общедоступная схема'
|
label='Общедоступная схема'
|
||||||
tooltip='Общедоступные схемы видны всем пользователям и могут быть изменены'
|
tooltip='Общедоступные схемы видны всем пользователям и могут быть изменены'
|
||||||
dimensions='w-fit'
|
dimensions='w-fit'
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
value={common}
|
value={common}
|
||||||
setValue={value => setCommon(value)}
|
setValue={value => setCommon(value)}
|
||||||
/>
|
/>
|
||||||
|
@ -111,7 +111,7 @@ function FormRSForm({
|
||||||
label='Неизменная схема'
|
label='Неизменная схема'
|
||||||
tooltip='Только администраторы могут присваивать схемам неизменный статус'
|
tooltip='Только администраторы могут присваивать схемам неизменный статус'
|
||||||
dimensions='w-fit'
|
dimensions='w-fit'
|
||||||
disabled={!editorMode || !adminMode}
|
disabled={!isMutable || !adminMode}
|
||||||
value={canonical}
|
value={canonical}
|
||||||
setValue={value => setCanonical(value)}
|
setValue={value => setCanonical(value)}
|
||||||
/>
|
/>
|
||||||
|
@ -120,7 +120,7 @@ function FormRSForm({
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Сохранить изменения'
|
text='Сохранить изменения'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
disabled={!isModified || !editorMode}
|
disabled={!isModified || !isMutable}
|
||||||
icon={<SaveIcon size={6} />}
|
icon={<SaveIcon size={6} />}
|
||||||
dimensions='my-2 w-fit'
|
dimensions='my-2 w-fit'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import HelpRSFormMeta from '../../../components/Help/HelpRSFormMeta'
|
||||||
import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../../components/Icons'
|
import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../../components/Icons'
|
||||||
|
|
||||||
interface RSFormToolbarProps {
|
interface RSFormToolbarProps {
|
||||||
editorMode: boolean
|
isMutable: boolean
|
||||||
modified: boolean
|
modified: boolean
|
||||||
claimable: boolean
|
claimable: boolean
|
||||||
anonymous: boolean
|
anonymous: boolean
|
||||||
|
@ -19,11 +19,11 @@ interface RSFormToolbarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSFormToolbar({
|
function RSFormToolbar({
|
||||||
editorMode, modified, claimable, anonymous,
|
isMutable, modified, claimable, anonymous,
|
||||||
onSubmit, onShare, onDownload,
|
onSubmit, onShare, onDownload,
|
||||||
onClaim, onDestroy
|
onClaim, onDestroy
|
||||||
}: RSFormToolbarProps) {
|
}: RSFormToolbarProps) {
|
||||||
const canSave = useMemo(() => (modified && editorMode), [modified, editorMode]);
|
const canSave = useMemo(() => (modified && isMutable), [modified, isMutable]);
|
||||||
return (
|
return (
|
||||||
<div className='relative flex items-start justify-center w-full'>
|
<div className='relative flex items-start justify-center w-full'>
|
||||||
<div className='absolute flex mt-1'>
|
<div className='absolute flex mt-1'>
|
||||||
|
@ -51,9 +51,9 @@ function RSFormToolbar({
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить схему'
|
tooltip='Удалить схему'
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onDestroy}
|
onClick={onDestroy}
|
||||||
icon={<DumpBinIcon size={5} color={editorMode ? 'text-warning' : ''} />}
|
icon={<DumpBinIcon size={5} color={isMutable ? 'text-warning' : ''} />}
|
||||||
/>
|
/>
|
||||||
<div id='rsform-help' className='py-1 ml-1'>
|
<div id='rsform-help' className='py-1 ml-1'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useLayoutEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { type RowSelectionState } from '../../../components/DataTable';
|
import { type RowSelectionState } from '../../../components/DataTable';
|
||||||
|
import SelectedCounter from '../../../components/Shared/SelectedCounter';
|
||||||
import { useRSForm } from '../../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import { CstType, ICstCreateData, ICstMovetoData } from '../../../models/rsform'
|
import { CstType, ICstCreateData, ICstMovetoData } from '../../../models/rsform'
|
||||||
import RSListToolbar from './RSListToolbar';
|
import RSListToolbar from './RSListToolbar';
|
||||||
|
@ -15,7 +16,7 @@ interface EditorRSListProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
|
function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
|
||||||
const { schema, editorMode: isEditable, cstMoveTo, resetAliases } = useRSForm();
|
const { schema, isMutable, cstMoveTo, resetAliases } = useRSForm();
|
||||||
const [selected, setSelected] = useState<number[]>([]);
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
|
@ -154,7 +155,7 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
||||||
|
|
||||||
// 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 (!isMutable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key === 'Delete' && selected.length > 0) {
|
if (event.key === 'Delete' && selected.length > 0) {
|
||||||
|
@ -197,23 +198,25 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
|
||||||
<div tabIndex={-1}
|
<div tabIndex={-1}
|
||||||
className='w-full outline-none'
|
className='w-full outline-none'
|
||||||
onKeyDown={handleTableKey}
|
onKeyDown={handleTableKey}
|
||||||
>
|
>
|
||||||
<div className='sticky top-0 flex items-center justify-start w-full gap-1 px-2 py-1 border-b select-none clr-app'>
|
<RSListToolbar
|
||||||
<div className='min-w-[9rem] max-w-[9rem] whitespace-nowrap small-caps'>
|
selectedCount={selected.length}
|
||||||
{`Выбор ${selected.length} из ${schema?.stats?.count_all ?? 0}`}
|
isMutable={isMutable}
|
||||||
</div>
|
onMoveUp={handleMoveUp}
|
||||||
<RSListToolbar
|
onMoveDown={handleMoveDown}
|
||||||
selectedCount={selected.length}
|
onClone={handleClone}
|
||||||
editorMode={isEditable}
|
onCreate={handleCreateCst}
|
||||||
onMoveUp={handleMoveUp}
|
onDelete={handleDelete}
|
||||||
onMoveDown={handleMoveDown}
|
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
|
||||||
onClone={handleClone}
|
onReindex={handleReindex}
|
||||||
onCreate={handleCreateCst}
|
/>
|
||||||
onDelete={handleDelete}
|
<SelectedCounter
|
||||||
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
|
total={schema?.stats?.count_all ?? 0}
|
||||||
onReindex={handleReindex}
|
selected={selected.length}
|
||||||
/>
|
position='left-0 top-1'
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
|
<div className='pt-[2.3rem] border-b' />
|
||||||
|
|
||||||
<RSTable
|
<RSTable
|
||||||
items={schema?.items}
|
items={schema?.items}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Dropdown from '../../../components/Common/Dropdown';
|
||||||
import DropdownButton from '../../../components/Common/DropdownButton';
|
import DropdownButton from '../../../components/Common/DropdownButton';
|
||||||
import MiniButton from '../../../components/Common/MiniButton';
|
import MiniButton from '../../../components/Common/MiniButton';
|
||||||
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
|
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
|
||||||
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons';
|
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon, UpdateIcon } from '../../../components/Icons';
|
||||||
import useDropdown from '../../../hooks/useDropdown';
|
import useDropdown from '../../../hooks/useDropdown';
|
||||||
import { CstType } from '../../../models/rsform';
|
import { CstType } from '../../../models/rsform';
|
||||||
import { prefixes } from '../../../utils/constants';
|
import { prefixes } from '../../../utils/constants';
|
||||||
|
@ -13,7 +13,7 @@ import { labelCstType } from '../../../utils/labels';
|
||||||
import { getCstTypePrefix, getCstTypeShortcut } from '../../../utils/misc';
|
import { getCstTypePrefix, getCstTypeShortcut } from '../../../utils/misc';
|
||||||
|
|
||||||
interface RSListToolbarProps {
|
interface RSListToolbarProps {
|
||||||
editorMode?: boolean
|
isMutable?: boolean
|
||||||
selectedCount: number
|
selectedCount: number
|
||||||
|
|
||||||
onMoveUp: () => void
|
onMoveUp: () => void
|
||||||
|
@ -26,86 +26,90 @@ interface RSListToolbarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSListToolbar({
|
function RSListToolbar({
|
||||||
selectedCount, editorMode,
|
selectedCount, isMutable,
|
||||||
onMoveUp, onMoveDown, onDelete, onClone, onCreate, onTemplates, onReindex
|
onMoveUp, onMoveDown, onDelete, onClone,
|
||||||
|
onCreate, onTemplates, onReindex
|
||||||
}: RSListToolbarProps) {
|
}: RSListToolbarProps) {
|
||||||
const insertMenu = useDropdown();
|
const insertMenu = useDropdown();
|
||||||
|
|
||||||
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
|
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-center w-full mr-[9rem]'>
|
<div className='relative w-full z-pop'>
|
||||||
|
<div className='absolute flex items-start justify-center w-full top-1'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Переместить вверх [Alt + вверх]'
|
tooltip='Переместить вверх [Alt + вверх]'
|
||||||
icon={<ArrowUpIcon size={5}/>}
|
icon={<ArrowUpIcon size={5}/>}
|
||||||
disabled={!editorMode || nothingSelected}
|
disabled={!isMutable || nothingSelected}
|
||||||
onClick={onMoveUp}
|
onClick={onMoveUp}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Переместить вниз [Alt + вниз]'
|
tooltip='Переместить вниз [Alt + вниз]'
|
||||||
icon={<ArrowDownIcon size={5}/>}
|
icon={<ArrowDownIcon size={5}/>}
|
||||||
disabled={!editorMode || nothingSelected}
|
disabled={!isMutable || nothingSelected}
|
||||||
onClick={onMoveDown}
|
onClick={onMoveDown}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Клонировать конституенту [Alt + V]'
|
tooltip='Клонировать конституенту [Alt + V]'
|
||||||
icon={<CloneIcon color={editorMode && selectedCount === 1 ? 'text-success': ''} size={5}/>}
|
icon={<CloneIcon color={isMutable && selectedCount === 1 ? 'text-success': ''} size={5}/>}
|
||||||
disabled={!editorMode || selectedCount !== 1}
|
disabled={!isMutable || selectedCount !== 1}
|
||||||
onClick={onClone}
|
onClick={onClone}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Добавить новую конституенту... [Alt + `]'
|
tooltip='Добавить новую конституенту... [Alt + `]'
|
||||||
icon={<SmallPlusIcon color={editorMode ? 'text-success': ''} size={5}/>}
|
icon={<SmallPlusIcon color={isMutable ? 'text-success': ''} size={5}/>}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={() => onCreate()}
|
onClick={() => onCreate()}
|
||||||
/>
|
/>
|
||||||
<div ref={insertMenu.ref} className='flex justify-center'>
|
<div ref={insertMenu.ref} className='flex justify-center'>
|
||||||
<MiniButton
|
<div>
|
||||||
tooltip='Добавить пустую конституенту'
|
<MiniButton
|
||||||
icon={<ArrowDropdownIcon color={editorMode ? 'text-success': ''} size={5}/>}
|
tooltip='Добавить пустую конституенту'
|
||||||
disabled={!editorMode}
|
icon={<ArrowDropdownIcon color={isMutable ? 'text-success': ''} size={5}/>}
|
||||||
onClick={insertMenu.toggle}
|
disabled={!isMutable}
|
||||||
/>
|
onClick={insertMenu.toggle}
|
||||||
{insertMenu.isActive ?
|
/>
|
||||||
<Dropdown>
|
{insertMenu.isActive ?
|
||||||
{(Object.values(CstType)).map(
|
<Dropdown>
|
||||||
(typeStr) => {
|
{(Object.values(CstType)).map(
|
||||||
const type = typeStr as CstType;
|
(typeStr) => {
|
||||||
return (
|
const type = typeStr as CstType;
|
||||||
<DropdownButton
|
return (
|
||||||
key={`${prefixes.csttype_list}${typeStr}`}
|
<DropdownButton
|
||||||
onClick={() => onCreate(type)}
|
key={`${prefixes.csttype_list}${typeStr}`}
|
||||||
tooltip={getCstTypeShortcut(type)}
|
onClick={() => onCreate(type)}
|
||||||
>
|
tooltip={getCstTypeShortcut(type)}
|
||||||
{`${getCstTypePrefix(type)}1 — ${labelCstType(type)}`}
|
>
|
||||||
</DropdownButton>);
|
{`${getCstTypePrefix(type)}1 — ${labelCstType(type)}`}
|
||||||
})}
|
</DropdownButton>);
|
||||||
</Dropdown> : null}
|
})}
|
||||||
|
</Dropdown> : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Создать конституенту из шаблона'
|
tooltip='Создать конституенту из шаблона'
|
||||||
icon={<DiamondIcon color={editorMode ? 'text-primary': ''} size={5}/>}
|
icon={<DiamondIcon color={isMutable ? 'text-primary': ''} size={5}/>}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onTemplates}
|
onClick={onTemplates}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сброс имен: присвоить порядковые имена'
|
tooltip='Сброс имен: присвоить порядковые имена'
|
||||||
icon={<UpdateIcon color={editorMode ? 'text-primary': ''} size={5}/>}
|
icon={<UpdateIcon color={isMutable ? 'text-primary': ''} size={5}/>}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onReindex}
|
onClick={onReindex}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить выбранные [Delete]'
|
tooltip='Удалить выбранные [Delete]'
|
||||||
icon={<DumpBinIcon color={editorMode && !nothingSelected ? 'text-warning' : ''} size={5}/>}
|
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
|
||||||
disabled={!editorMode || nothingSelected}
|
disabled={!isMutable || nothingSelected}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
/>
|
/>
|
||||||
<div className='ml-1' id='items-table-help'>
|
<div className='px-1 py-1' id='items-table-help'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
</div>
|
</div>
|
||||||
<ConceptTooltip anchorSelect='#items-table-help' offset={8}>
|
<ConceptTooltip anchorSelect='#items-table-help' offset={8}>
|
||||||
<HelpRSFormItems />
|
<HelpRSFormItems />
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ function RSTable({
|
||||||
|
|
||||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||||
|
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
setColumnVisibility({
|
setColumnVisibility({
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { GraphEdge, GraphNode, LayoutTypes } from 'reagraph';
|
import { GraphEdge, GraphNode, LayoutTypes } from 'reagraph';
|
||||||
|
|
||||||
import InfoConstituenta from '../../../components/Shared/InfoConstituenta';
|
import InfoConstituenta from '../../../components/Shared/InfoConstituenta';
|
||||||
|
import SelectedCounter from '../../../components/Shared/SelectedCounter';
|
||||||
import { useRSForm } from '../../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
import DlgGraphParams from '../../../dialogs/DlgGraphParams';
|
import DlgGraphParams from '../../../dialogs/DlgGraphParams';
|
||||||
|
@ -12,7 +13,6 @@ import { colorbgGraphNode } from '../../../utils/color';
|
||||||
import { TIMEOUT_GRAPH_REFRESH } from '../../../utils/constants';
|
import { TIMEOUT_GRAPH_REFRESH } from '../../../utils/constants';
|
||||||
import GraphSidebar from './GraphSidebar';
|
import GraphSidebar from './GraphSidebar';
|
||||||
import GraphToolbar from './GraphToolbar';
|
import GraphToolbar from './GraphToolbar';
|
||||||
import SelectedCounter from './SelectedCounter';
|
|
||||||
import TermGraph from './TermGraph';
|
import TermGraph from './TermGraph';
|
||||||
import useGraphFilter from './useGraphFilter';
|
import useGraphFilter from './useGraphFilter';
|
||||||
import ViewHidden from './ViewHidden';
|
import ViewHidden from './ViewHidden';
|
||||||
|
@ -24,7 +24,7 @@ interface EditorTermGraphProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||||
const { schema, editorMode: isEditable } = useRSForm();
|
const { schema, isMutable } = useRSForm();
|
||||||
const { colors } = useConceptTheme();
|
const { colors } = useConceptTheme();
|
||||||
|
|
||||||
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
|
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
|
||||||
|
@ -179,7 +179,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
|
|
||||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
// Hotkeys implementation
|
// Hotkeys implementation
|
||||||
if (!isEditable) {
|
if (!isMutable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key === 'Delete') {
|
if (event.key === 'Delete') {
|
||||||
|
@ -197,13 +197,14 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
onConfirm={handleChangeParams}
|
onConfirm={handleChangeParams}
|
||||||
/> : null}
|
/> : null}
|
||||||
|
|
||||||
<SelectedCounter
|
<SelectedCounter hideZero
|
||||||
total={schema?.stats?.count_all ?? 0}
|
total={schema?.stats?.count_all ?? 0}
|
||||||
selected={selected.length}
|
selected={selected.length}
|
||||||
|
position='top-[0.3rem] left-0'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GraphToolbar
|
<GraphToolbar
|
||||||
editorMode={isEditable}
|
isMutable={isMutable}
|
||||||
nothingSelected={nothingSelected}
|
nothingSelected={nothingSelected}
|
||||||
is3D={is3D}
|
is3D={is3D}
|
||||||
orbit={orbit}
|
orbit={orbit}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import HelpTermGraph from '../../../components/Help/HelpTermGraph'
|
||||||
import { ArrowsFocusIcon, DumpBinIcon, FilterIcon, HelpIcon, LetterAIcon, LetterALinesIcon, PlanetIcon, SmallPlusIcon } from '../../../components/Icons'
|
import { ArrowsFocusIcon, DumpBinIcon, FilterIcon, HelpIcon, LetterAIcon, LetterALinesIcon, PlanetIcon, SmallPlusIcon } from '../../../components/Icons'
|
||||||
|
|
||||||
interface GraphToolbarProps {
|
interface GraphToolbarProps {
|
||||||
editorMode: boolean
|
isMutable: boolean
|
||||||
nothingSelected: boolean
|
nothingSelected: boolean
|
||||||
is3D: boolean
|
is3D: boolean
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ interface GraphToolbarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function GraphToolbar({
|
function GraphToolbar({
|
||||||
editorMode, nothingSelected, is3D,
|
isMutable, nothingSelected, is3D,
|
||||||
noText, toggleNoText,
|
noText, toggleNoText,
|
||||||
orbit, toggleOrbit,
|
orbit, toggleOrbit,
|
||||||
showParamsDialog,
|
showParamsDialog,
|
||||||
|
@ -33,7 +33,8 @@ function GraphToolbar({
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Настройки фильтрации узлов и связей'
|
tooltip='Настройки фильтрации узлов и связей'
|
||||||
icon={<FilterIcon color='text-primary' size={5} />}
|
icon={<FilterIcon color='text-primary' size={5} />}
|
||||||
onClick={showParamsDialog} />
|
onClick={showParamsDialog}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||||
icon={
|
icon={
|
||||||
|
@ -41,26 +42,31 @@ function GraphToolbar({
|
||||||
? <LetterALinesIcon color='text-success' size={5} />
|
? <LetterALinesIcon color='text-success' size={5} />
|
||||||
: <LetterAIcon color='text-primary' size={5} />
|
: <LetterAIcon color='text-primary' size={5} />
|
||||||
}
|
}
|
||||||
onClick={toggleNoText} />
|
onClick={toggleNoText}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Новая конституента'
|
tooltip='Новая конституента'
|
||||||
icon={<SmallPlusIcon color={editorMode ? 'text-success' : ''} size={5} />}
|
icon={<SmallPlusIcon color={isMutable ? 'text-success' : ''} size={5} />}
|
||||||
disabled={!editorMode}
|
disabled={!isMutable}
|
||||||
onClick={onCreate} />
|
onClick={onCreate}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить выбранные'
|
tooltip='Удалить выбранные'
|
||||||
icon={<DumpBinIcon color={editorMode && !nothingSelected ? 'text-warning' : ''} size={5} />}
|
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'text-warning' : ''} size={5} />}
|
||||||
disabled={!editorMode || nothingSelected}
|
disabled={!isMutable || nothingSelected}
|
||||||
onClick={onDelete} />
|
onClick={onDelete}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<ArrowsFocusIcon color='text-primary' size={5} />}
|
icon={<ArrowsFocusIcon color='text-primary' size={5} />}
|
||||||
tooltip='Восстановить камеру'
|
tooltip='Восстановить камеру'
|
||||||
onClick={onResetViewpoint} />
|
onClick={onResetViewpoint}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'text-success' : 'text-primary'} size={5} />}
|
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'text-success' : 'text-primary'} size={5} />}
|
||||||
tooltip='Анимация вращения'
|
tooltip='Анимация вращения'
|
||||||
disabled={!is3D}
|
disabled={!is3D}
|
||||||
onClick={toggleOrbit} />
|
onClick={toggleOrbit}
|
||||||
|
/>
|
||||||
<div className='px-1 py-1' id='items-graph-help'>
|
<div className='px-1 py-1' id='items-graph-help'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
interface SelectedCounterProps {
|
|
||||||
total: number
|
|
||||||
selected: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectedCounter({ total, selected } : SelectedCounterProps) {
|
|
||||||
if (selected === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='relative w-full z-pop'>
|
|
||||||
<div className='absolute left-0 px-2 select-none top-[0.3rem] whitespace-nowrap small-caps clr-app'>
|
|
||||||
Выбор {selected} из {total}
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SelectedCounter;
|
|
|
@ -42,7 +42,7 @@ function ViewHidden({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col text-sm ml-2 border clr-app max-w-[12.5rem] min-w-[12.5rem]'>
|
<div className='flex flex-col text-sm ml-2 border clr-app max-w-[12.5rem] min-w-[12.5rem]'>
|
||||||
<p className='pt-2 text-center'><b>Скрытые конституенты</b></p>
|
<p className='mt-2 text-center'><b>Скрытые конституенты</b></p>
|
||||||
<div className='flex flex-wrap justify-center gap-2 py-2 overflow-y-auto' style={{ maxHeight: dismissedHeight }}>
|
<div className='flex flex-wrap justify-center gap-2 py-2 overflow-y-auto' style={{ maxHeight: dismissedHeight }}>
|
||||||
{items.map(
|
{items.map(
|
||||||
(cstID) => {
|
(cstID) => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { useLibrary } from '../../context/LibraryContext';
|
||||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import DlgCloneRSForm from '../../dialogs/DlgCloneRSForm';
|
import DlgCloneLibraryItem from '../../dialogs/DlgCloneLibraryItem';
|
||||||
import DlgConstituentaTemplate from '../../dialogs/DlgConstituentaTemplate';
|
import DlgConstituentaTemplate from '../../dialogs/DlgConstituentaTemplate';
|
||||||
import DlgCreateCst from '../../dialogs/DlgCreateCst';
|
import DlgCreateCst from '../../dialogs/DlgCreateCst';
|
||||||
import DlgDeleteCst from '../../dialogs/DlgDeleteCst';
|
import DlgDeleteCst from '../../dialogs/DlgDeleteCst';
|
||||||
|
@ -48,7 +48,7 @@ function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ( <BackendError error={error} />);
|
return (<BackendError error={error} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +337,8 @@ function RSTabs() {
|
||||||
hideWindow={() => setShowUpload(false)}
|
hideWindow={() => setShowUpload(false)}
|
||||||
/> : null}
|
/> : null}
|
||||||
{showClone ?
|
{showClone ?
|
||||||
<DlgCloneRSForm
|
<DlgCloneLibraryItem
|
||||||
|
base={schema!}
|
||||||
hideWindow={() => setShowClone(false)}
|
hideWindow={() => setShowClone(false)}
|
||||||
/> : null}
|
/> : null}
|
||||||
{showAST ?
|
{showAST ?
|
||||||
|
|
|
@ -30,7 +30,7 @@ function RSTabsMenu({
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const {
|
const {
|
||||||
isOwned, editorMode: isEditable, isTracking, isReadonly, isClaimable, adminMode: isForceAdmin,
|
isOwned, isMutable, isTracking, isReadonly, isClaimable, adminMode: isForceAdmin,
|
||||||
toggleForceAdmin, toggleReadonly, processing
|
toggleForceAdmin, toggleReadonly, processing
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
const schemaMenu = useDropdown();
|
const schemaMenu = useDropdown();
|
||||||
|
@ -100,15 +100,15 @@ function RSTabsMenu({
|
||||||
<p>Выгрузить в Экстеор</p>
|
<p>Выгрузить в Экстеор</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton disabled={!isEditable} onClick={handleUpload}>
|
<DropdownButton disabled={!isMutable} onClick={handleUpload}>
|
||||||
<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}/>
|
<UploadIcon color={isMutable ? 'text-warning' : ''} size={4}/>
|
||||||
<p>Загрузить из Экстеора</p>
|
<p>Загрузить из Экстеора</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton disabled={!isEditable} onClick={handleDelete}>
|
<DropdownButton disabled={!isMutable} onClick={handleDelete}>
|
||||||
<span className='inline-flex items-center justify-start gap-2'>
|
<span className='inline-flex items-center justify-start gap-2'>
|
||||||
<DumpBinIcon color={isEditable ? 'text-warning' : ''} size={4} />
|
<DumpBinIcon color={isMutable ? 'text-warning' : ''} size={4} />
|
||||||
<p>Удалить схему</p>
|
<p>Удалить схему</p>
|
||||||
</span>
|
</span>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
@ -122,10 +122,10 @@ function RSTabsMenu({
|
||||||
</div>
|
</div>
|
||||||
<div ref={editMenu.ref}>
|
<div ref={editMenu.ref}>
|
||||||
<Button dense noBorder tabIndex={-1}
|
<Button dense noBorder tabIndex={-1}
|
||||||
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
|
tooltip={'измнение: ' + (isMutable ? '[доступно]' : '[запрещено]')}
|
||||||
dimensions='h-full w-fit'
|
dimensions='h-full w-fit'
|
||||||
style={{outlineColor: 'transparent'}}
|
style={{outlineColor: 'transparent'}}
|
||||||
icon={<EditIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
|
icon={<EditIcon size={5} color={isMutable ? 'text-success' : 'text-warning'}/>}
|
||||||
onClick={editMenu.toggle}
|
onClick={editMenu.toggle}
|
||||||
/>
|
/>
|
||||||
{editMenu.isActive ?
|
{editMenu.isActive ?
|
||||||
|
@ -135,14 +135,14 @@ function RSTabsMenu({
|
||||||
onClick={!isOwned ? handleClaimOwner : undefined}
|
onClick={!isOwned ? handleClaimOwner : undefined}
|
||||||
tooltip={!user || !isClaimable ? 'Взять во владение можно общую изменяемую схему' : ''}
|
tooltip={!user || !isClaimable ? 'Взять во владение можно общую изменяемую схему' : ''}
|
||||||
>
|
>
|
||||||
<div className='flex items-center gap-2 pl-1'>
|
<div className='flex items-center gap-2 pl-1 text-default'>
|
||||||
<span>
|
<span>
|
||||||
<OwnerIcon size={5} color={isOwned ? 'text-success' : 'text-controls'} />
|
<OwnerIcon size={4} color={isOwned ? 'text-success' : 'text-controls'} />
|
||||||
</span>
|
</span>
|
||||||
<p>
|
<div>
|
||||||
{isOwned ? <b>Владелец схемы</b> : null}
|
{isOwned ? <b className='text-default'>Вы — владелец</b> : null}
|
||||||
{!isOwned ? <b>Стать владельцем</b> : null}
|
{!isOwned ? <b>Стать владельцем</b> : null}
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
{(isOwned || user?.is_staff) ?
|
{(isOwned || user?.is_staff) ?
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Module: miscellaneous static functions to generate UI resources.
|
* Module: miscellaneous static functions to generate UI resources.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ILibraryItem } from '../models/library';
|
||||||
import { CstType, IConstituenta, IRSForm } from '../models/rsform';
|
import { CstType, IConstituenta, IRSForm } from '../models/rsform';
|
||||||
import { IRSErrorDescription, RSErrorClass } from '../models/rslang';
|
import { IRSErrorDescription, RSErrorClass } from '../models/rslang';
|
||||||
import { inferErrorClass } from '../models/rslangAPI';
|
import { inferErrorClass } from '../models/rslangAPI';
|
||||||
|
@ -61,11 +62,11 @@ export function createAliasFor(type: CstType, schema: IRSForm): string {
|
||||||
return `${prefix}${index}`;
|
return `${prefix}${index}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cloneTitle(schema: IRSForm): string {
|
export function cloneTitle(target: ILibraryItem): string {
|
||||||
if (!schema.title.includes('[клон]')) {
|
if (!target.title.includes('[клон]')) {
|
||||||
return schema.title + ' [клон]';
|
return target.title + ' [клон]';
|
||||||
} else {
|
} else {
|
||||||
return (schema.title + '+');
|
return (target.title + '+');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user