Small UI design improvements

This commit is contained in:
IRBorisov 2023-12-04 14:19:54 +03:00
parent b026a57fad
commit c2e0cfae07
31 changed files with 487 additions and 445 deletions

View File

@ -7,7 +7,7 @@ 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>

View File

@ -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}

View File

@ -1,9 +1,15 @@
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>);
} }

View 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;

View File

@ -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),

View 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;

View File

@ -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;

View File

@ -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 />

View File

@ -54,10 +54,10 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
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) }}
@ -98,8 +98,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
onChange={event => updateCstData({ convention: event.target.value })} onChange={event => updateCstData({ convention: event.target.value })}
/> />
</div> </div>
</Modal> </Modal>);
);
} }
export default DlgCreateCst; export default DlgCreateCst;

View File

@ -35,7 +35,7 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
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(
@ -64,8 +64,7 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
setValue={value => setExpandOut(value)} setValue={value => setExpandOut(value)}
/> />
</div> </div>
</Modal> </Modal>);
);
} }
export default DlgDeleteCst; export default DlgDeleteCst;

View File

@ -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,11 +237,12 @@ 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'>
<div className='flex items-center'>
<TextArea <TextArea
placeholder='Введите текст' placeholder='Введите текст'
rows={2} rows={2}
@ -252,39 +253,36 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
<div className='max-w-min'> <div className='max-w-min'>
<MiniButton <MiniButton
tooltip='Генерировать словоформу' tooltip='Генерировать словоформу'
icon={<ArrowLeftIcon icon={<ArrowLeftIcon size={5} color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'} />}
size={6}
color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}
/>}
disabled={textProcessor.loading || inputGrams.length == 0} disabled={textProcessor.loading || inputGrams.length == 0}
onClick={handleInflect} onClick={handleInflect}
/> />
<MiniButton <MiniButton
tooltip='Определить граммемы' tooltip='Определить граммемы'
icon={<ArrowRightIcon icon={<ArrowRightIcon
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={handleParse} 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,22 +291,19 @@ 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}
/> />

View File

@ -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

View File

@ -48,7 +48,8 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
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'>
<div className='flex items-center gap-6'>
<SelectSingle <SelectSingle
placeholder='Выберите тип' placeholder='Выберите тип'
className='min-w-[14rem] self-center' className='min-w-[14rem] self-center'
@ -67,8 +68,8 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
/> />
</div> </div>
</div> </div>
</Modal> </div>
); </Modal>);
} }
export default DlgRenameCst; export default DlgRenameCst;

View File

@ -57,7 +57,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
hideWindow={hideWindow} hideWindow={hideWindow}
readonly readonly
> >
<div className='flex flex-col items-start gap-2'> <div className='flex flex-col items-start gap-2 mt-2'>
<div className='w-full text-lg text-center'> <div className='w-full text-lg text-center'>
{!hoverNode ? expression : null} {!hoverNode ? expression : null}
{hoverNode ? {hoverNode ?
@ -87,8 +87,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
</div> </div>
</div> </div>
</div> </div>
</Modal> </Modal>);
);
} }
export default DlgShowAST; export default DlgShowAST;

View File

@ -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 & {

View File

@ -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} />

View File

@ -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}

View File

@ -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('');

View File

@ -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}

View File

@ -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'
/> />

View File

@ -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} />

View File

@ -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) {
@ -198,13 +199,9 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
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'>
<div className='min-w-[9rem] max-w-[9rem] whitespace-nowrap small-caps'>
{`Выбор ${selected.length} из ${schema?.stats?.count_all ?? 0}`}
</div>
<RSListToolbar <RSListToolbar
selectedCount={selected.length} selectedCount={selected.length}
editorMode={isEditable} isMutable={isMutable}
onMoveUp={handleMoveUp} onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown} onMoveDown={handleMoveDown}
onClone={handleClone} onClone={handleClone}
@ -213,7 +210,13 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)} onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
onReindex={handleReindex} onReindex={handleReindex}
/> />
</div> <SelectedCounter
total={schema?.stats?.count_all ?? 0}
selected={selected.length}
position='left-0 top-1'
/>
<div className='pt-[2.3rem] border-b' />
<RSTable <RSTable
items={schema?.items} items={schema?.items}

View File

@ -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,44 +26,46 @@ 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'>
<div>
<MiniButton <MiniButton
tooltip='Добавить пустую конституенту' tooltip='Добавить пустую конституенту'
icon={<ArrowDropdownIcon color={editorMode ? 'text-success': ''} size={5}/>} icon={<ArrowDropdownIcon color={isMutable ? 'text-success': ''} size={5}/>}
disabled={!editorMode} disabled={!isMutable}
onClick={insertMenu.toggle} onClick={insertMenu.toggle}
/> />
{insertMenu.isActive ? {insertMenu.isActive ?
@ -82,30 +84,32 @@ function RSListToolbar({
})} })}
</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>);
} }

View File

@ -33,7 +33,6 @@ function RSTable({
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({}); const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
useLayoutEffect( useLayoutEffect(
() => { () => {
setColumnVisibility({ setColumnVisibility({

View File

@ -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}

View File

@ -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>

View File

@ -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;

View File

@ -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) => {

View File

@ -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';
@ -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 ?

View File

@ -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) ?

View File

@ -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 + '+');
} }
} }