Refactoring constants + small UI fixes

This commit is contained in:
IRBorisov 2024-04-06 14:39:49 +03:00
parent 16252b2145
commit a10bda8af3
15 changed files with 51 additions and 72 deletions

View File

@ -4,6 +4,7 @@ import { motion } from 'framer-motion';
import { IconPin, IconUnpin } from '@/components/Icons'; import { IconPin, IconUnpin } from '@/components/Icons';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
import { animateNavigationToggle } from '@/styling/animations'; import { animateNavigationToggle } from '@/styling/animations';
import { globals } from '@/utils/constants';
function ToggleNavigationButton() { function ToggleNavigationButton() {
const { noNavigationAnimation, toggleNoNavigation } = useConceptOptions(); const { noNavigationAnimation, toggleNoNavigation } = useConceptOptions();
@ -11,7 +12,6 @@ function ToggleNavigationButton() {
<motion.button <motion.button
type='button' type='button'
tabIndex={-1} tabIndex={-1}
title={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'}
className={clsx( className={clsx(
'absolute top-0 right-0 z-navigation flex items-center justify-center', 'absolute top-0 right-0 z-navigation flex items-center justify-center',
'clr-btn-nav', 'clr-btn-nav',
@ -21,6 +21,8 @@ function ToggleNavigationButton() {
initial={false} initial={false}
animate={noNavigationAnimation ? 'off' : 'on'} animate={noNavigationAnimation ? 'off' : 'on'}
variants={animateNavigationToggle} variants={animateNavigationToggle}
data-tooltip-id={globals.tooltip}
data-tooltip-content={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'}
> >
{!noNavigationAnimation ? <IconPin /> : null} {!noNavigationAnimation ? <IconPin /> : null}
{noNavigationAnimation ? <IconUnpin /> : null} {noNavigationAnimation ? <IconUnpin /> : null}

View File

@ -22,7 +22,8 @@ export { LuGlasses as IconReader } from 'react-icons/lu';
export { FiBell as IconFollow } from 'react-icons/fi'; export { FiBell as IconFollow } from 'react-icons/fi';
export { FiBellOff as IconFollowOff } from 'react-icons/fi'; export { FiBellOff as IconFollowOff } from 'react-icons/fi';
export { BiListUl as IconList } from 'react-icons/bi'; export { TbColumns as IconList } from 'react-icons/tb';
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
export { BiFontFamily as IconText } from 'react-icons/bi'; export { BiFontFamily as IconText } from 'react-icons/bi';
export { BiFont as IconTextOff } from 'react-icons/bi'; export { BiFont as IconTextOff } from 'react-icons/bi';
export { RiTreeLine as IconTree } from 'react-icons/ri'; export { RiTreeLine as IconTree } from 'react-icons/ri';

View File

@ -4,8 +4,6 @@ import EmbedYoutube from '@/components/ui/EmbedYoutube';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { external_urls, youtube } from '@/utils/constants'; import { external_urls, youtube } from '@/utils/constants';
const OPT_VIDEO_H = 1080;
function HelpRSLang() { function HelpRSLang() {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
@ -13,7 +11,7 @@ function HelpRSLang() {
const viewH = windowSize.height ?? 0; const viewH = windowSize.height ?? 0;
const viewW = windowSize.width ?? 0; const viewW = windowSize.width ?? 0;
const availableWidth = viewW - (windowSize.isSmall ? 35 : 290); const availableWidth = viewW - (windowSize.isSmall ? 35 : 290);
return Math.min(OPT_VIDEO_H, viewH - 320, Math.floor((availableWidth * 9) / 16)); return Math.min(1080, viewH - 320, Math.floor((availableWidth * 9) / 16));
}, [windowSize]); }, [windowSize]);
// prettier-ignore // prettier-ignore

View File

@ -8,8 +8,7 @@ import { getDefinitionPrefix } from '@/models/rsformAPI';
import { IArgumentInfo, IExpressionParse } from '@/models/rslang'; import { IArgumentInfo, IExpressionParse } from '@/models/rslang';
import { RSErrorType } from '@/models/rslang'; import { RSErrorType } from '@/models/rslang';
import { DataCallback, postCheckExpression } from '@/utils/backendAPI'; import { DataCallback, postCheckExpression } from '@/utils/backendAPI';
import { PARAMETER } from '@/utils/constants';
const LOGIC_TYPIFICATION = 'LOGIC';
function useCheckExpression({ schema }: { schema?: IRSForm }) { function useCheckExpression({ schema }: { schema?: IRSForm }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -47,17 +46,17 @@ function checkTypeConsistency(type: CstType, typification: string, args: IArgume
case CstType.CONSTANT: case CstType.CONSTANT:
case CstType.STRUCTURED: case CstType.STRUCTURED:
case CstType.TERM: case CstType.TERM:
return typification !== LOGIC_TYPIFICATION && args.length === 0; return typification !== PARAMETER.logicLabel && args.length === 0;
case CstType.AXIOM: case CstType.AXIOM:
case CstType.THEOREM: case CstType.THEOREM:
return typification === LOGIC_TYPIFICATION && args.length === 0; return typification === PARAMETER.logicLabel && args.length === 0;
case CstType.FUNCTION: case CstType.FUNCTION:
return typification !== LOGIC_TYPIFICATION && args.length !== 0; return typification !== PARAMETER.logicLabel && args.length !== 0;
case CstType.PREDICATE: case CstType.PREDICATE:
return typification === LOGIC_TYPIFICATION && args.length !== 0; return typification === PARAMETER.logicLabel && args.length !== 0;
} }
} }

View File

@ -2,13 +2,10 @@
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
const KEY_NAME_ESC = 'Escape';
const KEY_EVENT_TYPE = 'keyup';
function useEscapeKey(handleClose: () => void) { function useEscapeKey(handleClose: () => void) {
const handleEscKey = useCallback( const handleEscKey = useCallback(
(event: KeyboardEvent) => { (event: KeyboardEvent) => {
if (event.key === KEY_NAME_ESC) { if (event.key === 'Escape') {
handleClose(); handleClose();
} }
}, },
@ -16,9 +13,9 @@ function useEscapeKey(handleClose: () => void) {
); );
useEffect(() => { useEffect(() => {
document.addEventListener(KEY_EVENT_TYPE, handleEscKey, false); document.addEventListener('keyup', handleEscKey, false);
return () => { return () => {
document.removeEventListener(KEY_EVENT_TYPE, handleEscKey, false); document.removeEventListener('keyup', handleEscKey, false);
}; };
}, [handleEscKey]); }, [handleEscKey]);
} }

View File

@ -2,7 +2,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { SMALL_SCREEN_WIDTH } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
function useWindowSize() { function useWindowSize() {
const isClient = typeof window === 'object'; const isClient = typeof window === 'object';
@ -11,7 +11,7 @@ function useWindowSize() {
return { return {
width: isClient ? window.innerWidth : undefined, width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined, height: isClient ? window.innerHeight : undefined,
isSmall: isClient && window.innerWidth < SMALL_SCREEN_WIDTH isSmall: isClient && window.innerWidth < PARAMETER.smallScreen
}; };
} }

View File

@ -32,9 +32,7 @@ export function loadRSFormData(input: IRSFormData): IRSForm {
result.graph = new Graph(); result.graph = new Graph();
result.stats = calculateStats(result.items); result.stats = calculateStats(result.items);
const derivationLookup: Map<ConstituentaID, ConstituentaID> = new Map();
result.items.forEach(cst => { result.items.forEach(cst => {
derivationLookup.set(cst.id, cst.id);
cst.derived_from = cst.id; cst.derived_from = cst.id;
cst.derived_children = []; cst.derived_children = [];
cst.derived_children_alias = []; cst.derived_children_alias = [];
@ -51,7 +49,9 @@ export function loadRSFormData(input: IRSFormData): IRSForm {
}); });
}); });
// Calculate derivation of constituents based on formal definition analysis // Calculate derivation of constituents based on formal definition analysis
const derivationLookup: Map<ConstituentaID, ConstituentaID> = new Map();
result.graph.topologicalOrder().forEach(id => { result.graph.topologicalOrder().forEach(id => {
derivationLookup.set(id, id);
const cst = result.items.find(item => item.id === id)!; const cst = result.items.find(item => item.id === id)!;
if (cst.cst_type === CstType.STRUCTURED) { if (cst.cst_type === CstType.STRUCTURED) {
return; return;

View File

@ -3,7 +3,7 @@ import { useLayoutEffect } from 'react';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { TIMEOUT_UI_REFRESH } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
function HomePage() { function HomePage() {
const router = useConceptNavigation(); const router = useConceptNavigation();
@ -13,11 +13,11 @@ function HomePage() {
if (!user) { if (!user) {
setTimeout(() => { setTimeout(() => {
router.push(urls.manuals); router.push(urls.manuals);
}, TIMEOUT_UI_REFRESH); }, PARAMETER.refreshTimeout);
} else { } else {
setTimeout(() => { setTimeout(() => {
router.push(urls.library); router.push(urls.library);
}, TIMEOUT_UI_REFRESH); }, PARAMETER.refreshTimeout);
} }
}, [router, user]); }, [router, user]);

View File

@ -14,9 +14,6 @@ import ViewConstituents from '../ViewConstituents';
import ConstituentaToolbar from './ConstituentaToolbar'; import ConstituentaToolbar from './ConstituentaToolbar';
import FormConstituenta from './FormConstituenta'; import FormConstituenta from './FormConstituenta';
// Max height of content for left editor pane.
const UNFOLDED_HEIGHT = '59.1rem';
// Threshold window width to switch layout. // Threshold window width to switch layout.
const SIDELIST_LAYOUT_THRESHOLD = 1100; // px const SIDELIST_LAYOUT_THRESHOLD = 1100; // px
@ -119,7 +116,6 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
schema={controller.schema} schema={controller.schema}
expression={activeCst?.definition_formal ?? ''} expression={activeCst?.definition_formal ?? ''}
isBottom={isNarrow} isBottom={isNarrow}
baseHeight={UNFOLDED_HEIGHT}
activeID={activeCst?.id} activeID={activeCst?.id}
onOpenEdit={onOpenEdit} onOpenEdit={onOpenEdit}
/> />

View File

@ -6,7 +6,7 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { FaRegKeyboard } from 'react-icons/fa6'; import { FaRegKeyboard } from 'react-icons/fa6';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { IconList, IconText, IconTree } from '@/components/Icons'; import { IconList, IconListOff, IconText, IconTextOff, IconTree } from '@/components/Icons';
import BadgeHelp from '@/components/man/BadgeHelp'; import BadgeHelp from '@/components/man/BadgeHelp';
import RSInput from '@/components/RSInput'; import RSInput from '@/components/RSInput';
import { RSTextWrapper } from '@/components/RSInput/textEditing'; import { RSTextWrapper } from '@/components/RSInput/textEditing';
@ -164,19 +164,21 @@ function EditorRSExpression({
<MiniButton <MiniButton
title='Изменить шрифт' title='Изменить шрифт'
onClick={toggleFont} onClick={toggleFont}
icon={<IconText size='1.25rem' className={mathFont === 'math' ? 'icon-primary' : ''} />} icon={
mathFont === 'math' ? <IconText size='1.25rem' className='icon-primary' /> : <IconTextOff size='1.25rem' />
}
/> />
{!disabled || model.processing ? ( {!disabled || model.processing ? (
<MiniButton <MiniButton
title='Отображение специальной клавиатуры' title='Отображение специальной клавиатуры'
onClick={() => setShowControls(prev => !prev)}
icon={<FaRegKeyboard size='1.25rem' className={showControls ? 'icon-primary' : ''} />} icon={<FaRegKeyboard size='1.25rem' className={showControls ? 'icon-primary' : ''} />}
onClick={() => setShowControls(prev => !prev)}
/> />
) : null} ) : null}
<MiniButton <MiniButton
title='Отображение списка конституент' title='Отображение списка конституент'
icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />}
onClick={onToggleList} onClick={onToggleList}
icon={<IconList size='1.25rem' className={showList ? 'icon-primary' : ''} />}
/> />
<MiniButton <MiniButton
title='Дерево разбора выражения' title='Дерево разбора выражения'

View File

@ -14,7 +14,7 @@ import useLocalStorage from '@/hooks/useLocalStorage';
import { GraphColoringScheme, GraphFilterParams } from '@/models/miscellaneous'; import { GraphColoringScheme, GraphFilterParams } from '@/models/miscellaneous';
import { ConstituentaID, CstType } from '@/models/rsform'; import { ConstituentaID, CstType } from '@/models/rsform';
import { colorBgGraphNode } from '@/styling/color'; import { colorBgGraphNode } from '@/styling/color';
import { storage, TIMEOUT_GRAPH_REFRESH } from '@/utils/constants'; import { PARAMETER, storage } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
import GraphSelectors from './GraphSelectors'; import GraphSelectors from './GraphSelectors';
@ -141,7 +141,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
setLayout(newLayout); setLayout(newLayout);
setTimeout(() => { setTimeout(() => {
setToggleResetView(prev => !prev); setToggleResetView(prev => !prev);
}, TIMEOUT_GRAPH_REFRESH); }, PARAMETER.graphRefreshDelay);
} }
const handleChangeParams = useCallback( const handleChangeParams = useCallback(
@ -173,7 +173,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
})); }));
setTimeout(() => { setTimeout(() => {
setToggleResetView(prev => !prev); setToggleResetView(prev => !prev);
}, TIMEOUT_GRAPH_REFRESH); }, PARAMETER.graphRefreshDelay);
}, [setFilterParams, setToggleResetView]); }, [setFilterParams, setToggleResetView]);
const graph = useMemo( const graph = useMemo(

View File

@ -6,7 +6,7 @@ import GraphUI, { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, useSelectio
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
import { ConstituentaID } from '@/models/rsform'; import { ConstituentaID } from '@/models/rsform';
import { graphDarkT, graphLightT } from '@/styling/color'; import { graphDarkT, graphLightT } from '@/styling/color';
import { resources } from '@/utils/constants'; import { PARAMETER, resources } from '@/utils/constants';
interface TermGraphProps { interface TermGraphProps {
nodes: GraphNode[]; nodes: GraphNode[];
@ -25,8 +25,6 @@ interface TermGraphProps {
toggleResetView: boolean; toggleResetView: boolean;
} }
const TREE_SIZE_MILESTONE = 50;
function TermGraph({ function TermGraph({
nodes, nodes,
edges, edges,
@ -114,7 +112,7 @@ function TermGraph({
maxNodeSize={8} maxNodeSize={8}
cameraMode={orbit ? 'orbit' : is3D ? 'rotate' : 'pan'} cameraMode={orbit ? 'orbit' : is3D ? 'rotate' : 'pan'}
layoutOverrides={ layoutOverrides={
layout.includes('tree') ? { nodeLevelRatio: nodes.length < TREE_SIZE_MILESTONE ? 3 : 1 } : undefined layout.includes('tree') ? { nodeLevelRatio: nodes.length < PARAMETER.smallTreeNodes ? 3 : 1 } : undefined
} }
labelFontUrl={resources.graph_font} labelFontUrl={resources.graph_font}
theme={darkMode ? graphDarkT : graphLightT} theme={darkMode ? graphDarkT : graphLightT}

View File

@ -14,7 +14,7 @@ import { useConceptOptions } from '@/context/OptionsContext';
import { useRSForm } from '@/context/RSFormContext'; import { useRSForm } from '@/context/RSFormContext';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
import { ConstituentaID, IConstituenta, IConstituentaMeta } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IConstituentaMeta } from '@/models/rsform';
import { prefixes, TIMEOUT_UI_REFRESH } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
import { labelVersion } from '@/utils/labels'; import { labelVersion } from '@/utils/labels';
import EditorConstituenta from './EditorConstituenta'; import EditorConstituenta from './EditorConstituenta';
@ -124,7 +124,7 @@ function RSTabs() {
inline: 'nearest' inline: 'nearest'
}); });
} }
}, TIMEOUT_UI_REFRESH); }, PARAMETER.refreshTimeout);
} }
}, },
[activeTab, navigateTab] [activeTab, navigateTab]

View File

@ -2,7 +2,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useMemo, useState } from 'react'; import { useState } from 'react';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
@ -11,33 +11,22 @@ import { animateSideView } from '@/styling/animations';
import ConstituentsSearch from './ConstituentsSearch'; import ConstituentsSearch from './ConstituentsSearch';
import ConstituentsTable from './ConstituentsTable'; import ConstituentsTable from './ConstituentsTable';
// Height that should be left to accommodate navigation panel + bottom margin
const LOCAL_NAVIGATION_H = '2.1rem';
// Window width cutoff for expression show // Window width cutoff for expression show
const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500; const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500;
interface ViewConstituentsProps { interface ViewConstituentsProps {
expression: string; expression: string;
isBottom?: boolean; isBottom?: boolean;
baseHeight: string;
activeID?: ConstituentaID; activeID?: ConstituentaID;
schema?: IRSForm; schema?: IRSForm;
onOpenEdit: (cstID: ConstituentaID) => void; onOpenEdit: (cstID: ConstituentaID) => void;
} }
function ViewConstituents({ expression, schema, activeID, isBottom, baseHeight, onOpenEdit }: ViewConstituentsProps) { function ViewConstituents({ expression, schema, activeID, isBottom, onOpenEdit }: ViewConstituentsProps) {
const { noNavigation } = useConceptOptions(); const { calculateHeight } = useConceptOptions();
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []); const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
const maxHeight = useMemo(() => {
const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}`;
return noNavigation
? `calc(min(100vh - 8.2rem, ${siblingHeight}))`
: `calc(min(100vh - 11.7rem, ${siblingHeight}))`;
}, [noNavigation, baseHeight]);
return ( return (
<motion.div <motion.div
className={clsx( className={clsx(
@ -58,7 +47,7 @@ function ViewConstituents({ expression, schema, activeID, isBottom, baseHeight,
setFiltered={setFilteredData} setFiltered={setFilteredData}
/> />
<ConstituentsTable <ConstituentsTable
maxHeight={isBottom ? '12rem' : maxHeight} maxHeight={isBottom ? '12rem' : calculateHeight('8.2rem')}
items={filteredData} items={filteredData}
activeID={activeID} activeID={activeID}
onOpenEdit={onOpenEdit} onOpenEdit={onOpenEdit}

View File

@ -10,24 +10,16 @@ export const buildConstants = {
}; };
/** /**
* General UI timeout [in ms] for waiting for render. * Global application Parameters. The place where magic numbers are put to rest.
*/ */
export const TIMEOUT_UI_REFRESH = 100; export const PARAMETER = {
smallScreen: 640, // == tailwind:xs
smallTreeNodes: 50, // amount of nodes threshold for size increase for large graphs
refreshTimeout: 100, // milliseconds delay for post-refresh actions
graphRefreshDelay: 10, // milliseconds delay for graph viewpoint reset
/** logicLabel: 'LOGIC'
* Threshold for small screen size optimizations. };
*/
export const SMALL_SCREEN_WIDTH = 640; // == tailwind:xs
/**
* Timeout [in ms] for graph refresh.
*/
export const TIMEOUT_GRAPH_REFRESH = 200;
/**
* Exteor file extension for RSForm.
*/
export const EXTEOR_TRS_FILE = '.trs';
/** /**
* Numeric limitations. * Numeric limitations.
@ -36,6 +28,11 @@ export const limits = {
library_alias_len: 12 library_alias_len: 12
}; };
/**
* Exteor file extension for RSForm.
*/
export const EXTEOR_TRS_FILE = '.trs';
/** /**
* Regex patterns for data validation. * Regex patterns for data validation.
*/ */