mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
UI improvements
This commit is contained in:
parent
852f32e085
commit
99f9bdb856
|
@ -1,3 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { labelCstTypification } from '@/utils/labels';
|
||||
|
||||
|
@ -7,9 +9,9 @@ interface InfoConstituentaProps extends CProps.Div {
|
|||
data: IConstituenta;
|
||||
}
|
||||
|
||||
function InfoConstituenta({ data, ...restProps }: InfoConstituentaProps) {
|
||||
function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) {
|
||||
return (
|
||||
<div {...restProps}>
|
||||
<div className={clsx('dense', className)} {...restProps}>
|
||||
<h2>Конституента {data.alias}</h2>
|
||||
<p>
|
||||
<b>Типизация: </b>
|
||||
|
|
|
@ -14,7 +14,7 @@ function InfoCstClass({ header }: InfoCstClassProps) {
|
|||
const { colors } = useConceptTheme();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-1 mb-2'>
|
||||
<div className='flex flex-col gap-1 mb-2 dense'>
|
||||
{header ? <h1>{header}</h1> : null}
|
||||
{Object.values(CstClass).map((cstClass, index) => {
|
||||
return (
|
||||
|
|
|
@ -14,7 +14,7 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
|
|||
const { colors } = useConceptTheme();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-1 mb-2'>
|
||||
<div className='flex flex-col gap-1 mb-2 dense'>
|
||||
{title ? <h1>{title}</h1> : null}
|
||||
{Object.values(ExpressionStatus)
|
||||
.filter(status => status !== ExpressionStatus.UNDEFINED)
|
||||
|
|
|
@ -7,14 +7,29 @@ import { CProps } from '../props';
|
|||
interface AnimateFadeProps extends CProps.AnimatedDiv {
|
||||
noFadeIn?: boolean;
|
||||
noFadeOut?: boolean;
|
||||
removeContent?: boolean;
|
||||
hideContent?: boolean;
|
||||
}
|
||||
|
||||
function AnimateFade({ noFadeIn, noFadeOut, children, ...restProps }: AnimateFadeProps) {
|
||||
function AnimateFade({
|
||||
style,
|
||||
noFadeIn,
|
||||
noFadeOut,
|
||||
children,
|
||||
removeContent,
|
||||
hideContent,
|
||||
...restProps
|
||||
}: AnimateFadeProps) {
|
||||
if (removeContent) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ ...(!noFadeIn ? animateFade.initial : {}) }}
|
||||
animate={{ ...animateFade.animate }}
|
||||
animate={hideContent ? 'hidden' : 'active'}
|
||||
variants={animateFade.variants}
|
||||
exit={{ ...(!noFadeOut ? animateFade.exit : {}) }}
|
||||
style={{ display: hideContent ? 'none' : '', ...style }}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { AnimatePresence } from 'framer-motion';
|
||||
|
||||
import AnimateFade from './AnimateFade';
|
||||
import InfoError, { ErrorData } from '../info/InfoError';
|
||||
import { CProps } from '../props';
|
||||
import Loader from '../ui/Loader';
|
||||
import AnimateFade from './AnimateFade';
|
||||
|
||||
interface DataLoaderProps extends CProps.AnimatedDiv {
|
||||
id: string;
|
||||
|
@ -20,16 +20,12 @@ function DataLoader({ id, isLoading, hasNoData, error, children, ...restProps }:
|
|||
<AnimatePresence mode='wait'>
|
||||
{isLoading ? <Loader key={`${id}-loader`} /> : null}
|
||||
{error ? <InfoError key={`${id}-error`} error={error} /> : null}
|
||||
{!isLoading && !error && !hasNoData ? (
|
||||
<AnimateFade id={id} key={`${id}-data`} {...restProps}>
|
||||
{children}
|
||||
</AnimateFade>
|
||||
) : null}
|
||||
{!isLoading && !error && hasNoData ? (
|
||||
<AnimateFade id={id} key={`${id}-data`} {...restProps}>
|
||||
Данные не загружены
|
||||
</AnimateFade>
|
||||
) : null}
|
||||
<AnimateFade id={id} key={`${id}-data`} removeContent={isLoading || !!error || hasNoData} {...restProps}>
|
||||
{children}
|
||||
</AnimateFade>
|
||||
<AnimateFade id={id} key={`${id}-data`} removeContent={isLoading || !!error || !hasNoData} {...restProps}>
|
||||
Данные не загружены
|
||||
</AnimateFade>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ export function applyFilterCategory(start: IConstituenta, schema: IRSFormData):
|
|||
/**
|
||||
* Prefix for alias indicating {@link CstType}.
|
||||
*/
|
||||
export function getCstTypePrefix(type: CstType) {
|
||||
export function getCstTypePrefix(type: CstType): string {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.BASE: return 'X';
|
||||
|
@ -256,7 +256,7 @@ export function getCstTypePrefix(type: CstType) {
|
|||
/**
|
||||
* Guess {@link CstType} from user input hint.
|
||||
*/
|
||||
export function guessCstType(hint: string, defaultType: CstType = CstType.TERM) {
|
||||
export function guessCstType(hint: string, defaultType: CstType = CstType.TERM): CstType {
|
||||
if (hint.length !== 1) {
|
||||
return defaultType;
|
||||
}
|
||||
|
@ -274,6 +274,57 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM)
|
|||
return defaultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate if {@link CstType} is basic concept.
|
||||
*/
|
||||
export function isBasicConcept(type: CstType): boolean {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.BASE: return true;
|
||||
case CstType.CONSTANT: return true;
|
||||
case CstType.STRUCTURED: return true;
|
||||
case CstType.AXIOM: return true;
|
||||
case CstType.TERM: return false;
|
||||
case CstType.FUNCTION: return false;
|
||||
case CstType.PREDICATE: return false;
|
||||
case CstType.THEOREM: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate if {@link CstType} is base set or constant set.
|
||||
*/
|
||||
export function isBaseSet(type: CstType): boolean {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.BASE: return true;
|
||||
case CstType.CONSTANT: return true;
|
||||
case CstType.STRUCTURED: return false;
|
||||
case CstType.AXIOM: return false;
|
||||
case CstType.TERM: return false;
|
||||
case CstType.FUNCTION: return false;
|
||||
case CstType.PREDICATE: return false;
|
||||
case CstType.THEOREM: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate if {@link CstType} is function.
|
||||
*/
|
||||
export function isFunctional(type: CstType): boolean {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.BASE: return false;
|
||||
case CstType.CONSTANT: return false;
|
||||
case CstType.STRUCTURED: return false;
|
||||
case CstType.AXIOM: return false;
|
||||
case CstType.TERM: return false;
|
||||
case CstType.FUNCTION: return true;
|
||||
case CstType.PREDICATE: return true;
|
||||
case CstType.THEOREM: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate new alias against {@link CstType} and {@link IRSForm}.
|
||||
*/
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import RefsInput from '@/components/RefsInput';
|
||||
import SubmitButton from '@/components/ui/SubmitButton';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { IConstituenta, ICstUpdateData } from '@/models/rsform';
|
||||
import { CstType, IConstituenta, ICstUpdateData } from '@/models/rsform';
|
||||
import { isBaseSet, isBasicConcept, isFunctional } from '@/models/rsformAPI';
|
||||
import { labelCstTypification } from '@/utils/labels';
|
||||
|
||||
import EditorRSExpression from '../EditorRSExpression';
|
||||
|
@ -40,9 +43,11 @@ function FormConstituenta({
|
|||
disabled,
|
||||
showList,
|
||||
id,
|
||||
constituenta,
|
||||
|
||||
isModified,
|
||||
setIsModified,
|
||||
constituenta,
|
||||
|
||||
toggleReset,
|
||||
onRename,
|
||||
onEditTerm,
|
||||
|
@ -56,6 +61,14 @@ function FormConstituenta({
|
|||
const [expression, setExpression] = useState('');
|
||||
const [convention, setConvention] = useState('');
|
||||
const [typification, setTypification] = useState('N/A');
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
|
||||
const isBasic = useMemo(() => !!constituenta && isBasicConcept(constituenta.cst_type), [constituenta]);
|
||||
const isElementary = useMemo(() => !!constituenta && isBaseSet(constituenta.cst_type), [constituenta]);
|
||||
const showConvention = useMemo(
|
||||
() => !constituenta || !!constituenta.convention || forceComment || isBasic,
|
||||
[constituenta, forceComment, isBasic]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!constituenta) {
|
||||
|
@ -90,6 +103,7 @@ function FormConstituenta({
|
|||
setTextDefinition(constituenta.definition_raw || '');
|
||||
setExpression(constituenta.definition_formal || '');
|
||||
setTypification(constituenta ? labelCstTypification(constituenta) : 'N/A');
|
||||
setForceComment(false);
|
||||
}
|
||||
}, [constituenta, schema, toggleReset]);
|
||||
|
||||
|
@ -126,73 +140,101 @@ function FormConstituenta({
|
|||
className={clsx('cc-column', 'mt-1 w-full md:w-[47.8rem] shrink-0', 'px-4 py-1')}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<RefsInput
|
||||
id='cst_term'
|
||||
label='Термин'
|
||||
placeholder='Обозначение, используемое в текстовых определениях'
|
||||
items={schema?.items}
|
||||
value={term}
|
||||
initialValue={constituenta?.term_raw ?? ''}
|
||||
resolved={constituenta?.term_resolved ?? ''}
|
||||
disabled={disabled}
|
||||
onChange={newValue => setTerm(newValue)}
|
||||
/>
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
dense
|
||||
noBorder
|
||||
disabled={true}
|
||||
label='Типизация'
|
||||
rows={typification.length > ROW_SIZE_IN_CHARACTERS ? 2 : 1}
|
||||
value={typification}
|
||||
colors='clr-app'
|
||||
style={{
|
||||
resize: 'none'
|
||||
}}
|
||||
/>
|
||||
<EditorRSExpression
|
||||
id='cst_expression'
|
||||
label='Формальное определение'
|
||||
placeholder='Родоструктурное выражение'
|
||||
value={expression}
|
||||
activeCst={constituenta}
|
||||
showList={showList}
|
||||
disabled={disabled}
|
||||
toggleReset={toggleReset}
|
||||
onToggleList={onToggleList}
|
||||
onChange={newValue => setExpression(newValue)}
|
||||
setTypification={setTypification}
|
||||
/>
|
||||
<RefsInput
|
||||
id='cst_definition'
|
||||
label='Текстовое определение'
|
||||
placeholder='Текстовая интерпретация формального выражения'
|
||||
height='3.8rem'
|
||||
items={schema?.items}
|
||||
value={textDefinition}
|
||||
initialValue={constituenta?.definition_raw ?? ''}
|
||||
resolved={constituenta?.definition_resolved ?? ''}
|
||||
disabled={disabled}
|
||||
onChange={newValue => setTextDefinition(newValue)}
|
||||
/>
|
||||
<TextArea
|
||||
id='cst_convention'
|
||||
spellCheck
|
||||
label='Конвенция / Комментарий'
|
||||
placeholder='Договоренность об интерпретации или пояснение'
|
||||
value={convention}
|
||||
disabled={disabled}
|
||||
rows={convention.length > 2 * ROW_SIZE_IN_CHARACTERS ? 3 : 2}
|
||||
onChange={event => setConvention(event.target.value)}
|
||||
/>
|
||||
{!disabled || processing ? (
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center'
|
||||
disabled={disabled || !isModified}
|
||||
icon={<FiSave size='1.25rem' />}
|
||||
<AnimatePresence>
|
||||
<RefsInput
|
||||
id='cst_term'
|
||||
label='Термин'
|
||||
placeholder='Обозначение, используемое в текстовых определениях'
|
||||
items={schema?.items}
|
||||
value={term}
|
||||
initialValue={constituenta?.term_raw ?? ''}
|
||||
resolved={constituenta?.term_resolved ?? ''}
|
||||
disabled={disabled}
|
||||
onChange={newValue => setTerm(newValue)}
|
||||
/>
|
||||
) : null}
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
dense
|
||||
noBorder
|
||||
disabled={true}
|
||||
label='Типизация'
|
||||
rows={typification.length > ROW_SIZE_IN_CHARACTERS ? 2 : 1}
|
||||
value={typification}
|
||||
colors='clr-app'
|
||||
style={{
|
||||
resize: 'none'
|
||||
}}
|
||||
/>
|
||||
<AnimateFade hideContent={!!constituenta && !constituenta?.definition_formal && isElementary}>
|
||||
<EditorRSExpression
|
||||
id='cst_expression'
|
||||
label={
|
||||
constituenta?.cst_type === CstType.STRUCTURED
|
||||
? 'Область определения'
|
||||
: !!constituenta && isFunctional(constituenta.cst_type)
|
||||
? 'Определение функции'
|
||||
: 'Формальное определение'
|
||||
}
|
||||
placeholder={
|
||||
constituenta?.cst_type !== CstType.STRUCTURED
|
||||
? 'Родоструктурное выражение'
|
||||
: 'Определение множества, которому принадлежат элементы родовой структуры'
|
||||
}
|
||||
value={expression}
|
||||
activeCst={constituenta}
|
||||
showList={showList}
|
||||
disabled={disabled}
|
||||
toggleReset={toggleReset}
|
||||
onToggleList={onToggleList}
|
||||
onChange={newValue => setExpression(newValue)}
|
||||
setTypification={setTypification}
|
||||
/>
|
||||
</AnimateFade>
|
||||
<AnimateFade hideContent={!!constituenta && !constituenta?.definition_raw && isElementary}>
|
||||
<RefsInput
|
||||
id='cst_definition'
|
||||
label='Текстовое определение'
|
||||
placeholder='Текстовая интерпретация формального выражения'
|
||||
height='3.8rem'
|
||||
items={schema?.items}
|
||||
value={textDefinition}
|
||||
initialValue={constituenta?.definition_raw ?? ''}
|
||||
resolved={constituenta?.definition_resolved ?? ''}
|
||||
disabled={disabled}
|
||||
onChange={newValue => setTextDefinition(newValue)}
|
||||
/>
|
||||
</AnimateFade>
|
||||
<AnimateFade hideContent={!showConvention}>
|
||||
<TextArea
|
||||
id='cst_convention'
|
||||
spellCheck
|
||||
className='h-[3.8rem]'
|
||||
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
||||
placeholder='Договоренность об интерпретации или пояснение'
|
||||
value={convention}
|
||||
disabled={disabled}
|
||||
rows={convention.length > 2 * ROW_SIZE_IN_CHARACTERS ? 3 : 2}
|
||||
onChange={event => setConvention(event.target.value)}
|
||||
/>
|
||||
</AnimateFade>
|
||||
{!showConvention && (!disabled || processing) ? (
|
||||
<button
|
||||
type='button'
|
||||
className='self-start cc-label clr-text-url hover:underline'
|
||||
onClick={() => setForceComment(true)}
|
||||
>
|
||||
Добавить комментарий
|
||||
</button>
|
||||
) : null}
|
||||
{!disabled || processing ? (
|
||||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center'
|
||||
disabled={disabled || !isModified}
|
||||
icon={<FiSave size='1.25rem' />}
|
||||
/>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -49,7 +49,7 @@ const MAIN_THIRD_ROW: TokenID[] = [
|
|||
TokenID.LOGIC_EQUIVALENT,
|
||||
TokenID.SET_SYMMETRIC_MINUS,
|
||||
TokenID.PUNCTUATION_ASSIGN,
|
||||
TokenID.EQUAL,
|
||||
TokenID.MULTIPLY,
|
||||
TokenID.GREATER_OR_EQ,
|
||||
TokenID.LESSER_OR_EQ
|
||||
];
|
||||
|
|
|
@ -65,7 +65,11 @@ function RegisterPage() {
|
|||
}
|
||||
|
||||
if (user) {
|
||||
return <ExpectedAnonymous />;
|
||||
return (
|
||||
<AnimateFade>
|
||||
<ExpectedAnonymous />
|
||||
</AnimateFade>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AnimateFade>
|
||||
|
|
|
@ -216,12 +216,22 @@ export const animateFade = {
|
|||
initial: {
|
||||
opacity: 0
|
||||
},
|
||||
animate: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
type: 'tween',
|
||||
ease: 'linear',
|
||||
duration: 0.3
|
||||
variants: {
|
||||
active: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
type: 'tween',
|
||||
ease: 'linear',
|
||||
duration: 0.3
|
||||
}
|
||||
},
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
transition: {
|
||||
type: 'tween',
|
||||
ease: 'linear',
|
||||
duration: 0.3
|
||||
}
|
||||
}
|
||||
},
|
||||
exit: {
|
||||
|
|
|
@ -129,10 +129,11 @@ export function domTooltipConstituenta(cst?: IConstituenta) {
|
|||
dom.className = clsx(
|
||||
'z-modal-tooltip',
|
||||
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
||||
'dense',
|
||||
'p-2',
|
||||
'border shadow-md',
|
||||
'overflow-y-auto',
|
||||
'text-sm'
|
||||
'text-sm font-main'
|
||||
);
|
||||
|
||||
if (cst) {
|
||||
|
@ -179,6 +180,7 @@ export function domTooltipEntityReference(ref: IEntityReference, cst: IConstitue
|
|||
dom.className = clsx(
|
||||
'z-tooltip',
|
||||
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
||||
'dense',
|
||||
'p-2 flex flex-col',
|
||||
'border shadow-md',
|
||||
'overflow-y-auto',
|
||||
|
@ -225,6 +227,7 @@ export function domTooltipSyntacticReference(ref: ISyntacticReference, masterRef
|
|||
dom.className = clsx(
|
||||
'z-tooltip',
|
||||
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
||||
'dense',
|
||||
'p-2 flex flex-col',
|
||||
'border shadow-md',
|
||||
'overflow-y-auto',
|
||||
|
|
|
@ -96,6 +96,7 @@ export function labelToken(id: TokenID): string {
|
|||
case TokenID.LOGIC_EQUIVALENT: return '⇔';
|
||||
case TokenID.LIT_EMPTYSET: return '∅';
|
||||
case TokenID.LIT_WHOLE_NUMBERS: return 'Z';
|
||||
case TokenID.MULTIPLY: return '*';
|
||||
case TokenID.EQUAL: return '=';
|
||||
case TokenID.NOTEQUAL: return '≠';
|
||||
case TokenID.GREATER_OR_EQ: return '≥';
|
||||
|
@ -163,6 +164,7 @@ export function describeToken(id: TokenID): string {
|
|||
case TokenID.LIT_EMPTYSET: return prepareTooltip('Пустое множество', 'Alt + X');
|
||||
case TokenID.LIT_WHOLE_NUMBERS: return prepareTooltip('Целые числа', 'Alt + Z');
|
||||
case TokenID.EQUAL: return prepareTooltip('Равенство');
|
||||
case TokenID.MULTIPLY: return prepareTooltip('Умножение чисел', 'Alt + 8');
|
||||
case TokenID.NOTEQUAL: return prepareTooltip('Неравенство', 'Alt + Shift + `');
|
||||
case TokenID.GREATER_OR_EQ: return prepareTooltip('Больше или равно', 'Alt + Shift + 7');
|
||||
case TokenID.LESSER_OR_EQ: return prepareTooltip('Меньше или равно', 'Alt + Shift + 8');
|
||||
|
|
Loading…
Reference in New Issue
Block a user