R: Refactor layout definitions

This commit is contained in:
Ivan 2025-03-11 12:47:00 +03:00
parent aea9dececf
commit 3d63c25845
22 changed files with 141 additions and 147 deletions

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { useRoleStore, UserRole } from '@/features/users';
@ -20,9 +22,11 @@ interface ToolbarItemAccessProps {
toggleReadOnly: () => void;
schema: ILibraryItem;
isAttachedToOSS: boolean;
className?: string;
}
export function ToolbarItemAccess({
className,
visible,
toggleVisible,
readOnly,
@ -40,7 +44,7 @@ export function ToolbarItemAccess({
}
return (
<div className='absolute z-bottom top-18 right-0 w-48 flex pr-2'>
<div className={clsx('w-46 flex', className)}>
<Label text='Доступ' className='self-center select-none' />
<div className='ml-auto cc-icons'>
<SelectAccessPolicy

View File

@ -1,5 +1,7 @@
'use client';
import clsx from 'clsx';
import { urls, useConceptNavigation } from '@/app';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
@ -18,13 +20,14 @@ import { useMutatingLibrary } from '../backend/useMutatingLibrary';
import { MiniSelectorOSS } from './MiniSelectorOSS';
interface ToolbarItemCardProps {
className?: string;
onSubmit: () => void;
isMutable: boolean;
schema: ILibraryItem;
deleteSchema: () => void;
}
export function ToolbarItemCard({ schema, onSubmit, isMutable, deleteSchema }: ToolbarItemCardProps) {
export function ToolbarItemCard({ className, schema, onSubmit, isMutable, deleteSchema }: ToolbarItemCardProps) {
const role = useRoleStore(state => state.role);
const router = useConceptNavigation();
const { isModified } = useModificationStore();
@ -48,7 +51,7 @@ export function ToolbarItemCard({ schema, onSubmit, isMutable, deleteSchema }: T
})();
return (
<div className='cc-tab-tools cc-icons'>
<div className={clsx('cc-icons', className)}>
{ossSelector}
{isMutable || isModified ? (
<MiniButton

View File

@ -34,7 +34,13 @@ export function EditorOssCard() {
return (
<>
<ToolbarItemCard onSubmit={initiateSubmit} schema={schema} isMutable={isMutable} deleteSchema={deleteSchema} />
<ToolbarItemCard
className='cc-tab-tools'
onSubmit={initiateSubmit}
schema={schema}
isMutable={isMutable}
deleteSchema={deleteSchema}
/>
<div
onKeyDown={handleInput}
className={clsx(
@ -48,7 +54,7 @@ export function EditorOssCard() {
<EditorLibraryItem schema={schema} isAttachedToOSS={false} />
</div>
<OssStats stats={schema.stats} />
<OssStats className='mt-3 md:mt-8 md:ml-5 w-56 md:w-48 mx-auto h-min' stats={schema.stats} />
</div>
</>
);

View File

@ -78,6 +78,7 @@ export function FormOSS() {
error={errors.alias}
/>
<ToolbarItemAccess
className='absolute top-18 right-2'
visible={visible}
toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
readOnly={readOnly}

View File

@ -6,17 +6,13 @@ import { ValueStats } from '@/components/View';
import { type IOperationSchemaStats } from '../../../models/oss';
interface OssStatsProps {
className?: string;
stats: IOperationSchemaStats;
}
export function OssStats({ stats }: OssStatsProps) {
export function OssStats({ className, stats }: OssStatsProps) {
return (
<div
className={clsx(
'mt-3 md:ml-5 md:mt-8 md:w-48 w-56 h-min mx-auto', //
'grid grid-cols-3 gap-1 justify-items-end'
)}
>
<div className={clsx('grid grid-cols-3 gap-1 justify-items-end', className)}>
<div id='count_operations' className='w-fit flex gap-3 hover:cursor-default '>
<span>Всего</span>
<span>{stats.count_operations}</span>

View File

@ -74,7 +74,7 @@ export function EditorConstituenta() {
<div
tabIndex={-1}
className={clsx(
'relative',
'relative ',
'cc-fade-in',
'min-h-80 max-w-[calc(min(100vw,95rem))] mx-auto',
'flex pt-8',
@ -85,11 +85,13 @@ export function EditorConstituenta() {
onKeyDown={handleInput}
>
<ToolbarConstituenta
className='cc-tab-tools right-1/2 translate-x-0 xs:right-4 xs:-translate-x-1/2 md:right-1/2 md:translate-x-0 cc-animate-position'
activeCst={activeCst}
disabled={disabled}
onSubmit={initiateSubmit}
onReset={() => setToggleReset(prev => !prev)}
/>
<div className='mx-0 md:mx-auto pt-8 md:w-195 shrink-0 xs:pt-0'>
{activeCst ? (
<FormConstituenta
@ -102,7 +104,11 @@ export function EditorConstituenta() {
/>
) : null}
</div>
<ViewConstituents isMounted={showList} isBottom={isNarrow} />
<ViewConstituents
className={isNarrow ? 'mt-3 mx-6 overflow-hidden' : 'mt-9 h-fit overflow-visible'}
isMounted={showList}
isBottom={isNarrow}
/>
</div>
);
}

View File

@ -1,63 +0,0 @@
import clsx from 'clsx';
import { MiniButton } from '@/components/Control';
import { IconEdit } from '@/components/Icons';
import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification';
import { tooltipText } from '@/utils/labels';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
import { type IConstituenta } from '../../../models/rsform';
import { useRSEdit } from '../RSEditContext';
interface EditorControlsProps {
constituenta: IConstituenta;
disabled: boolean;
onEditTerm: () => void;
}
export function EditorControls({ constituenta, disabled, onEditTerm }: EditorControlsProps) {
const schema = useRSEdit().schema;
const { isModified } = useModificationStore();
const isProcessing = useMutatingRSForm();
const showRenameCst = useDialogsStore(state => state.showRenameCst);
function handleRenameCst() {
showRenameCst({ schema: schema, target: constituenta });
}
return (
<div className='absolute z-pop top-0 left-19 flex select-none'>
{!disabled || isProcessing ? (
<MiniButton
title={isModified ? tooltipText.unsaved : `Редактировать словоформы термина`}
noHover
onClick={onEditTerm}
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}
<div
className={clsx(
'pt-1 sm:pl-5 pl-1',
'text-sm font-medium whitespace-nowrap',
'select-text cursor-default',
disabled && !isProcessing && 'pl-6 sm:pl-11'
)}
>
<span>Имя </span>
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
</div>
{!disabled || isProcessing ? (
<MiniButton
noHover
title={isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
onClick={handleRenameCst}
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}
</div>
);
}

View File

@ -6,13 +6,13 @@ import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import { zodResolver } from '@hookform/resolvers/zod';
import { SubmitButton } from '@/components/Control';
import { IconChild, IconPredecessor, IconSave } from '@/components/Icons';
import { MiniButton, SubmitButton } from '@/components/Control';
import { IconChild, IconEdit, IconPredecessor, IconSave } from '@/components/Icons';
import { TextArea } from '@/components/Input';
import { Indicator } from '@/components/View';
import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification';
import { errorMsg } from '@/utils/labels';
import { errorMsg, tooltipText } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils';
import {
@ -30,8 +30,6 @@ import { type IConstituenta, type IRSForm } from '../../../models/rsform';
import { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsformAPI';
import { EditorRSExpression } from '../EditorRSExpression';
import { EditorControls } from './EditorControls';
interface FormConstituentaProps {
id?: string;
disabled: boolean;
@ -57,6 +55,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
const { cstUpdate } = useCstUpdate();
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
const showRenameCst = useDialogsStore(state => state.showRenameCst);
const [localParse, setLocalParse] = useState<IExpressionParseDTO | null>(null);
@ -129,9 +128,38 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
showEditTerm({ itemID: schema.id, target: activeCst });
}
function handleRenameCst() {
showRenameCst({ schema: schema, target: activeCst });
}
return (
<form id={id} className='relative cc-column mt-1 px-6 py-1' onSubmit={event => void handleSubmit(onSubmit)(event)}>
<EditorControls disabled={disabled} constituenta={activeCst} onEditTerm={handleEditTermForms} />
{!disabled || isProcessing ? (
<MiniButton
title={isModified ? tooltipText.unsaved : `Редактировать словоформы термина`}
noHover
onClick={handleEditTermForms}
className='absolute z-pop top-0 left-[calc(7ch+4px)]'
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}
<div className='absolute z-pop top-0 left-[calc(7ch+4px+3rem)] flex select-none'>
<div className='pt-1 text-sm font-medium whitespace-nowrap select-text cursor-default'>
<span>Имя </span>
<span className='ml-1'>{activeCst?.alias ?? ''}</span>
</div>
{!disabled || isProcessing ? (
<MiniButton
noHover
title={isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
onClick={handleRenameCst}
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}
</div>
<Controller
control={control}

View File

@ -31,6 +31,7 @@ import { type IConstituenta } from '../../../models/rsform';
import { RSTabID, useRSEdit } from '../RSEditContext';
interface ToolbarConstituentaProps {
className?: string;
activeCst: IConstituenta | null;
disabled: boolean;
@ -39,6 +40,7 @@ interface ToolbarConstituentaProps {
}
export function ToolbarConstituenta({
className,
activeCst,
disabled,
@ -78,14 +80,7 @@ export function ToolbarConstituenta({
}
return (
<div
className={clsx(
'absolute z-pop right-1/2 translate-x-0 xs:right-4 xs:-translate-x-1/2 md:right-1/2 md:translate-x-0',
'px-1 rounded-b-2xl',
'cc-blur',
'cc-tab-tools cc-icons cc-animate-position outline-hidden'
)}
>
<div className={clsx('px-1 rounded-b-2xl cc-blur cc-icons cc-animate-position outline-hidden', className)}>
{schema.oss.length > 0 ? (
<MiniSelectorOSS
items={schema.oss}

View File

@ -155,9 +155,15 @@ export function EditorRSExpression({
return (
<div className='relative cc-fade-in'>
<ToolbarRSExpression disabled={disabled} showAST={handleShowAST} showTypeGraph={onShowTypeGraph} />
<ToolbarRSExpression
className='absolute -top-2 right-0'
disabled={disabled}
showAST={handleShowAST}
showTypeGraph={onShowTypeGraph}
/>
<StatusBar
className='absolute -top-2 right-1/2 translate-x-1/2'
processing={isPending}
isModified={isModified}
activeCst={activeCst}

View File

@ -18,6 +18,7 @@ import { ExpressionStatus, type IConstituenta } from '../../../models/rsform';
import { inferStatus } from '../../../models/rsformAPI';
interface StatusBarProps {
className?: string;
processing: boolean;
isModified: boolean;
parseData: IExpressionParseDTO | null;
@ -25,7 +26,7 @@ interface StatusBarProps {
onAnalyze: () => void;
}
export function StatusBar({ isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) {
export function StatusBar({ className, isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) {
const status = (() => {
if (isModified) {
return ExpressionStatus.UNKNOWN;
@ -38,12 +39,7 @@ export function StatusBar({ isModified, processing, activeCst, parseData, onAnal
})();
return (
<div
className={clsx(
'absolute z-pop -top-2 right-1/2 translate-x-1/2 w-fit', //
'pl-34 xs:pl-8 flex gap-1'
)}
>
<div className={clsx('pl-34 xs:pl-8 flex gap-1', className)}>
<div
tabIndex={0}
className={clsx(

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { MiniButton } from '@/components/Control';
import { IconControls, IconTree, IconTypeGraph } from '@/components/Icons';
import { usePreferencesStore } from '@/stores/preferences';
@ -5,18 +7,19 @@ import { usePreferencesStore } from '@/stores/preferences';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
interface ToolbarRSExpressionProps {
className?: string;
disabled?: boolean;
showAST: (event: React.MouseEvent<Element>) => void;
showTypeGraph: (event: React.MouseEvent<Element>) => void;
}
export function ToolbarRSExpression({ disabled, showTypeGraph, showAST }: ToolbarRSExpressionProps) {
export function ToolbarRSExpression({ className, disabled, showTypeGraph, showAST }: ToolbarRSExpressionProps) {
const isProcessing = useMutatingRSForm();
const showControls = usePreferencesStore(state => state.showExpressionControls);
const toggleControls = usePreferencesStore(state => state.toggleShowExpressionControls);
return (
<div className='absolute z-pop -top-2 right-0 cc-icons'>
<div className={clsx('cc-icons', className)}>
{!disabled || isProcessing ? (
<MiniButton
title='Отображение специальной клавиатуры'

View File

@ -42,14 +42,24 @@ export function EditorRSFormCard() {
'flex flex-row flex-wrap px-6 pt-8'
)}
>
<ToolbarItemCard onSubmit={initiateSubmit} schema={schema} isMutable={isMutable} deleteSchema={deleteSchema} />
<ToolbarItemCard
className='cc-tab-tools'
onSubmit={initiateSubmit}
schema={schema}
isMutable={isMutable}
deleteSchema={deleteSchema}
/>
<div className='cc-column shrink'>
<FormRSForm />
<EditorLibraryItem schema={schema} isAttachedToOSS={isAttachedToOSS} />
</div>
<RSFormStats stats={schema.stats} isArchive={isArchive} />
<RSFormStats
className='mt-3 md:mt-8 md:ml-5 w-80 md:w-56 mx-auto h-min'
stats={schema.stats}
isArchive={isArchive}
/>
</div>
);
}

View File

@ -90,17 +90,13 @@ export function FormRSForm() {
disabled={!isContentEditable}
error={errors.alias}
/>
<div className='relative flex flex-col'>
<ToolbarVersioning blockReload={schema.oss.length > 0} />
<ToolbarItemAccess
visible={visible}
toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
readOnly={readOnly}
toggleReadOnly={() => setValue('read_only', !readOnly, { shouldDirty: true })}
schema={schema}
isAttachedToOSS={isAttachedToOSS}
<div className='relative flex flex-col gap-2'>
<ToolbarVersioning
className='absolute -top-2 right-2' //
blockReload={schema.oss.length > 0}
/>
<Label text='Версия' className='mb-2 select-none' />
<Label text='Версия' className='select-none w-fit' />
<SelectVersion
id='schema_version'
className='select-none'
@ -108,6 +104,16 @@ export function FormRSForm() {
items={schema.versions}
onChange={handleSelectVersion}
/>
<ToolbarItemAccess
className='absolute top-18 right-2'
visible={visible}
toggleVisible={() => setValue('visible', !visible, { shouldDirty: true })}
readOnly={readOnly}
toggleReadOnly={() => setValue('read_only', !readOnly, { shouldDirty: true })}
schema={schema}
isAttachedToOSS={isAttachedToOSS}
/>
</div>
</div>

View File

@ -24,18 +24,14 @@ import { ValueStats } from '@/components/View';
import { type IRSFormStats } from '../../../models/rsform';
interface RSFormStatsProps {
className?: string;
isArchive: boolean;
stats: IRSFormStats;
}
export function RSFormStats({ stats, isArchive }: RSFormStatsProps) {
export function RSFormStats({ className, stats, isArchive }: RSFormStatsProps) {
return (
<div
className={clsx(
'mt-3 md:ml-5 md:mt-8 md:w-56 w-80 h-min mx-auto', //
'grid grid-cols-4 gap-1 justify-items-end'
)}
>
<div className={clsx('grid grid-cols-4 gap-1 justify-items-end', className)}>
<div id='count_all' className='col-span-2 w-fit flex gap-3 hover:cursor-default '>
<span>Всего</span>
<span>{stats.count_all}</span>

View File

@ -1,5 +1,7 @@
'use client';
import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { useVersionRestore } from '@/features/library/backend/useVersionRestore';
@ -15,9 +17,10 @@ import { useRSEdit } from '../RSEditContext';
interface ToolbarVersioningProps {
blockReload?: boolean;
className?: string;
}
export function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
export function ToolbarVersioning({ blockReload, className }: ToolbarVersioningProps) {
const { isModified } = useModificationStore();
const { versionRestore } = useVersionRestore();
const { schema, isMutable, isContentEditable, navigateVersion, activeVersion, selected } = useRSEdit();
@ -55,7 +58,7 @@ export function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
}
return (
<div className='absolute z-bottom -top-2 right-0 pr-2 cc-icons'>
<div className={clsx('cc-icons', className)}>
{isMutable ? (
<>
<MiniButton

View File

@ -127,7 +127,9 @@ export function EditorRSList() {
return (
<div tabIndex={-1} onKeyDown={handleKeyDown} className='relative cc-fade-in pt-8'>
{isContentEditable ? <ToolbarRSList /> : null}
{isContentEditable ? (
<ToolbarRSList className='cc-tab-tools right-4 md:right-1/2 -translate-x-1/2 md:translate-x-0 cc-animate-position' />
) : null}
{isContentEditable ? (
<div className='flex items-center border-b'>
<div className='px-2'>

View File

@ -24,7 +24,11 @@ import { IconCstType } from '../../../components/IconCstType';
import { getCstTypeShortcut, labelCstType } from '../../../labels';
import { useRSEdit } from '../RSEditContext';
export function ToolbarRSList() {
interface ToolbarRSListProps {
className?: string;
}
export function ToolbarRSList({ className }: ToolbarRSListProps) {
const isProcessing = useMutatingRSForm();
const insertMenu = useDropdown();
const {
@ -42,13 +46,7 @@ export function ToolbarRSList() {
} = useRSEdit();
return (
<div
className={clsx(
'cc-tab-tools right-4 md:right-1/2 -translate-x-1/2 md:translate-x-0',
'cc-icons items-start',
'outline-hidden cc-animate-position'
)}
>
<div className={clsx('cc-icons items-start outline-hidden', className)}>
{schema.oss.length > 0 ? (
<MiniSelectorOSS
items={schema.oss}

View File

@ -12,7 +12,6 @@ import {
useReactFlow,
useStoreApi
} from 'reactflow';
import clsx from 'clsx';
import { useMainHeight } from '@/stores/appLayout';
import { PARAMETER } from '@/utils/constants';
@ -181,13 +180,7 @@ export function TGFlow() {
) : null}
</div>
<div
className={clsx(
'absolute z-pop top-18 sm:top-16 left-2 sm:left-3 w-54',
'flex flex-col',
'pointer-events-none'
)}
>
<div className='absolute z-pop top-18 sm:top-16 left-2 sm:left-3 w-54 flex flex-col pointer-events-none'>
<span className='px-2 pb-1 select-none whitespace-nowrap cc-blur rounded-xl'>
Выбор {selected.length} из {schema.stats?.count_all ?? 0}
</span>

View File

@ -15,21 +15,26 @@ import { TableSideConstituents } from './TableSideConstituents';
const COLUMN_DENSE_SEARCH_THRESHOLD = 1100;
interface ViewConstituentsProps {
className?: string;
isBottom?: boolean;
isMounted: boolean;
}
export function ViewConstituents({ isBottom, isMounted }: ViewConstituentsProps) {
export function ViewConstituents({ className, isBottom, isMounted }: ViewConstituentsProps) {
const windowSize = useWindowSize();
const role = useRoleStore(state => state.role);
const listHeight = useFitHeight(!isBottom ? '8.2rem' : role !== UserRole.READER ? '42rem' : '35rem', '10rem');
return (
<aside
className={clsx('border', {
'mt-9 rounded-l-md rounded-r-none h-fit overflow-visible': !isBottom,
'mt-3 mx-6 rounded-md overflow-hidden': isBottom
})}
className={clsx(
'border',
{
'rounded-l-md rounded-r-none': !isBottom,
'rounded-md': isBottom
},
className
)}
style={{
willChange: 'opacity, max-width',
transitionProperty: 'opacity, max-width',

View File

@ -35,7 +35,7 @@ export function EditorPassword() {
return (
<form
className='max-w-64 px-6 py-2 flex flex-col justify-between border-l-2'
className='max-w-64 px-6 py-2 flex flex-col gap-2 justify-between border-l-2'
onSubmit={event => void handleSubmit(onSubmit)(event)}
onChange={resetErrors}
>
@ -69,7 +69,7 @@ export function EditorPassword() {
/>
{serverError ? <ServerError error={serverError} /> : null}
</div>
<SubmitButton text='Сменить пароль' className='self-center mt-2' loading={isPending} />
<SubmitButton text='Сменить пароль' className='self-center' loading={isPending} />
</form>
);
}