R: Migrating to zustand for local state management pt1

This commit is contained in:
Ivan 2025-01-14 21:57:32 +03:00
parent 2e03756646
commit 26bd0ce16b
34 changed files with 357 additions and 168 deletions

View File

@ -44,6 +44,7 @@ This readme file is used mostly to document project dependencies and conventions
- use-debounce - use-debounce
- qrcode.react - qrcode.react
- html-to-image - html-to-image
- zustand
- @tanstack/react-table - @tanstack/react-table
- @uiw/react-codemirror - @uiw/react-codemirror
- @uiw/codemirror-themes - @uiw/codemirror-themes

View File

@ -30,7 +30,8 @@
"react-tooltip": "^5.28.0", "react-tooltip": "^5.28.0",
"react-zoom-pan-pinch": "^3.6.1", "react-zoom-pan-pinch": "^3.6.1",
"reactflow": "^11.11.4", "reactflow": "^11.11.4",
"use-debounce": "^10.0.4" "use-debounce": "^10.0.4",
"zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.2", "@lezer/generator": "^1.7.2",
@ -2445,6 +2446,34 @@
"react-dom": ">=17" "react-dom": ">=17"
} }
}, },
"node_modules/@reactflow/background/node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@reactflow/controls": { "node_modules/@reactflow/controls": {
"version": "11.2.14", "version": "11.2.14",
"resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
@ -2460,6 +2489,34 @@
"react-dom": ">=17" "react-dom": ">=17"
} }
}, },
"node_modules/@reactflow/controls/node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@reactflow/core": { "node_modules/@reactflow/core": {
"version": "11.11.4", "version": "11.11.4",
"resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
@ -2481,6 +2538,34 @@
"react-dom": ">=17" "react-dom": ">=17"
} }
}, },
"node_modules/@reactflow/core/node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@reactflow/minimap": { "node_modules/@reactflow/minimap": {
"version": "11.7.14", "version": "11.7.14",
"resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
@ -2500,6 +2585,34 @@
"react-dom": ">=17" "react-dom": ">=17"
} }
}, },
"node_modules/@reactflow/minimap/node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@reactflow/node-resizer": { "node_modules/@reactflow/node-resizer": {
"version": "2.2.14", "version": "2.2.14",
"resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
@ -2517,6 +2630,34 @@
"react-dom": ">=17" "react-dom": ">=17"
} }
}, },
"node_modules/@reactflow/node-resizer/node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@reactflow/node-toolbar": { "node_modules/@reactflow/node-toolbar": {
"version": "1.3.14", "version": "1.3.14",
"resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
@ -2532,6 +2673,34 @@
"react-dom": ">=17" "react-dom": ">=17"
} }
}, },
"node_modules/@reactflow/node-toolbar/node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.30.1", "version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
@ -10711,20 +10880,18 @@
} }
}, },
"node_modules/zustand": { "node_modules/zustand": {
"version": "4.5.6", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
"license": "MIT", "license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": { "engines": {
"node": ">=12.7.0" "node": ">=12.20.0"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": ">=16.8", "@types/react": ">=18.0.0",
"immer": ">=9.0.6", "immer": ">=9.0.6",
"react": ">=16.8" "react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@types/react": { "@types/react": {
@ -10735,6 +10902,9 @@
}, },
"react": { "react": {
"optional": true "optional": true
},
"use-sync-external-store": {
"optional": true
} }
} }
} }

View File

@ -34,7 +34,8 @@
"react-tooltip": "^5.28.0", "react-tooltip": "^5.28.0",
"react-zoom-pan-pinch": "^3.6.1", "react-zoom-pan-pinch": "^3.6.1",
"reactflow": "^11.11.4", "reactflow": "^11.11.4",
"use-debounce": "^10.0.4" "use-debounce": "^10.0.4",
"zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.2", "@lezer/generator": "^1.7.2",

View File

@ -5,12 +5,17 @@ import ConceptToaster from '@/app/ConceptToaster';
import Footer from '@/app/Footer'; import Footer from '@/app/Footer';
import Navigation from '@/app/Navigation'; import Navigation from '@/app/Navigation';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { NavigationState } from '@/context/NavigationContext'; import { NavigationState } from '@/context/NavigationContext';
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
import { globals } from '@/utils/constants'; import { globals } from '@/utils/constants';
function ApplicationLayout() { function ApplicationLayout() {
const { viewportHeight, mainHeight, showScroll, noNavigationAnimation } = useConceptOptions(); const mainHeight = useMainHeight();
const viewportHeight = useViewportHeight();
const showScroll = useAppLayoutStore(state => !state.noScroll);
const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const noFooter = useAppLayoutStore(state => state.noFooter);
return ( return (
<NavigationState> <NavigationState>
<div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'> <div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
@ -36,7 +41,7 @@ function ApplicationLayout() {
<Outlet /> <Outlet />
</Suspense> </Suspense>
</main> </main>
<Footer /> {!noNavigation && !noFooter ? <Footer /> : null}
</div> </div>
</div> </div>
</NavigationState> </NavigationState>

View File

@ -1,15 +1,10 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { external_urls } from '@/utils/constants'; import { external_urls } from '@/utils/constants';
import TextURL from '../components/ui/TextURL'; import TextURL from '../components/ui/TextURL';
function Footer() { function Footer() {
const { noNavigation, noFooter } = useConceptOptions();
if (noNavigation || noFooter) {
return null;
}
return ( return (
<footer <footer
className={clsx( className={clsx(

View File

@ -2,9 +2,9 @@ import clsx from 'clsx';
import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons'; import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { useAppLayoutStore } from '@/stores/appLayout';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { urls } from '../urls'; import { urls } from '../urls';
@ -16,7 +16,7 @@ import UserMenu from './UserMenu';
function Navigation() { function Navigation() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const size = useWindowSize(); const size = useWindowSize();
const { noNavigationAnimation } = useConceptOptions(); const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
const navigateHome = (event: CProps.EventMouse) => router.push(urls.home, event.ctrlKey || event.metaKey); const navigateHome = (event: CProps.EventMouse) => router.push(urls.home, event.ctrlKey || event.metaKey);
const navigateLibrary = (event: CProps.EventMouse) => router.push(urls.library, event.ctrlKey || event.metaKey); const navigateLibrary = (event: CProps.EventMouse) => router.push(urls.library, event.ctrlKey || event.metaKey);

View File

@ -2,10 +2,14 @@ import clsx from 'clsx';
import { IconDarkTheme, IconLightTheme, IconPin, IconUnpin } from '@/components/Icons'; import { IconDarkTheme, IconLightTheme, IconPin, IconUnpin } from '@/components/Icons';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useAppLayoutStore } from '@/stores/appLayout';
import { globals, PARAMETER } from '@/utils/constants'; import { globals, PARAMETER } from '@/utils/constants';
function ToggleNavigation() { function ToggleNavigation() {
const { noNavigationAnimation, noNavigation, toggleNoNavigation, toggleDarkMode, darkMode } = useConceptOptions(); const { toggleDarkMode, darkMode } = useConceptOptions();
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
const toggleNoNavigation = useAppLayoutStore(state => state.toggleNoNavigation);
const iconSize = !noNavigationAnimation ? '0.75rem' : '1rem'; const iconSize = !noNavigationAnimation ? '0.75rem' : '1rem';
return ( return (
<div <div

View File

@ -18,6 +18,7 @@ import DropdownButton from '@/components/ui/DropdownButton';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { usePreferencesStore } from '@/stores/preferences';
import { urls } from '../urls'; import { urls } from '../urls';
@ -27,10 +28,15 @@ interface UserDropdownProps {
} }
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) { function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
const { darkMode, adminMode, toggleAdminMode, toggleDarkMode, showHelp, toggleShowHelp } = useConceptOptions(); const { darkMode, toggleDarkMode } = useConceptOptions();
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user, logout } = useAuth(); const { user, logout } = useAuth();
const showHelp = usePreferencesStore(state => state.showHelp);
const toggleShowHelp = usePreferencesStore(state => state.toggleShowHelp);
const adminMode = usePreferencesStore(state => state.adminMode);
const toggleAdminMode = usePreferencesStore(state => state.toggleAdminMode);
function navigateProfile(event: CProps.EventMouse) { function navigateProfile(event: CProps.EventMouse) {
hideDropdown(); hideDropdown();
router.push(urls.profile, event.ctrlKey || event.metaKey); router.push(urls.profile, event.ctrlKey || event.metaKey);

View File

@ -1,9 +1,9 @@
import { IconLogin, IconUser2 } from '@/components/Icons'; import { IconLogin, IconUser2 } from '@/components/Icons';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import { usePreferencesStore } from '@/stores/preferences';
import { urls } from '../urls'; import { urls } from '../urls';
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
@ -12,7 +12,7 @@ import UserDropdown from './UserDropdown';
function UserMenu() { function UserMenu() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user, loading } = useAuth(); const { user, loading } = useAuth();
const { adminMode } = useConceptOptions(); const adminMode = usePreferencesStore(state => state.adminMode);
const menu = useDropdown(); const menu = useDropdown();
const navigateLogin = () => router.push(urls.login); const navigateLogin = () => router.push(urls.login);

View File

@ -2,8 +2,8 @@ import React, { Suspense } from 'react';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import Tooltip, { PlacesType } from '@/components/ui/Tooltip'; import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { usePreferencesStore } from '@/stores/preferences';
import { IconHelp } from '../Icons'; import { IconHelp } from '../Icons';
import { CProps } from '../props'; import { CProps } from '../props';
@ -29,7 +29,7 @@ interface BadgeHelpProps extends CProps.Styling {
* Display help icon with a manual page tooltip. * Display help icon with a manual page tooltip.
*/ */
function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpProps) { function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpProps) {
const { showHelp } = useConceptOptions(); const showHelp = usePreferencesStore(state => state.showHelp);
if (!showHelp) { if (!showHelp) {
return null; return null;

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { useFitHeight } from '@/stores/appLayout';
/** Maximum width of the viewer. */ /** Maximum width of the viewer. */
const MAXIMUM_WIDTH = 1600; const MAXIMUM_WIDTH = 1600;
@ -25,10 +25,9 @@ interface PDFViewerProps {
*/ */
function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) { function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { calculateHeight } = useConceptOptions();
const pageWidth = Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH)); const pageWidth = Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
const pageHeight = calculateHeight('1rem'); const pageHeight = useFitHeight('1rem');
return <embed src={`${file}#toolbar=0`} className='p-3' style={{ width: pageWidth, height: pageHeight }} />; return <embed src={`${file}#toolbar=0`} className='p-3' style={{ width: pageWidth, height: pageHeight }} />;
} }

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import InfoConstituenta from '@/components/info/InfoConstituenta'; import InfoConstituenta from '@/components/info/InfoConstituenta';
@ -12,28 +12,9 @@ import { globals, PARAMETER, storage } from '@/utils/constants';
import { contextOutsideScope } from '@/utils/labels'; import { contextOutsideScope } from '@/utils/labels';
interface IOptionsContext { interface IOptionsContext {
viewportHeight: string;
mainHeight: string;
darkMode: boolean; darkMode: boolean;
toggleDarkMode: () => void; toggleDarkMode: () => void;
adminMode: boolean;
toggleAdminMode: () => void;
noNavigationAnimation: boolean;
noNavigation: boolean;
toggleNoNavigation: () => void;
noFooter: boolean;
setNoFooter: React.Dispatch<React.SetStateAction<boolean>>;
showScroll: boolean;
setShowScroll: React.Dispatch<React.SetStateAction<boolean>>;
showHelp: boolean;
toggleShowHelp: () => void;
folderMode: boolean; folderMode: boolean;
setFolderMode: React.Dispatch<React.SetStateAction<boolean>>; setFolderMode: React.Dispatch<React.SetStateAction<boolean>>;
@ -41,8 +22,6 @@ interface IOptionsContext {
setLocation: React.Dispatch<React.SetStateAction<string>>; setLocation: React.Dispatch<React.SetStateAction<string>>;
setHoverCst: (newValue: IConstituenta | undefined) => void; setHoverCst: (newValue: IConstituenta | undefined) => void;
calculateHeight: (offset: string, minimum?: string) => string;
} }
const OptionsContext = createContext<IOptionsContext | null>(null); const OptionsContext = createContext<IOptionsContext | null>(null);
@ -56,17 +35,10 @@ export const useConceptOptions = () => {
export const OptionsState = ({ children }: React.PropsWithChildren) => { export const OptionsState = ({ children }: React.PropsWithChildren) => {
const [darkMode, setDarkMode] = useLocalStorage(storage.themeDark, false); const [darkMode, setDarkMode] = useLocalStorage(storage.themeDark, false);
const [adminMode, setAdminMode] = useLocalStorage(storage.optionsAdmin, false);
const [showHelp, setShowHelp] = useLocalStorage(storage.optionsHelp, true);
const [noNavigation, setNoNavigation] = useState(false);
const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true); const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true);
const [location, setLocation] = useLocalStorage<string>(storage.librarySearchLocation, ''); const [location, setLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
const [noNavigationAnimation, setNoNavigationAnimation] = useState(false);
const [noFooter, setNoFooter] = useState(false);
const [showScroll, setShowScroll] = useState(false);
const [hoverCst, setHoverCst] = useState<IConstituenta | undefined>(undefined); const [hoverCst, setHoverCst] = useState<IConstituenta | undefined>(undefined);
function setDarkClass(isDark: boolean) { function setDarkClass(isDark: boolean) {
@ -83,29 +55,6 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
setDarkClass(darkMode); setDarkClass(darkMode);
}, [darkMode]); }, [darkMode]);
const toggleNoNavigation = useCallback(() => {
if (noNavigation) {
setNoNavigationAnimation(false);
setNoNavigation(false);
} else {
setNoNavigationAnimation(true);
setTimeout(() => setNoNavigation(true), PARAMETER.moveDuration);
}
}, [noNavigation]);
const calculateHeight = useCallback(
(offset: string, minimum: string = '0px') => {
if (noNavigation) {
return `max(calc(100dvh - (${offset})), ${minimum})`;
} else if (noFooter) {
return `max(calc(100dvh - 3rem - (${offset})), ${minimum})`;
} else {
return `max(calc(100dvh - 6.75rem - (${offset})), ${minimum})`;
}
},
[noNavigation, noFooter]
);
const toggleDarkMode = useCallback(() => { const toggleDarkMode = useCallback(() => {
if (!document.startViewTransition) { if (!document.startViewTransition) {
setDarkMode(prev => !prev); setDarkMode(prev => !prev);
@ -129,43 +78,15 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
} }
}, [setDarkMode]); }, [setDarkMode]);
const mainHeight = useMemo(() => {
if (noNavigation) {
return '100dvh';
} else if (noFooter) {
return 'calc(100dvh - 3rem)';
} else {
return 'calc(100dvh - 6.75rem)';
}
}, [noNavigation, noFooter]);
const viewportHeight = useMemo(() => {
return !noNavigation ? 'calc(100dvh - 3rem)' : '100dvh';
}, [noNavigation]);
return ( return (
<OptionsContext <OptionsContext
value={{ value={{
darkMode, darkMode,
adminMode,
noNavigationAnimation,
noNavigation,
noFooter,
folderMode, folderMode,
setFolderMode, setFolderMode,
location, location,
setLocation, setLocation,
showScroll,
showHelp,
toggleDarkMode: toggleDarkMode, toggleDarkMode: toggleDarkMode,
toggleAdminMode: () => setAdminMode(prev => !prev),
toggleNoNavigation: toggleNoNavigation,
setNoFooter,
setShowScroll,
toggleShowHelp: () => setShowHelp(prev => !prev),
viewportHeight,
mainHeight,
calculateHeight,
setHoverCst setHoverCst
}} }}
> >

View File

@ -21,10 +21,10 @@ import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI'
import { ILibraryFilter } from '@/models/miscellaneous'; import { ILibraryFilter } from '@/models/miscellaneous';
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform'; import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
import { RSFormLoader } from '@/models/RSFormLoader'; import { RSFormLoader } from '@/models/RSFormLoader';
import { usePreferencesStore } from '@/stores/preferences';
import { contextOutsideScope } from '@/utils/labels'; import { contextOutsideScope } from '@/utils/labels';
import { useAuth } from './AuthContext'; import { useAuth } from './AuthContext';
import { useConceptOptions } from './ConceptOptionsContext';
interface ILibraryContext { interface ILibraryContext {
items: ILibraryItem[]; items: ILibraryItem[];
@ -63,7 +63,7 @@ export const useLibrary = (): ILibraryContext => {
export const LibraryState = ({ children }: React.PropsWithChildren) => { export const LibraryState = ({ children }: React.PropsWithChildren) => {
const { user, loading: userLoading } = useAuth(); const { user, loading: userLoading } = useAuth();
const { adminMode } = useConceptOptions(); const adminMode = usePreferencesStore(state => state.adminMode);
const [items, setItems] = useState<ILibraryItem[]>([]); const [items, setItems] = useState<ILibraryItem[]>([]);
const [templates, setTemplates] = useState<ILibraryItem[]>([]); const [templates, setTemplates] = useState<ILibraryItem[]>([]);

View File

@ -3,18 +3,18 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'; import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useAppLayoutStore, useFitHeight } from '@/stores/appLayout';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
function DatabaseSchemaPage() { function DatabaseSchemaPage() {
const { calculateHeight, setNoFooter } = useConceptOptions(); const hideFooter = useAppLayoutStore(state => state.hideFooter);
const panelHeight = calculateHeight('0px'); const panelHeight = useFitHeight('0px');
useEffect(() => { useEffect(() => {
setNoFooter(true); hideFooter(true);
return () => setNoFooter(false); return () => hideFooter(false);
}, [setNoFooter]); }, [hideFooter]);
return ( return (
<div className='cc-fade-in flex justify-center overflow-hidden' style={{ maxHeight: panelHeight }}> <div className='cc-fade-in flex justify-center overflow-hidden' style={{ maxHeight: panelHeight }}>

View File

@ -16,6 +16,7 @@ import useLocalStorage from '@/hooks/useLocalStorage';
import { ILibraryItem, IRenameLocationData, LocationHead } from '@/models/library'; import { ILibraryItem, IRenameLocationData, LocationHead } from '@/models/library';
import { ILibraryFilter } from '@/models/miscellaneous'; import { ILibraryFilter } from '@/models/miscellaneous';
import { UserID } from '@/models/user'; import { UserID } from '@/models/user';
import { useAppLayoutStore } from '@/stores/appLayout';
import { storage } from '@/utils/constants'; import { storage } from '@/utils/constants';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
import { convertToCSV, toggleTristateFlag } from '@/utils/utils'; import { convertToCSV, toggleTristateFlag } from '@/utils/utils';
@ -29,6 +30,7 @@ function LibraryPage() {
const { user } = useAuth(); const { user } = useAuth();
const [items, setItems] = useState<ILibraryItem[]>([]); const [items, setItems] = useState<ILibraryItem[]>([]);
const options = useConceptOptions(); const options = useConceptOptions();
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [path, setPath] = useState(''); const [path, setPath] = useState('');
@ -133,7 +135,7 @@ function LibraryPage() {
/> />
) : null} ) : null}
<Overlay <Overlay
position={options.noNavigation ? 'top-[0.25rem] right-[3rem]' : 'top-[0.25rem] right-0'} position={noNavigation ? 'top-[0.25rem] right-[3rem]' : 'top-[0.25rem] right-0'}
layer='z-tooltip' layer='z-tooltip'
className='cc-animate-position' className='cc-animate-position'
> >

View File

@ -12,12 +12,12 @@ import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } fro
import FlexColumn from '@/components/ui/FlexColumn'; import FlexColumn from '@/components/ui/FlexColumn';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useUsers } from '@/context/UsersContext'; import { useUsers } from '@/context/UsersContext';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { ILibraryItem, LibraryItemType } from '@/models/library'; import { ILibraryItem, LibraryItemType } from '@/models/library';
import { useFitHeight } from '@/stores/appLayout';
import { APP_COLORS } from '@/styling/color'; import { APP_COLORS } from '@/styling/color';
import { storage } from '@/utils/constants'; import { storage } from '@/utils/constants';
@ -34,7 +34,6 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
const router = useConceptNavigation(); const router = useConceptNavigation();
const intl = useIntl(); const intl = useIntl();
const { getUserLabel } = useUsers(); const { getUserLabel } = useUsers();
const { calculateHeight } = useConceptOptions();
const [itemsPerPage, setItemsPerPage] = useLocalStorage<number>(storage.libraryPagination, 50); const [itemsPerPage, setItemsPerPage] = useLocalStorage<number>(storage.libraryPagination, 50);
function handleOpenItem(item: ILibraryItem, event: CProps.EventMouse) { function handleOpenItem(item: ILibraryItem, event: CProps.EventMouse) {
@ -140,7 +139,7 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
}) })
]; ];
const tableHeight = calculateHeight('2.2rem'); const tableHeight = useFitHeight('2.2rem');
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [ const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
{ {

View File

@ -8,11 +8,11 @@ import { CProps } from '@/components/props';
import SelectLocation from '@/components/select/SelectLocation'; import SelectLocation from '@/components/select/SelectLocation';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { FolderNode, FolderTree } from '@/models/FolderTree'; import { FolderNode, FolderTree } from '@/models/FolderTree';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { useFitHeight } from '@/stores/appLayout';
import { PARAMETER, prefixes } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
@ -39,7 +39,6 @@ function ViewSideLocation({
}: ViewSideLocationProps) { }: ViewSideLocationProps) {
const { user } = useAuth(); const { user } = useAuth();
const { items } = useLibrary(); const { items } = useLibrary();
const { calculateHeight } = useConceptOptions();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const canRename = (() => { const canRename = (() => {
@ -56,7 +55,7 @@ function ViewSideLocation({
return located.length !== 0; return located.length !== 0;
})(); })();
const maxHeight = calculateHeight('4.5rem'); const maxHeight = useFitHeight('4.5rem');
function handleClickFolder(event: CProps.EventMouse, target: FolderNode) { function handleClickFolder(event: CProps.EventMouse, target: FolderNode) {
event.preventDefault(); event.preventDefault();

View File

@ -3,10 +3,10 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { useMainHeight } from '@/stores/appLayout';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import TopicsList from './TopicsList'; import TopicsList from './TopicsList';
@ -17,7 +17,7 @@ function ManualsPage() {
const query = useQueryStrings(); const query = useQueryStrings();
const activeTopic = (query.get('topic') || HelpTopic.MAIN) as HelpTopic; const activeTopic = (query.get('topic') || HelpTopic.MAIN) as HelpTopic;
const { mainHeight } = useConceptOptions(); const mainHeight = useMainHeight();
const onSelectTopic = useCallback( const onSelectTopic = useCallback(
(newTopic: HelpTopic) => { (newTopic: HelpTopic) => {

View File

@ -6,9 +6,9 @@ import { useCallback } from 'react';
import { IconMenuFold, IconMenuUnfold } from '@/components/Icons'; import { IconMenuFold, IconMenuUnfold } from '@/components/Icons';
import Button from '@/components/ui/Button'; import Button from '@/components/ui/Button';
import SelectTree from '@/components/ui/SelectTree'; import SelectTree from '@/components/ui/SelectTree';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import { HelpTopic, topicParent } from '@/models/miscellaneous'; import { HelpTopic, topicParent } from '@/models/miscellaneous';
import { useAppLayoutStore, useFitHeight } from '@/stores/appLayout';
import { PARAMETER, prefixes } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
import { describeHelpTopic, labelHelpTopic } from '@/utils/labels'; import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
@ -19,7 +19,8 @@ interface TopicsDropdownProps {
function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) { function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
const menu = useDropdown(); const menu = useDropdown();
const { noNavigation, calculateHeight } = useConceptOptions(); const noNavigation = useAppLayoutStore(state => state.noNavigation);
const treeHeight = useFitHeight('4rem + 2px');
const handleSelectTopic = useCallback( const handleSelectTopic = useCallback(
(topic: HelpTopic) => { (topic: HelpTopic) => {
@ -67,7 +68,7 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
'bg-prim-200' 'bg-prim-200'
)} )}
style={{ style={{
maxHeight: calculateHeight('4rem + 2px'), maxHeight: treeHeight,
transitionProperty: 'clip-path', transitionProperty: 'clip-path',
transitionDuration: `${PARAMETER.moveDuration}ms`, transitionDuration: `${PARAMETER.moveDuration}ms`,
clipPath: menu.isOpen ? 'inset(0% 0% 0% 0%)' : 'inset(0% 100% 0% 0%)' clipPath: menu.isOpen ? 'inset(0% 0% 0% 0%)' : 'inset(0% 100% 0% 0%)'

View File

@ -1,8 +1,8 @@
import clsx from 'clsx'; import clsx from 'clsx';
import SelectTree from '@/components/ui/SelectTree'; import SelectTree from '@/components/ui/SelectTree';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { HelpTopic, topicParent } from '@/models/miscellaneous'; import { HelpTopic, topicParent } from '@/models/miscellaneous';
import { useFitHeight } from '@/stores/appLayout';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { describeHelpTopic, labelHelpTopic } from '@/utils/labels'; import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
@ -12,7 +12,7 @@ interface TopicsStaticProps {
} }
function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) { function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
const { calculateHeight } = useConceptOptions(); const topicsHeight = useFitHeight('1rem + 2px');
return ( return (
<SelectTree <SelectTree
items={Object.values(HelpTopic).map(item => item as HelpTopic)} items={Object.values(HelpTopic).map(item => item as HelpTopic)}
@ -31,7 +31,7 @@ function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
'text-xs sm:text-sm bg-prim-200', 'text-xs sm:text-sm bg-prim-200',
'select-none' 'select-none'
)} )}
style={{ maxHeight: calculateHeight('1rem + 2px') }} style={{ maxHeight: topicsHeight }}
/> />
); );
} }

View File

@ -1,15 +1,15 @@
'use client'; 'use client';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import TopicPage from '@/pages/ManualsPage/TopicPage'; import TopicPage from '@/pages/ManualsPage/TopicPage';
import { useMainHeight } from '@/stores/appLayout';
interface ViewTopicProps { interface ViewTopicProps {
topic: HelpTopic; topic: HelpTopic;
} }
function ViewTopic({ topic }: ViewTopicProps) { function ViewTopic({ topic }: ViewTopicProps) {
const { mainHeight } = useConceptOptions(); const mainHeight = useMainHeight();
return ( return (
<div <div
key={topic} key={topic}

View File

@ -18,11 +18,11 @@ import {
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useOSS } from '@/context/OssContext'; import { useOSS } from '@/context/OssContext';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { OssNode } from '@/models/miscellaneous'; import { OssNode } from '@/models/miscellaneous';
import { OperationID } from '@/models/oss'; import { OperationID } from '@/models/oss';
import { useMainHeight } from '@/stores/appLayout';
import { APP_COLORS } from '@/styling/color'; import { APP_COLORS } from '@/styling/color';
import { PARAMETER, storage } from '@/utils/constants'; import { PARAMETER, storage } from '@/utils/constants';
import { errors } from '@/utils/labels'; import { errors } from '@/utils/labels';
@ -41,7 +41,7 @@ interface OssFlowProps {
} }
function OssFlow({ isModified, setIsModified }: OssFlowProps) { function OssFlow({ isModified, setIsModified }: OssFlowProps) {
const { mainHeight } = useConceptOptions(); const mainHeight = useMainHeight();
const model = useOSS(); const model = useOSS();
const controller = useOssEdit(); const controller = useOssEdit();
const flow = useReactFlow(); const flow = useReactFlow();

View File

@ -6,7 +6,6 @@ import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext'; import { useOSS } from '@/context/OssContext';
@ -32,6 +31,7 @@ import {
OperationType OperationType
} from '@/models/oss'; } from '@/models/oss';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserLevel } from '@/models/user';
import { usePreferencesStore } from '@/stores/preferences';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { errors, information } from '@/utils/labels'; import { errors, information } from '@/utils/labels';
@ -95,7 +95,7 @@ interface OssEditStateProps {
export const OssEditState = ({ selected, setSelected, children }: React.PropsWithChildren<OssEditStateProps>) => { export const OssEditState = ({ selected, setSelected, children }: React.PropsWithChildren<OssEditStateProps>) => {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuth();
const { adminMode } = useConceptOptions(); const adminMode = usePreferencesStore(state => state.adminMode);
const { accessLevel, setAccessLevel } = useAccessMode(); const { accessLevel, setAccessLevel } = useAccessMode();
const model = useOSS(); const model = useOSS();
const library = useLibrary(); const library = useLibrary();

View File

@ -13,12 +13,12 @@ import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext'; import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext'; import { useOSS } from '@/context/OssContext';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
import { OperationID } from '@/models/oss'; import { OperationID } from '@/models/oss';
import { useAppLayoutStore } from '@/stores/appLayout';
import { information, prompts } from '@/utils/labels'; import { information, prompts } from '@/utils/labels';
import EditorRSForm from './EditorOssCard'; import EditorRSForm from './EditorOssCard';
@ -37,7 +37,7 @@ function OssTabs() {
const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH; const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH;
const { user } = useAuth(); const { user } = useAuth();
const { setNoFooter } = useConceptOptions(); const hideFooter = useAppLayoutStore(state => state.hideFooter);
const { schema, loading, loadingError: errorLoading } = useOSS(); const { schema, loading, loadingError: errorLoading } = useOSS();
const { destroyItem } = useLibrary(); const { destroyItem } = useLibrary();
@ -61,8 +61,8 @@ function OssTabs() {
}, [schema, schema?.title]); }, [schema, schema?.title]);
useEffect(() => { useEffect(() => {
setNoFooter(activeTab === OssTabID.GRAPH); hideFooter(activeTab === OssTabID.GRAPH);
}, [activeTab, setNoFooter]); }, [activeTab, hideFooter]);
function navigateTab(tab: OssTabID) { function navigateTab(tab: OssTabID) {
if (!schema) { if (!schema) {

View File

@ -3,10 +3,10 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { ConstituentaID, IConstituenta } from '@/models/rsform'; import { ConstituentaID, IConstituenta } from '@/models/rsform';
import { useMainHeight } from '@/stores/appLayout';
import { globals, storage } from '@/utils/constants'; import { globals, storage } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -27,7 +27,7 @@ interface EditorConstituentaProps {
function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) { function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) {
const controller = useRSEdit(); const controller = useRSEdit();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { mainHeight } = useConceptOptions(); const mainHeight = useMainHeight();
const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true); const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true);
const [toggleReset, setToggleReset] = useState(false); const [toggleReset, setToggleReset] = useState(false);

View File

@ -9,10 +9,10 @@ import { type RowSelectionState } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import SearchBar from '@/components/ui/SearchBar'; import SearchBar from '@/components/ui/SearchBar';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { CstMatchMode } from '@/models/miscellaneous'; import { CstMatchMode } from '@/models/miscellaneous';
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform'; import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
import { matchConstituenta } from '@/models/rsformAPI'; import { matchConstituenta } from '@/models/rsformAPI';
import { useFitHeight } from '@/stores/appLayout';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
import { convertToCSV } from '@/utils/utils'; import { convertToCSV } from '@/utils/utils';
@ -25,7 +25,6 @@ interface EditorRSListProps {
} }
function EditorRSList({ onOpenEdit }: EditorRSListProps) { function EditorRSList({ onOpenEdit }: EditorRSListProps) {
const { calculateHeight } = useConceptOptions();
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const controller = useRSEdit(); const controller = useRSEdit();
@ -136,7 +135,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
return false; return false;
} }
const tableHeight = calculateHeight('4.05rem + 5px'); const tableHeight = useFitHeight('4.05rem + 5px');
return ( return (
<> <>

View File

@ -24,12 +24,12 @@ import SelectedCounter from '@/components/info/SelectedCounter';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import ToolbarGraphSelection from '@/components/select/ToolbarGraphSelection'; import ToolbarGraphSelection from '@/components/select/ToolbarGraphSelection';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import DlgGraphParams from '@/dialogs/DlgGraphParams'; import DlgGraphParams from '@/dialogs/DlgGraphParams';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { GraphColoring, GraphFilterParams } from '@/models/miscellaneous'; import { GraphColoring, GraphFilterParams } from '@/models/miscellaneous';
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform'; import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
import { isBasicConcept } from '@/models/rsformAPI'; import { isBasicConcept } from '@/models/rsformAPI';
import { useMainHeight } from '@/stores/appLayout';
import { APP_COLORS, colorBgGraphNode } from '@/styling/color'; import { APP_COLORS, colorBgGraphNode } from '@/styling/color';
import { PARAMETER, storage } from '@/utils/constants'; import { PARAMETER, storage } from '@/utils/constants';
import { errors } from '@/utils/labels'; import { errors } from '@/utils/labels';
@ -53,7 +53,7 @@ interface TGFlowProps {
} }
function TGFlow({ onOpenEdit }: TGFlowProps) { function TGFlow({ onOpenEdit }: TGFlowProps) {
const { mainHeight } = useConceptOptions(); const mainHeight = useMainHeight();
const controller = useRSEdit(); const controller = useRSEdit();
const [nodes, setNodes, onNodesChange] = useNodesState([]); const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges] = useEdgesState([]); const [edges, setEdges] = useEdgesState([]);

View File

@ -11,6 +11,7 @@ import useLocalStorage from '@/hooks/useLocalStorage';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { GraphColoring } from '@/models/miscellaneous'; import { GraphColoring } from '@/models/miscellaneous';
import { ConstituentaID, IRSForm } from '@/models/rsform'; import { ConstituentaID, IRSForm } from '@/models/rsform';
import { useFitHeight } from '@/stores/appLayout';
import { APP_COLORS, colorBgGraphNode } from '@/styling/color'; import { APP_COLORS, colorBgGraphNode } from '@/styling/color';
import { globals, PARAMETER, prefixes, storage } from '@/utils/constants'; import { globals, PARAMETER, prefixes, storage } from '@/utils/constants';
@ -26,11 +27,11 @@ interface ViewHiddenProps {
} }
function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) { function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) {
const { calculateHeight } = useConceptOptions();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const localSelected = items.filter(id => selected.includes(id)); const localSelected = items.filter(id => selected.includes(id));
const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false); const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false);
const { setHoverCst } = useConceptOptions(); const { setHoverCst } = useConceptOptions();
const hiddenHeight = useFitHeight(windowSize.isSmall ? '10.4rem + 2px' : '12.5rem + 2px');
function handleClick(cstID: ConstituentaID, event: CProps.EventMouse) { function handleClick(cstID: ConstituentaID, event: CProps.EventMouse) {
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
@ -77,7 +78,7 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
'cc-scroll-y' 'cc-scroll-y'
)} )}
style={{ style={{
maxHeight: calculateHeight(windowSize.isSmall ? '10.4rem + 2px' : '12.5rem + 2px'), maxHeight: hiddenHeight,
transitionProperty: 'clip-path', transitionProperty: 'clip-path',
transitionDuration: `${PARAMETER.fastAnimation}ms`, transitionDuration: `${PARAMETER.fastAnimation}ms`,
transitionTimingFunction: 'ease-out', transitionTimingFunction: 'ease-out',

View File

@ -7,7 +7,6 @@ import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useRSForm } from '@/context/RSFormContext'; import { useRSForm } from '@/context/RSFormContext';
import DlgChangeLocation from '@/dialogs/DlgChangeLocation'; import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
@ -51,6 +50,7 @@ import {
} from '@/models/rsform'; } from '@/models/rsform';
import { generateAlias } from '@/models/rsformAPI'; import { generateAlias } from '@/models/rsformAPI';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserLevel } from '@/models/user';
import { usePreferencesStore } from '@/stores/preferences';
import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { information, prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
@ -142,7 +142,7 @@ export const RSEditState = ({
}: React.PropsWithChildren<RSEditStateProps>) => { }: React.PropsWithChildren<RSEditStateProps>) => {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuth();
const { adminMode } = useConceptOptions(); const adminMode = usePreferencesStore(state => state.adminMode);
const { accessLevel, setAccessLevel } = useAccessMode(); const { accessLevel, setAccessLevel } = useAccessMode();
const model = useRSForm(); const model = useRSForm();

View File

@ -13,13 +13,13 @@ import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useGlobalOss } from '@/context/GlobalOssContext'; import { useGlobalOss } from '@/context/GlobalOssContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext'; import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
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 { useAppLayoutStore } from '@/stores/appLayout';
import { PARAMETER, prefixes } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
import { information, labelVersion, prompts } from '@/utils/labels'; import { information, labelVersion, prompts } from '@/utils/labels';
@ -45,7 +45,7 @@ function RSTabs() {
const version = query.get('v') ? Number(query.get('v')) : undefined; const version = query.get('v') ? Number(query.get('v')) : undefined;
const cstQuery = query.get('active'); const cstQuery = query.get('active');
const { setNoFooter } = useConceptOptions(); const hideFooter = useAppLayoutStore(state => state.hideFooter);
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm(); const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
const library = useLibrary(); const library = useLibrary();
const oss = useGlobalOss(); const oss = useGlobalOss();
@ -73,7 +73,7 @@ function RSTabs() {
}, [schema, schema?.title]); }, [schema, schema?.title]);
useEffect(() => { useEffect(() => {
setNoFooter(activeTab !== RSTabID.CARD); hideFooter(activeTab !== RSTabID.CARD);
setIsModified(false); setIsModified(false);
if (activeTab === RSTabID.CST_EDIT) { if (activeTab === RSTabID.CST_EDIT) {
const cstID = Number(cstQuery); const cstID = Number(cstQuery);
@ -83,8 +83,8 @@ function RSTabs() {
setSelected([]); setSelected([]);
} }
} }
return () => setNoFooter(false); return () => hideFooter(false);
}, [activeTab, cstQuery, setSelected, schema, setNoFooter, setIsModified]); }, [activeTab, cstQuery, setSelected, schema, hideFooter, setIsModified]);
function navigateTab(tab: RSTabID, activeID?: ConstituentaID) { function navigateTab(tab: RSTabID, activeID?: ConstituentaID) {
if (!schema) { if (!schema) {

View File

@ -4,10 +4,10 @@ import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { UserLevel } from '@/models/user'; import { UserLevel } from '@/models/user';
import { useFitHeight } from '@/stores/appLayout';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import ConstituentsSearch from './ConstituentsSearch'; import ConstituentsSearch from './ConstituentsSearch';
@ -26,9 +26,9 @@ interface ViewConstituentsProps {
} }
function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit, isMounted }: ViewConstituentsProps) { function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit, isMounted }: ViewConstituentsProps) {
const { calculateHeight } = useConceptOptions();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { accessLevel } = useAccessMode(); const { accessLevel } = useAccessMode();
const listHeight = useFitHeight(!isBottom ? '8.2rem' : accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem');
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []); const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
@ -57,11 +57,7 @@ function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit,
setFiltered={setFilteredData} setFiltered={setFilteredData}
/> />
<TableSideConstituents <TableSideConstituents
maxHeight={ maxHeight={listHeight}
isBottom
? calculateHeight(accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem')
: calculateHeight('8.2rem')
}
items={filteredData} items={filteredData}
activeCst={activeCst} activeCst={activeCst}
onOpenEdit={onOpenEdit} onOpenEdit={onOpenEdit}

View File

@ -0,0 +1,68 @@
import { create } from 'zustand';
import { PARAMETER } from '@/utils/constants';
/** Application layout state manager. */
interface AppLayoutStore {
noNavigation: boolean;
noNavigationAnimation: boolean;
toggleNoNavigation: () => void;
noFooter: boolean;
hideFooter: (value?: boolean) => void;
noScroll: boolean;
hideScroll: (value?: boolean) => void;
}
export const useAppLayoutStore = create<AppLayoutStore>()(set => ({
noNavigation: false,
noNavigationAnimation: false,
toggleNoNavigation: () =>
set(state => {
if (state.noNavigation) {
return { noNavigation: false, noNavigationAnimation: false };
} else {
setTimeout(() => set({ noNavigation: true, noNavigationAnimation: true }), PARAMETER.moveDuration);
return { noNavigation: false, noNavigationAnimation: true };
}
}),
noFooter: false,
hideFooter: value => set({ noFooter: value ?? true }),
noScroll: true,
hideScroll: value => set({ noScroll: value ?? true })
}));
/** Utility function that returns the height of the main area. */
export function useMainHeight(): string {
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const noFooter = useAppLayoutStore(state => state.noFooter);
if (noNavigation) {
return '100dvh';
} else if (noFooter) {
return 'calc(100dvh - 3rem)';
} else {
return 'calc(100dvh - 6.75rem)';
}
}
/** Utility function that returns the height of the viewport. */
export function useViewportHeight(): string {
const noNavigation = useAppLayoutStore(state => state.noNavigation);
return !noNavigation ? 'calc(100dvh - 3rem)' : '100dvh';
}
/** Utility function that returns the height of the viewport with a given offset. */
export function useFitHeight(offset: string, minimum: string = '0px'): string {
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const noFooter = useAppLayoutStore(state => state.noFooter);
if (noNavigation) {
return `max(calc(100dvh - (${offset})), ${minimum})`;
} else if (noFooter) {
return `max(calc(100dvh - 3rem - (${offset})), ${minimum})`;
} else {
return `max(calc(100dvh - 6.75rem - (${offset})), ${minimum})`;
}
}

View File

@ -0,0 +1,24 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface PreferencesStore {
showHelp: boolean;
adminMode: boolean;
toggleShowHelp: () => void;
toggleAdminMode: () => void;
}
export const usePreferencesStore = create<PreferencesStore>()(
persist(
set => ({
showHelp: true,
adminMode: false,
toggleShowHelp: () => set(state => ({ showHelp: !state.showHelp })),
toggleAdminMode: () => set(state => ({ adminMode: !state.adminMode }))
}),
{
version: 1,
name: 'portal.preferences'
}
)
);

View File

@ -109,8 +109,6 @@ export const storage = {
PREFIX: 'portal.', PREFIX: 'portal.',
themeDark: 'theme.dark', themeDark: 'theme.dark',
optionsAdmin: 'options.admin',
optionsHelp: 'options.help',
rseditShowList: 'rsedit.show_list', rseditShowList: 'rsedit.show_list',
rseditShowControls: 'rsedit.show_controls', rseditShowControls: 'rsedit.show_controls',