mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
UI: Improve editing experience
This commit is contained in:
parent
bdbf77faa2
commit
4cd8b31b59
|
@ -1,5 +1,5 @@
|
|||
interface FormProps {
|
||||
title: string
|
||||
title?: string
|
||||
dimensions?: string
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||
children: React.ReactNode
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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]'
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 'опосредованные ссылки из текущей';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user