UI: Improve editing experience

This commit is contained in:
IRBorisov 2023-11-05 18:41:28 +03:00
parent bdbf77faa2
commit 4cd8b31b59
13 changed files with 88 additions and 47 deletions

View File

@ -1,5 +1,5 @@
interface FormProps {
title: string
title?: string
dimensions?: string
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
children: React.ReactNode

View File

@ -3,20 +3,20 @@ import InfoCstStatus from './InfoCstStatus';
function HelpConstituenta() {
return (
<div>
<div className='leading-tight'>
<h1>Подсказки</h1>
<p><b className='text-warning'>Изменения сохраняются ПОСЛЕ нажатия на соответствующую кнопку снизу или по центру</b></p>
<p><b>Формальное определение</b> - обратите внимание на кнопки снизу<br/>Горячие клавиши указаны в подсказках при наведении</p>
<p><b>Поля Термин и Определение</b> - Ctrl + Пробел открывает диалог редактирования отсылок<br/>Перед открытием диалога переместите текстовый курсор на заменяемое слово или ссылку</p>
<p><b>Список конституент справа</b> - обратите внимание на настройки фильтрации</p>
<p>- слева от ввода текста настраивается набор атрибутов конституенты</p>
<p>- справа от ввода текста настраивается список конституент, которые фильтруются</p>
<p><b>Сохранить изменения</b>: Ctrl + S или клик по кнопке Сохранить</p>
<p className='mt-1'><b>Формальное определение</b>: обратите внимание на кнопки снизу<br/>Горячие клавиши указаны в подсказках при наведении</p>
<p className='mt-1'><b>Поля Термин и Определение</b>: Ctrl + Пробел открывает диалог редактирования отсылок<br/>Перед открытием диалога переместите текстовый курсор на заменяемое слово или ссылку</p>
<p className='mt-1'><b>Список конституент справа</b>: обратите внимание на настройки фильтрации</p>
<p>- первая настройка - атрибуты конституенты</p>
<p>- вторая настройка - принцип отбора конституент по графу термов</p>
<p>- текущая конституента выделена цветом строки</p>
<p>- двойной клик / Alt + клик - выбор редактируемой конституенты</p>
<p>- при наведении на ID конституенты отображаются ее атрибуты</p>
<p>- при наведении на имя конституенты отображаются ее атрибуты</p>
<p>- столбец "Описание" содержит один из непустых текстовых атрибутов</p>
<Divider margins='mt-2' />
<Divider margins='mt-4' />
<InfoCstStatus title='Статусы' />
</div>);

View File

@ -2,6 +2,7 @@ function HelpRSFormMeta() {
return (
<div>
<h1>Паспорт схемы</h1>
<p><b>Сохранить изменения</b>: Ctrl + S или клик по кнопке Сохранить</p>
<p><b>Владелец</b> - пользователь, обладающий правом редактирования</p>
<p>Для <b>общедоступных</b> схем владельцем может стать любой пользователь</p>
<p>Для <b>неизменных</b> схем правом редактирования обладают только администраторы</p>

View File

@ -19,7 +19,7 @@ function InfoCstClass({ title }: InfoCstClassProps) {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm'
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
style={{backgroundColor: colorbgCstClass(cclass, colors)}}
>
{labelCstClass(cclass)}

View File

@ -21,7 +21,7 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm'
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
style={{backgroundColor: colorbgCstStatus(status, colors)}}
>
{labelExpressionStatus(status)}

View File

@ -104,29 +104,24 @@ function RSInput({
if (event.key === '*') {
text.insertToken(TokenID.DECART);
event.preventDefault();
return;
}
if (event.code === 'KeyB') {
} else if (event.code === 'KeyB') {
text.insertChar('');
event.preventDefault();
return;
} else if (event.code === 'KeyZ') {
text.insertChar('Z');
event.preventDefault();
}
}
if (event.altKey) {
if (!text.processAltKey(event.code, event.shiftKey)) {
return;
} else if (event.altKey) {
if (text.processAltKey(event.code, event.shiftKey)) {
event.preventDefault();
}
} else if (!event.ctrlKey) {
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
if (!newSymbol) {
return;
}
if (newSymbol) {
text.replaceWith(newSymbol);
} else {
return;
}
event.preventDefault();
}
}
}, [thisRef]);
return (

View File

@ -66,7 +66,7 @@ function LoginPage() {
}
return (
<div className='flex items-start justify-center w-full pt-4 select-none' style={{minHeight: mainHeight}}>
<div className='flex items-start justify-center w-full pt-8 select-none' style={{minHeight: mainHeight}}>
{ user &&
<div className='flex flex-col items-center gap-2'>
<p className='font-semibold'>{`Вы вошли в систему как ${user.username}`}</p>
@ -87,10 +87,13 @@ function LoginPage() {
</div>}
{ !user &&
<Form
title='Вход в Портал'
onSubmit={handleSubmit}
dimensions='w-[24rem]'
>
<img alt='Концепт Портал'
src='/logo_full.svg'
className='max-h-[2.5rem] min-w-[2.5rem] mt-2 mb-4'
/>
<TextInput id='username' type='text'
label='Имя пользователя'
required
@ -107,7 +110,7 @@ function LoginPage() {
onChange={event => setPassword(event.target.value)}
/>
<div className='flex justify-center w-full gap-2 py-2'>
<div className='flex justify-center w-full py-2'>
<SubmitButton
text='Войти'
dimensions='w-[12rem]'

View File

@ -6,7 +6,7 @@ import MiniButton from '../../components/Common/MiniButton';
import SubmitButton from '../../components/Common/SubmitButton';
import TextArea from '../../components/Common/TextArea';
import HelpConstituenta from '../../components/Help/HelpConstituenta';
import { CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import { ArrowsRotateIcon, CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import RefsInput from '../../components/RefsInput';
import { useRSForm } from '../../context/RSFormContext';
import useWindowSize from '../../hooks/useWindowSize';
@ -47,6 +47,7 @@ function EditorConstituenta({
const [expression, setExpression] = useState('');
const [convention, setConvention] = useState('');
const [typification, setTypification] = useState('N/A');
const [toggleReset, setToggleReset] = useState(false);
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
@ -77,7 +78,7 @@ function EditorConstituenta({
setExpression(activeCst.definition_formal || '');
setTypification(activeCst ? labelCstTypification(activeCst) : 'N/A');
}
}, [activeCst, onOpenEdit, schema]);
}, [activeCst, onOpenEdit, schema, toggleReset]);
function handleSubmit(event?: React.FormEvent<HTMLFormElement>) {
if (event) {
@ -150,8 +151,17 @@ function EditorConstituenta({
onRenameCst(data);
}
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.ctrlKey && event.code === 'KeyS') {
if (isModified) {
handleSubmit();
}
event.preventDefault();
}
}
return (
<div className='flex max-w-[1500px] gap-2'>
<div className='flex max-w-[1500px] gap-2' tabIndex={0} onKeyDown={handleInput}>
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-[50rem] px-4 py-1'>
<div className='relative w-full'>
<div className='absolute top-0 right-0 flex items-start justify-between w-full'>
@ -184,6 +194,12 @@ function EditorConstituenta({
icon={<SaveIcon size={5} color={isModified && isEnabled ? 'text-primary' : ''}/>}
onClick={() => handleSubmit()}
/>
<MiniButton
tooltip='Сборсить несохраненные изменения'
disabled={!isEnabled || !isModified}
onClick={() => setToggleReset(prev => !prev)}
icon={<ArrowsRotateIcon size={5} color={isEnabled && isModified ? 'text-primary' : ''} />}
/>
<MiniButton
tooltip='Создать конституенту после данной'
disabled={!isEnabled}
@ -234,6 +250,7 @@ function EditorConstituenta({
placeholder='Родоструктурное выражение, задающее формальное определение'
value={expression}
disabled={!isEnabled}
toggleReset={toggleReset}
onShowAST={onShowAST}
onChange={newValue => setExpression(newValue)}
setTypification={setTypification}

View File

@ -24,6 +24,7 @@ interface EditorRSExpressionProps {
activeCst?: IConstituenta
label: string
disabled?: boolean
toggleReset?: boolean
placeholder?: string
onShowAST: (expression: string, ast: SyntaxTree) => void
setTypification: (typificaiton: string) => void
@ -32,7 +33,7 @@ interface EditorRSExpressionProps {
}
function EditorRSExpression({
activeCst, disabled, value, onShowAST,
activeCst, disabled, value, onShowAST, toggleReset,
setTypification, onChange, ...props
}: EditorRSExpressionProps) {
const { schema } = useRSForm();
@ -44,7 +45,7 @@ function EditorRSExpression({
useLayoutEffect(() => {
setIsModified(false);
resetParse();
}, [activeCst, resetParse]);
}, [activeCst, resetParse, toggleReset]);
function handleChange(newvalue: string) {
onChange(newvalue);

View File

@ -69,8 +69,10 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
}
}, [schema]);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
if (event) {
event.preventDefault();
}
const data: IRSFormCreateData = {
item_type: LibraryItemType.RSFORM,
title: title,
@ -82,8 +84,17 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
update(data, () => toast.success('Изменения сохранены'));
};
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.ctrlKey && event.code === 'KeyS') {
if (isModified) {
handleSubmit();
}
event.preventDefault();
}
}
return (
<div>
<div tabIndex={0} onKeyDown={handleInput}>
<div className='relative flex items-start justify-center w-full'>
<div className='absolute flex mt-1'>
<MiniButton

View File

@ -66,12 +66,14 @@ function EditorPassword() {
<TextInput id='old_password'
type='password'
label='Старый пароль'
allowEnter
value={oldPassword}
onChange={event => setOldPassword(event.target.value)}
/>
<TextInput id='new_password' type='password'
colors={passwordColor}
label='Новый пароль'
allowEnter
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
@ -80,6 +82,7 @@ function EditorPassword() {
<TextInput id='new_password_repeat' type='password'
colors={passwordColor}
label='Повторите новый'
allowEnter
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);

View File

@ -55,17 +55,27 @@ function EditorProfile() {
<TextInput id='username'
label='Логин'
tooltip='Логин изменить нельзя'
disabled={true}
disabled
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='first_name'
label='Имя'
value={first_name}
allowEnter
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name' label='Фамилия' value={last_name} onChange={event => setLastName(event.target.value)}/>
<TextInput id='email' label='Электронная почта' value={email} onChange={event => setEmail(event.target.value)}/>
<TextInput id='last_name'
label='Фамилия'
value={last_name}
allowEnter
onChange={event => setLastName(event.target.value)}
/>
<TextInput id='email'
label='Электронная почта'
allowEnter
value={email}
onChange={event => setEmail(event.target.value)}
/>
</div>
<div className='flex justify-center w-full'>
<SubmitButton

View File

@ -152,8 +152,8 @@ export function labelCstSource(mode: DependencyMode): string {
case DependencyMode.EXPRESSION: return 'выражение';
case DependencyMode.OUTPUTS: return 'потребители';
case DependencyMode.INPUTS: return 'поставщики';
case DependencyMode.EXPAND_INPUTS: return 'влияющие';
case DependencyMode.EXPAND_OUTPUTS: return 'зависимые';
case DependencyMode.EXPAND_INPUTS: return 'влияющие';
}
}
@ -161,10 +161,10 @@ export function describeCstSource(mode: DependencyMode): string {
switch (mode) {
case DependencyMode.ALL: return 'все конституенты';
case DependencyMode.EXPRESSION: return 'идентификаторы из выражения';
case DependencyMode.OUTPUTS: return 'конституенты, ссылающиеся на данную';
case DependencyMode.INPUTS: return 'конституенты, на которые ссылается данная';
case DependencyMode.EXPAND_INPUTS: return 'конституенты, зависящие по цепочке';
case DependencyMode.EXPAND_OUTPUTS: return 'конституенты, влияющие на данную по цепочке';
case DependencyMode.OUTPUTS: return 'прямые ссылки на текущую';
case DependencyMode.INPUTS: return 'пярмые ссылки из текущей';
case DependencyMode.EXPAND_OUTPUTS: return 'опосредованные ссылки на текущую';
case DependencyMode.EXPAND_INPUTS: return 'опосредованные ссылки из текущей';
}
}