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) {
return (
<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}
</div>
</div>

View File

@ -1,7 +1,9 @@
import { useRef } from 'react';
import useEscapeKey from '../../hooks/useEscapeKey';
import { CrossIcon } from '../Icons';
import Button from './Button';
import MiniButton from './MiniButton';
export interface ModalProps {
title?: string
@ -35,16 +37,29 @@ function Modal({
if (onSubmit) onSubmit();
};
return (<>
return (
<>
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
<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='max-h-[calc(100vh-8rem)] overflow-auto px-2'>
<div className='relative'>
<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}
</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 ?
<Button autoFocus
text={submitText}

View File

@ -1,9 +1,15 @@
function HelpRSTemplates() {
return (
<div>
<div className='flex flex-col w-full gap-2 pb-2'>
<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>);
}

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
processing: boolean
editorMode: boolean
isMutable: boolean
adminMode: boolean
isOwned: 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;
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
const editorMode = useMemo(
const isMutable = useMemo(
() => {
return (
!loading && !processing && !isReadonly &&
@ -322,7 +322,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
<RSFormContext.Provider value={{
schema,
error, loading, processing,
adminMode, isReadonly, isOwned, editorMode,
adminMode, isReadonly, isOwned, isMutable,
isClaimable, isTracking,
toggleForceAdmin: () => setAdminMode(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>
<ConceptTooltip
anchorSelect='#templates-help'
className='max-w-[30rem] z-modal-tooltip'
className='max-w-[35rem] z-modal-tooltip'
offset={10}
>
<HelpRSTemplates />

View File

@ -54,10 +54,10 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
onSubmit={handleSubmit}
submitText='Создать'
>
<div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch gap-3'>
<div className='h-fit w-[35rem] px-2 my-2 flex flex-col justify-stretch gap-3'>
<div className='flex justify-center w-full gap-6'>
<SelectSingle
className='my-2 min-w-[15rem] self-center'
className='min-w-[15rem] self-center'
placeholder='Выберите тип'
options={SelectorCstType}
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 })}
/>
</div>
</Modal>
);
</Modal>);
}
export default DlgCreateCst;

View File

@ -35,7 +35,7 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
canSubmit={true}
onSubmit={handleSubmit}
>
<div className='max-w-[60vw] min-w-[20rem]'>
<div className='max-w-[60vw] min-w-[30rem]'>
<p>Выбраны к удалению: <b>{selected.length}</b></p>
<div className='px-3 border h-[9rem] mt-1 overflow-y-auto whitespace-nowrap'>
{selected.map(
@ -64,8 +64,7 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
setValue={value => setExpandOut(value)}
/>
</div>
</Modal>
);
</Modal>);
}
export default DlgDeleteCst;

View File

@ -207,7 +207,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
<Modal
title='Редактирование словоформ'
hideWindow={hideWindow}
submitText='Сохранить данные'
submitText='Сохранить'
canSubmit
onSubmit={handleSubmit}
>
@ -237,11 +237,12 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
spellCheck
/>
<div className='mt-4 mb-2 text-sm font-semibold'>
<div className='mt-3 mb-2 text-sm font-semibold'>
Параметры словоформы
</div>
<div className='flex items-start justify-between w-full'>
<div className='flex items-center'>
<TextArea
placeholder='Введите текст'
rows={2}
@ -252,39 +253,36 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
<div className='max-w-min'>
<MiniButton
tooltip='Генерировать словоформу'
icon={<ArrowLeftIcon
size={6}
color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}
/>}
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={6}
size={5}
color={!inputText ? 'text-disabled' : 'text-primary'}
/>}
disabled={textProcessor.loading || !inputText}
onClick={handleParse}
/>
</div>
</div>
<SelectMulti
className='min-w-[20rem] max-w-[20rem] h-full flex-grow'
options={options}
placeholder='Выберите граммемы'
value={inputGrams}
onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
/>
</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'>
<MiniButton
tooltip='Внести словоформу'
icon={<CheckIcon
size={6}
size={5}
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}
/>}
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
@ -293,22 +291,19 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
<MiniButton
tooltip='Генерировать все словоформы'
icon={<ChevronDoubleDownIcon
size={6}
size={5}
color={!inputText ? 'text-disabled' : 'text-primary'}
/>}
disabled={textProcessor.loading || !inputText}
onClick={handleGenerateLexeme}
/>
</div>
<div className='w-full mt-2 mb-1 text-sm font-semibold text-center'>
Заданные вручную словоформы: [{forms.length}]
<div className='w-full text-sm font-semibold text-center'>
Заданные вручную словоформы [{forms.length}]
</div>
<MiniButton
tooltip='Сбросить ВСЕ словоформы'
icon={<CrossIcon
size={6}
color={forms.length === 0 ? 'text-disabled' : 'text-warning'}
/>}
icon={<CrossIcon size={5} color={forms.length === 0 ? 'text-disabled' : 'text-warning'} />}
disabled={textProcessor.loading || forms.length === 0}
onClick={handleResetAll}
/>

View File

@ -14,10 +14,10 @@ extends Pick<ModalProps, 'hideWindow'> {
function DlgGraphParams({ hideWindow, initial, onConfirm } : DlgGraphParamsProps) {
const [params, updateParams] = usePartialUpdate(initial);
const handleSubmit = () => {
function handleSubmit() {
hideWindow();
onConfirm(params);
};
}
return (
<Modal canSubmit
@ -26,7 +26,7 @@ function DlgGraphParams({ hideWindow, initial, onConfirm } : DlgGraphParamsProps
onSubmit={handleSubmit}
submitText='Применить'
>
<div className='flex gap-2'>
<div className='flex gap-6 my-2'>
<div className='flex flex-col gap-1'>
<h1>Преобразования</h1>
<Checkbox

View File

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

View File

@ -57,7 +57,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
hideWindow={hideWindow}
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'>
{!hoverNode ? expression : null}
{hoverNode ?
@ -87,8 +87,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
</div>
</div>
</div>
</Modal>
);
</Modal>);
}
export default DlgShowAST;

View File

@ -303,6 +303,13 @@
}
}
.text-default {
color: var(--cl-fg-100);
&.dark {
color: var(--cd-fg-100);
}
}
.text-warning {
color: var(--cl-red-fg-100);
.dark & {

View File

@ -6,7 +6,7 @@ import HelpConstituenta from '../../../components/Help/HelpConstituenta'
import { ArrowsRotateIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../../components/Icons'
interface ConstituentaToolbarProps {
editorMode: boolean
isMutable: boolean
isModified: boolean
onSubmit: () => void
@ -19,11 +19,11 @@ interface ConstituentaToolbarProps {
}
function ConstituentaToolbar({
editorMode, isModified,
isMutable, isModified,
onSubmit, onReset,
onDelete, onClone, onCreate, onTemplates
}: ConstituentaToolbarProps) {
const canSave = useMemo(() => (isModified && editorMode), [isModified, editorMode]);
const canSave = useMemo(() => (isModified && isMutable), [isModified, isMutable]);
return (
<div className='relative w-full'>
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
@ -42,27 +42,27 @@ function ConstituentaToolbar({
/>
<MiniButton
tooltip='Создать конституенту после данной'
disabled={!editorMode}
disabled={!isMutable}
onClick={onCreate}
icon={<SmallPlusIcon size={5} color={editorMode ? 'text-success' : ''} />}
icon={<SmallPlusIcon size={5} color={isMutable ? 'text-success' : ''} />}
/>
<MiniButton
tooltip='Клонировать конституенту'
disabled={!editorMode}
disabled={!isMutable}
onClick={onClone}
icon={<CloneIcon size={5} color={editorMode ? 'text-success' : ''} />}
icon={<CloneIcon size={5} color={isMutable ? 'text-success' : ''} />}
/>
<MiniButton
tooltip='Создать конституенту из шаблона'
icon={<DiamondIcon color={editorMode ? 'text-primary': ''} size={5}/>}
disabled={!editorMode}
icon={<DiamondIcon color={isMutable ? 'text-primary': ''} size={5}/>}
disabled={!isMutable}
onClick={onTemplates}
/>
<MiniButton
tooltip='Удалить редактируемую конституенту'
disabled={!editorMode}
disabled={!isMutable}
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'>
<HelpIcon color='text-primary' size={5} />

View File

@ -35,11 +35,11 @@ function EditorConstituenta({
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
}: EditorConstituentaProps) {
const windowSize = useWindowSize();
const { schema, editorMode } = useRSForm();
const { schema, isMutable } = useRSForm();
const [toggleReset, setToggleReset] = useState(false);
const readyForEdit = useMemo(() => (!!activeCst && editorMode), [activeCst, editorMode]);
const readyForEdit = useMemo(() => (!!activeCst && isMutable), [activeCst, isMutable]);
function handleDelete() {
if (!schema || !activeID) {
@ -103,7 +103,7 @@ function EditorConstituenta({
onKeyDown={handleInput}
>
<ConstituentaToolbar
editorMode={readyForEdit}
isMutable={readyForEdit}
isModified={isModified}
onSubmit={initiateSubmit}

View File

@ -30,9 +30,9 @@ function FormConstituenta({
constituenta, toggleReset,
onRenameCst, onShowAST, onEditTerm
}: 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 [term, setTerm] = useState('');

View File

@ -19,7 +19,7 @@ interface EditorRSFormProps {
}
function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified, onDownload }: EditorRSFormProps) {
const { schema, editorMode: isEditable, isClaimable } = useRSForm();
const { schema, isMutable, isClaimable } = useRSForm();
const { user } = useAuth();
function initiateSubmit() {
@ -41,7 +41,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
return (
<div tabIndex={-1} onKeyDown={handleInput}>
<RSFormToolbar
editorMode={isEditable}
isMutable={isMutable}
modified={isModified}
claimable={isClaimable}
anonymous={!user}

View File

@ -21,7 +21,7 @@ function FormRSForm({
}: FormRSFormProps) {
const {
schema, update, adminMode: adminMode,
editorMode: editorMode, processing
isMutable: isMutable, processing
} = useRSForm();
const [title, setTitle] = useState('');
@ -82,20 +82,20 @@ function FormRSForm({
<TextInput required
label='Полное название'
value={title}
disabled={!editorMode}
disabled={!isMutable}
onChange={event => setTitle(event.target.value)}
/>
<TextInput required dense
label='Сокращение'
dimensions='w-full'
disabled={!editorMode}
disabled={!isMutable}
value={alias}
onChange={event => setAlias(event.target.value)}
/>
<TextArea
label='Комментарий'
value={comment}
disabled={!editorMode}
disabled={!isMutable}
onChange={event => setComment(event.target.value)}
/>
<div className='flex justify-between whitespace-nowrap'>
@ -103,7 +103,7 @@ function FormRSForm({
label='Общедоступная схема'
tooltip='Общедоступные схемы видны всем пользователям и могут быть изменены'
dimensions='w-fit'
disabled={!editorMode}
disabled={!isMutable}
value={common}
setValue={value => setCommon(value)}
/>
@ -111,7 +111,7 @@ function FormRSForm({
label='Неизменная схема'
tooltip='Только администраторы могут присваивать схемам неизменный статус'
dimensions='w-fit'
disabled={!editorMode || !adminMode}
disabled={!isMutable || !adminMode}
value={canonical}
setValue={value => setCanonical(value)}
/>
@ -120,7 +120,7 @@ function FormRSForm({
<SubmitButton
text='Сохранить изменения'
loading={processing}
disabled={!isModified || !editorMode}
disabled={!isModified || !isMutable}
icon={<SaveIcon size={6} />}
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'
interface RSFormToolbarProps {
editorMode: boolean
isMutable: boolean
modified: boolean
claimable: boolean
anonymous: boolean
@ -19,11 +19,11 @@ interface RSFormToolbarProps {
}
function RSFormToolbar({
editorMode, modified, claimable, anonymous,
isMutable, modified, claimable, anonymous,
onSubmit, onShare, onDownload,
onClaim, onDestroy
}: RSFormToolbarProps) {
const canSave = useMemo(() => (modified && editorMode), [modified, editorMode]);
const canSave = useMemo(() => (modified && isMutable), [modified, isMutable]);
return (
<div className='relative flex items-start justify-center w-full'>
<div className='absolute flex mt-1'>
@ -51,9 +51,9 @@ function RSFormToolbar({
/>
<MiniButton
tooltip='Удалить схему'
disabled={!editorMode}
disabled={!isMutable}
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'>
<HelpIcon color='text-primary' size={5} />

View File

@ -2,6 +2,7 @@ import { useLayoutEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { type RowSelectionState } from '../../../components/DataTable';
import SelectedCounter from '../../../components/Shared/SelectedCounter';
import { useRSForm } from '../../../context/RSFormContext';
import { CstType, ICstCreateData, ICstMovetoData } from '../../../models/rsform'
import RSListToolbar from './RSListToolbar';
@ -15,7 +16,7 @@ interface 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 [rowSelection, setRowSelection] = useState<RowSelectionState>({});
@ -154,7 +155,7 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
// Implement hotkeys for working with constituents table
function handleTableKey(event: React.KeyboardEvent<HTMLDivElement>) {
if (!isEditable) {
if (!isMutable) {
return;
}
if (event.key === 'Delete' && selected.length > 0) {
@ -198,13 +199,9 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
className='w-full outline-none'
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
selectedCount={selected.length}
editorMode={isEditable}
isMutable={isMutable}
onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown}
onClone={handleClone}
@ -213,7 +210,13 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
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
items={schema?.items}

View File

@ -13,7 +13,7 @@ import { labelCstType } from '../../../utils/labels';
import { getCstTypePrefix, getCstTypeShortcut } from '../../../utils/misc';
interface RSListToolbarProps {
editorMode?: boolean
isMutable?: boolean
selectedCount: number
onMoveUp: () => void
@ -26,44 +26,46 @@ interface RSListToolbarProps {
}
function RSListToolbar({
selectedCount, editorMode,
onMoveUp, onMoveDown, onDelete, onClone, onCreate, onTemplates, onReindex
selectedCount, isMutable,
onMoveUp, onMoveDown, onDelete, onClone,
onCreate, onTemplates, onReindex
}: RSListToolbarProps) {
const insertMenu = useDropdown();
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
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
tooltip='Переместить вверх [Alt + вверх]'
icon={<ArrowUpIcon size={5}/>}
disabled={!editorMode || nothingSelected}
disabled={!isMutable || nothingSelected}
onClick={onMoveUp}
/>
<MiniButton
tooltip='Переместить вниз [Alt + вниз]'
icon={<ArrowDownIcon size={5}/>}
disabled={!editorMode || nothingSelected}
disabled={!isMutable || nothingSelected}
onClick={onMoveDown}
/>
<MiniButton
tooltip='Клонировать конституенту [Alt + V]'
icon={<CloneIcon color={editorMode && selectedCount === 1 ? 'text-success': ''} size={5}/>}
disabled={!editorMode || selectedCount !== 1}
icon={<CloneIcon color={isMutable && selectedCount === 1 ? 'text-success': ''} size={5}/>}
disabled={!isMutable || selectedCount !== 1}
onClick={onClone}
/>
<MiniButton
tooltip='Добавить новую конституенту... [Alt + `]'
icon={<SmallPlusIcon color={editorMode ? 'text-success': ''} size={5}/>}
disabled={!editorMode}
icon={<SmallPlusIcon color={isMutable ? 'text-success': ''} size={5}/>}
disabled={!isMutable}
onClick={() => onCreate()}
/>
<div ref={insertMenu.ref} className='flex justify-center'>
<div>
<MiniButton
tooltip='Добавить пустую конституенту'
icon={<ArrowDropdownIcon color={editorMode ? 'text-success': ''} size={5}/>}
disabled={!editorMode}
icon={<ArrowDropdownIcon color={isMutable ? 'text-success': ''} size={5}/>}
disabled={!isMutable}
onClick={insertMenu.toggle}
/>
{insertMenu.isActive ?
@ -82,30 +84,32 @@ function RSListToolbar({
})}
</Dropdown> : null}
</div>
</div>
<MiniButton
tooltip='Создать конституенту из шаблона'
icon={<DiamondIcon color={editorMode ? 'text-primary': ''} size={5}/>}
disabled={!editorMode}
icon={<DiamondIcon color={isMutable ? 'text-primary': ''} size={5}/>}
disabled={!isMutable}
onClick={onTemplates}
/>
<MiniButton
tooltip='Сброс имен: присвоить порядковые имена'
icon={<UpdateIcon color={editorMode ? 'text-primary': ''} size={5}/>}
disabled={!editorMode}
icon={<UpdateIcon color={isMutable ? 'text-primary': ''} size={5}/>}
disabled={!isMutable}
onClick={onReindex}
/>
<MiniButton
tooltip='Удалить выбранные [Delete]'
icon={<DumpBinIcon color={editorMode && !nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!editorMode || nothingSelected}
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isMutable || nothingSelected}
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} />
</div>
<ConceptTooltip anchorSelect='#items-table-help' offset={8}>
<HelpRSFormItems />
</ConceptTooltip>
</div>
</div>);
}

View File

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

View File

@ -2,6 +2,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { GraphEdge, GraphNode, LayoutTypes } from 'reagraph';
import InfoConstituenta from '../../../components/Shared/InfoConstituenta';
import SelectedCounter from '../../../components/Shared/SelectedCounter';
import { useRSForm } from '../../../context/RSFormContext';
import { useConceptTheme } from '../../../context/ThemeContext';
import DlgGraphParams from '../../../dialogs/DlgGraphParams';
@ -12,7 +13,6 @@ import { colorbgGraphNode } from '../../../utils/color';
import { TIMEOUT_GRAPH_REFRESH } from '../../../utils/constants';
import GraphSidebar from './GraphSidebar';
import GraphToolbar from './GraphToolbar';
import SelectedCounter from './SelectedCounter';
import TermGraph from './TermGraph';
import useGraphFilter from './useGraphFilter';
import ViewHidden from './ViewHidden';
@ -24,7 +24,7 @@ interface EditorTermGraphProps {
}
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
const { schema, editorMode: isEditable } = useRSForm();
const { schema, isMutable } = useRSForm();
const { colors } = useConceptTheme();
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
@ -179,7 +179,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
// Hotkeys implementation
if (!isEditable) {
if (!isMutable) {
return;
}
if (event.key === 'Delete') {
@ -197,13 +197,14 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
onConfirm={handleChangeParams}
/> : null}
<SelectedCounter
<SelectedCounter hideZero
total={schema?.stats?.count_all ?? 0}
selected={selected.length}
position='top-[0.3rem] left-0'
/>
<GraphToolbar
editorMode={isEditable}
isMutable={isMutable}
nothingSelected={nothingSelected}
is3D={is3D}
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'
interface GraphToolbarProps {
editorMode: boolean
isMutable: boolean
nothingSelected: boolean
is3D: boolean
@ -21,7 +21,7 @@ interface GraphToolbarProps {
}
function GraphToolbar({
editorMode, nothingSelected, is3D,
isMutable, nothingSelected, is3D,
noText, toggleNoText,
orbit, toggleOrbit,
showParamsDialog,
@ -33,7 +33,8 @@ function GraphToolbar({
<MiniButton
tooltip='Настройки фильтрации узлов и связей'
icon={<FilterIcon color='text-primary' size={5} />}
onClick={showParamsDialog} />
onClick={showParamsDialog}
/>
<MiniButton
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
icon={
@ -41,26 +42,31 @@ function GraphToolbar({
? <LetterALinesIcon color='text-success' size={5} />
: <LetterAIcon color='text-primary' size={5} />
}
onClick={toggleNoText} />
onClick={toggleNoText}
/>
<MiniButton
tooltip='Новая конституента'
icon={<SmallPlusIcon color={editorMode ? 'text-success' : ''} size={5} />}
disabled={!editorMode}
onClick={onCreate} />
icon={<SmallPlusIcon color={isMutable ? 'text-success' : ''} size={5} />}
disabled={!isMutable}
onClick={onCreate}
/>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={editorMode && !nothingSelected ? 'text-warning' : ''} size={5} />}
disabled={!editorMode || nothingSelected}
onClick={onDelete} />
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'text-warning' : ''} size={5} />}
disabled={!isMutable || nothingSelected}
onClick={onDelete}
/>
<MiniButton
icon={<ArrowsFocusIcon color='text-primary' size={5} />}
tooltip='Восстановить камеру'
onClick={onResetViewpoint} />
onClick={onResetViewpoint}
/>
<MiniButton
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'text-success' : 'text-primary'} size={5} />}
tooltip='Анимация вращения'
disabled={!is3D}
onClick={toggleOrbit} />
onClick={toggleOrbit}
/>
<div className='px-1 py-1' id='items-graph-help'>
<HelpIcon color='text-primary' size={5} />
</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 (
<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 }}>
{items.map(
(cstID) => {

View File

@ -13,7 +13,7 @@ import { useLibrary } from '../../context/LibraryContext';
import { useConceptNavigation } from '../../context/NagivationContext';
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import DlgCloneRSForm from '../../dialogs/DlgCloneRSForm';
import DlgCloneLibraryItem from '../../dialogs/DlgCloneLibraryItem';
import DlgConstituentaTemplate from '../../dialogs/DlgConstituentaTemplate';
import DlgCreateCst from '../../dialogs/DlgCreateCst';
import DlgDeleteCst from '../../dialogs/DlgDeleteCst';
@ -337,7 +337,8 @@ function RSTabs() {
hideWindow={() => setShowUpload(false)}
/> : null}
{showClone ?
<DlgCloneRSForm
<DlgCloneLibraryItem
base={schema!}
hideWindow={() => setShowClone(false)}
/> : null}
{showAST ?

View File

@ -30,7 +30,7 @@ function RSTabsMenu({
const navigate = useNavigate();
const { user } = useAuth();
const {
isOwned, editorMode: isEditable, isTracking, isReadonly, isClaimable, adminMode: isForceAdmin,
isOwned, isMutable, isTracking, isReadonly, isClaimable, adminMode: isForceAdmin,
toggleForceAdmin, toggleReadonly, processing
} = useRSForm();
const schemaMenu = useDropdown();
@ -100,15 +100,15 @@ function RSTabsMenu({
<p>Выгрузить в Экстеор</p>
</div>
</DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleUpload}>
<DropdownButton disabled={!isMutable} onClick={handleUpload}>
<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>
</div>
</DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleDelete}>
<DropdownButton disabled={!isMutable} onClick={handleDelete}>
<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>
</span>
</DropdownButton>
@ -122,10 +122,10 @@ function RSTabsMenu({
</div>
<div ref={editMenu.ref}>
<Button dense noBorder tabIndex={-1}
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
tooltip={'измнение: ' + (isMutable ? '[доступно]' : '[запрещено]')}
dimensions='h-full w-fit'
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}
/>
{editMenu.isActive ?
@ -135,14 +135,14 @@ function RSTabsMenu({
onClick={!isOwned ? handleClaimOwner : undefined}
tooltip={!user || !isClaimable ? 'Взять во владение можно общую изменяемую схему' : ''}
>
<div className='flex items-center gap-2 pl-1'>
<div className='flex items-center gap-2 pl-1 text-default'>
<span>
<OwnerIcon size={5} color={isOwned ? 'text-success' : 'text-controls'} />
<OwnerIcon size={4} color={isOwned ? 'text-success' : 'text-controls'} />
</span>
<p>
{isOwned ? <b>Владелец схемы</b> : null}
<div>
{isOwned ? <b className='text-default'>Вы владелец</b> : null}
{!isOwned ? <b>Стать владельцем</b> : null}
</p>
</div>
</div>
</DropdownButton>
{(isOwned || user?.is_staff) ?

View File

@ -2,6 +2,7 @@
* Module: miscellaneous static functions to generate UI resources.
*/
import { ILibraryItem } from '../models/library';
import { CstType, IConstituenta, IRSForm } from '../models/rsform';
import { IRSErrorDescription, RSErrorClass } from '../models/rslang';
import { inferErrorClass } from '../models/rslangAPI';
@ -61,11 +62,11 @@ export function createAliasFor(type: CstType, schema: IRSForm): string {
return `${prefix}${index}`;
}
export function cloneTitle(schema: IRSForm): string {
if (!schema.title.includes('[клон]')) {
return schema.title + ' [клон]';
export function cloneTitle(target: ILibraryItem): string {
if (!target.title.includes('[клон]')) {
return target.title + ' [клон]';
} else {
return (schema.title + '+');
return (target.title + '+');
}
}