mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
R: Migrating to zustand for local state management pt1
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
This commit is contained in:
parent
5679c86dac
commit
4c9b0b28b8
|
@ -44,6 +44,7 @@ This readme file is used mostly to document project dependencies and conventions
|
|||
- use-debounce
|
||||
- qrcode.react
|
||||
- html-to-image
|
||||
- zustand
|
||||
- @tanstack/react-table
|
||||
- @uiw/react-codemirror
|
||||
- @uiw/codemirror-themes
|
||||
|
|
190
rsconcept/frontend/package-lock.json
generated
190
rsconcept/frontend/package-lock.json
generated
|
@ -30,7 +30,8 @@
|
|||
"react-tooltip": "^5.28.0",
|
||||
"react-zoom-pan-pinch": "^3.6.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"use-debounce": "^10.0.4"
|
||||
"use-debounce": "^10.0.4",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.7.2",
|
||||
|
@ -2445,6 +2446,34 @@
|
|||
"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": {
|
||||
"version": "11.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
|
||||
|
@ -2460,6 +2489,34 @@
|
|||
"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": {
|
||||
"version": "11.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
|
||||
|
@ -2481,6 +2538,34 @@
|
|||
"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": {
|
||||
"version": "11.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
|
||||
|
@ -2500,6 +2585,34 @@
|
|||
"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": {
|
||||
"version": "2.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
|
||||
|
@ -2517,6 +2630,34 @@
|
|||
"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": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
|
||||
|
@ -2532,6 +2673,34 @@
|
|||
"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": {
|
||||
"version": "4.30.1",
|
||||
"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": {
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
|
||||
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
|
||||
"integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"@types/react": ">=18.0.0",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
"react": ">=18.0.0",
|
||||
"use-sync-external-store": ">=1.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
|
@ -10735,6 +10902,9 @@
|
|||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
"react-tooltip": "^5.28.0",
|
||||
"react-zoom-pan-pinch": "^3.6.1",
|
||||
"reactflow": "^11.11.4",
|
||||
"use-debounce": "^10.0.4"
|
||||
"use-debounce": "^10.0.4",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.7.2",
|
||||
|
|
|
@ -5,12 +5,17 @@ import ConceptToaster from '@/app/ConceptToaster';
|
|||
import Footer from '@/app/Footer';
|
||||
import Navigation from '@/app/Navigation';
|
||||
import Loader from '@/components/ui/Loader';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { NavigationState } from '@/context/NavigationContext';
|
||||
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
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 (
|
||||
<NavigationState>
|
||||
<div className='min-w-[20rem] antialiased h-full max-w-[120rem] mx-auto'>
|
||||
|
@ -36,7 +41,7 @@ function ApplicationLayout() {
|
|||
<Outlet />
|
||||
</Suspense>
|
||||
</main>
|
||||
<Footer />
|
||||
{!noNavigation && !noFooter ? <Footer /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</NavigationState>
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { external_urls } from '@/utils/constants';
|
||||
|
||||
import TextURL from '../components/ui/TextURL';
|
||||
|
||||
function Footer() {
|
||||
const { noNavigation, noFooter } = useConceptOptions();
|
||||
if (noNavigation || noFooter) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<footer
|
||||
className={clsx(
|
||||
|
|
|
@ -2,9 +2,9 @@ import clsx from 'clsx';
|
|||
|
||||
import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons';
|
||||
import { CProps } from '@/components/props';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import { urls } from '../urls';
|
||||
|
@ -16,7 +16,7 @@ import UserMenu from './UserMenu';
|
|||
function Navigation() {
|
||||
const router = useConceptNavigation();
|
||||
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 navigateLibrary = (event: CProps.EventMouse) => router.push(urls.library, event.ctrlKey || event.metaKey);
|
||||
|
|
|
@ -2,10 +2,14 @@ import clsx from 'clsx';
|
|||
|
||||
import { IconDarkTheme, IconLightTheme, IconPin, IconUnpin } from '@/components/Icons';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { globals, PARAMETER } from '@/utils/constants';
|
||||
|
||||
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';
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -18,6 +18,7 @@ import DropdownButton from '@/components/ui/DropdownButton';
|
|||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
import { urls } from '../urls';
|
||||
|
||||
|
@ -27,10 +28,15 @@ interface UserDropdownProps {
|
|||
}
|
||||
|
||||
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||
const { darkMode, adminMode, toggleAdminMode, toggleDarkMode, showHelp, toggleShowHelp } = useConceptOptions();
|
||||
const { darkMode, toggleDarkMode } = useConceptOptions();
|
||||
const router = useConceptNavigation();
|
||||
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) {
|
||||
hideDropdown();
|
||||
router.push(urls.profile, event.ctrlKey || event.metaKey);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { IconLogin, IconUser2 } from '@/components/Icons';
|
||||
import Loader from '@/components/ui/Loader';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
import { urls } from '../urls';
|
||||
import NavigationButton from './NavigationButton';
|
||||
|
@ -12,7 +12,7 @@ import UserDropdown from './UserDropdown';
|
|||
function UserMenu() {
|
||||
const router = useConceptNavigation();
|
||||
const { user, loading } = useAuth();
|
||||
const { adminMode } = useConceptOptions();
|
||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
const menu = useDropdown();
|
||||
|
||||
const navigateLogin = () => router.push(urls.login);
|
||||
|
|
|
@ -2,8 +2,8 @@ import React, { Suspense } from 'react';
|
|||
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
import { IconHelp } from '../Icons';
|
||||
import { CProps } from '../props';
|
||||
|
@ -29,7 +29,7 @@ interface BadgeHelpProps extends CProps.Styling {
|
|||
* Display help icon with a manual page tooltip.
|
||||
*/
|
||||
function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpProps) {
|
||||
const { showHelp } = useConceptOptions();
|
||||
const showHelp = usePreferencesStore(state => state.showHelp);
|
||||
|
||||
if (!showHelp) {
|
||||
return null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
|
||||
/** Maximum width of the viewer. */
|
||||
const MAXIMUM_WIDTH = 1600;
|
||||
|
@ -25,10 +25,9 @@ interface PDFViewerProps {
|
|||
*/
|
||||
function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
|
||||
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 }} />;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'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 InfoConstituenta from '@/components/info/InfoConstituenta';
|
||||
|
@ -12,28 +12,9 @@ import { globals, PARAMETER, storage } from '@/utils/constants';
|
|||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
interface IOptionsContext {
|
||||
viewportHeight: string;
|
||||
mainHeight: string;
|
||||
|
||||
darkMode: boolean;
|
||||
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;
|
||||
setFolderMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
|
@ -41,8 +22,6 @@ interface IOptionsContext {
|
|||
setLocation: React.Dispatch<React.SetStateAction<string>>;
|
||||
|
||||
setHoverCst: (newValue: IConstituenta | undefined) => void;
|
||||
|
||||
calculateHeight: (offset: string, minimum?: string) => string;
|
||||
}
|
||||
|
||||
const OptionsContext = createContext<IOptionsContext | null>(null);
|
||||
|
@ -56,17 +35,10 @@ export const useConceptOptions = () => {
|
|||
|
||||
export const OptionsState = ({ children }: React.PropsWithChildren) => {
|
||||
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 [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);
|
||||
|
||||
function setDarkClass(isDark: boolean) {
|
||||
|
@ -83,29 +55,6 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
|
|||
setDarkClass(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(() => {
|
||||
if (!document.startViewTransition) {
|
||||
setDarkMode(prev => !prev);
|
||||
|
@ -129,43 +78,15 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => {
|
|||
}
|
||||
}, [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 (
|
||||
<OptionsContext
|
||||
value={{
|
||||
darkMode,
|
||||
adminMode,
|
||||
noNavigationAnimation,
|
||||
noNavigation,
|
||||
noFooter,
|
||||
folderMode,
|
||||
setFolderMode,
|
||||
location,
|
||||
setLocation,
|
||||
showScroll,
|
||||
showHelp,
|
||||
toggleDarkMode: toggleDarkMode,
|
||||
toggleAdminMode: () => setAdminMode(prev => !prev),
|
||||
toggleNoNavigation: toggleNoNavigation,
|
||||
setNoFooter,
|
||||
setShowScroll,
|
||||
toggleShowHelp: () => setShowHelp(prev => !prev),
|
||||
viewportHeight,
|
||||
mainHeight,
|
||||
calculateHeight,
|
||||
setHoverCst
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -21,10 +21,10 @@ import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI'
|
|||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
import { useAuth } from './AuthContext';
|
||||
import { useConceptOptions } from './ConceptOptionsContext';
|
||||
|
||||
interface ILibraryContext {
|
||||
items: ILibraryItem[];
|
||||
|
@ -63,7 +63,7 @@ export const useLibrary = (): ILibraryContext => {
|
|||
|
||||
export const LibraryState = ({ children }: React.PropsWithChildren) => {
|
||||
const { user, loading: userLoading } = useAuth();
|
||||
const { adminMode } = useConceptOptions();
|
||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
|
||||
const [items, setItems] = useState<ILibraryItem[]>([]);
|
||||
const [templates, setTemplates] = useState<ILibraryItem[]>([]);
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
import { useEffect } from 'react';
|
||||
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useAppLayoutStore, useFitHeight } from '@/stores/appLayout';
|
||||
import { resources } from '@/utils/constants';
|
||||
|
||||
function DatabaseSchemaPage() {
|
||||
const { calculateHeight, setNoFooter } = useConceptOptions();
|
||||
const hideFooter = useAppLayoutStore(state => state.hideFooter);
|
||||
|
||||
const panelHeight = calculateHeight('0px');
|
||||
const panelHeight = useFitHeight('0px');
|
||||
|
||||
useEffect(() => {
|
||||
setNoFooter(true);
|
||||
return () => setNoFooter(false);
|
||||
}, [setNoFooter]);
|
||||
hideFooter(true);
|
||||
return () => hideFooter(false);
|
||||
}, [hideFooter]);
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in flex justify-center overflow-hidden' style={{ maxHeight: panelHeight }}>
|
||||
|
|
|
@ -16,6 +16,7 @@ import useLocalStorage from '@/hooks/useLocalStorage';
|
|||
import { ILibraryItem, IRenameLocationData, LocationHead } from '@/models/library';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { UserID } from '@/models/user';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { storage } from '@/utils/constants';
|
||||
import { information } from '@/utils/labels';
|
||||
import { convertToCSV, toggleTristateFlag } from '@/utils/utils';
|
||||
|
@ -29,6 +30,7 @@ function LibraryPage() {
|
|||
const { user } = useAuth();
|
||||
const [items, setItems] = useState<ILibraryItem[]>([]);
|
||||
const options = useConceptOptions();
|
||||
const noNavigation = useAppLayoutStore(state => state.noNavigation);
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [path, setPath] = useState('');
|
||||
|
@ -133,7 +135,7 @@ function LibraryPage() {
|
|||
/>
|
||||
) : null}
|
||||
<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'
|
||||
className='cc-animate-position'
|
||||
>
|
||||
|
|
|
@ -12,12 +12,12 @@ import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } fro
|
|||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useUsers } from '@/context/UsersContext';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { ILibraryItem, LibraryItemType } from '@/models/library';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { APP_COLORS } from '@/styling/color';
|
||||
import { storage } from '@/utils/constants';
|
||||
|
||||
|
@ -34,7 +34,6 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
|
|||
const router = useConceptNavigation();
|
||||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const [itemsPerPage, setItemsPerPage] = useLocalStorage<number>(storage.libraryPagination, 50);
|
||||
|
||||
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>[] = [
|
||||
{
|
||||
|
|
|
@ -8,11 +8,11 @@ import { CProps } from '@/components/props';
|
|||
import SelectLocation from '@/components/select/SelectLocation';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { FolderNode, FolderTree } from '@/models/FolderTree';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
|
@ -39,7 +39,6 @@ function ViewSideLocation({
|
|||
}: ViewSideLocationProps) {
|
||||
const { user } = useAuth();
|
||||
const { items } = useLibrary();
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const canRename = (() => {
|
||||
|
@ -56,7 +55,7 @@ function ViewSideLocation({
|
|||
return located.length !== 0;
|
||||
})();
|
||||
|
||||
const maxHeight = calculateHeight('4.5rem');
|
||||
const maxHeight = useFitHeight('4.5rem');
|
||||
|
||||
function handleClickFolder(event: CProps.EventMouse, target: FolderNode) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import useQueryStrings from '@/hooks/useQueryStrings';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { useMainHeight } from '@/stores/appLayout';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import TopicsList from './TopicsList';
|
||||
|
@ -17,7 +17,7 @@ function ManualsPage() {
|
|||
const query = useQueryStrings();
|
||||
const activeTopic = (query.get('topic') || HelpTopic.MAIN) as HelpTopic;
|
||||
|
||||
const { mainHeight } = useConceptOptions();
|
||||
const mainHeight = useMainHeight();
|
||||
|
||||
const onSelectTopic = useCallback(
|
||||
(newTopic: HelpTopic) => {
|
||||
|
|
|
@ -6,9 +6,9 @@ import { useCallback } from 'react';
|
|||
import { IconMenuFold, IconMenuUnfold } from '@/components/Icons';
|
||||
import Button from '@/components/ui/Button';
|
||||
import SelectTree from '@/components/ui/SelectTree';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { HelpTopic, topicParent } from '@/models/miscellaneous';
|
||||
import { useAppLayoutStore, useFitHeight } from '@/stores/appLayout';
|
||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
|
||||
|
||||
|
@ -19,7 +19,8 @@ interface TopicsDropdownProps {
|
|||
|
||||
function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
|
||||
const menu = useDropdown();
|
||||
const { noNavigation, calculateHeight } = useConceptOptions();
|
||||
const noNavigation = useAppLayoutStore(state => state.noNavigation);
|
||||
const treeHeight = useFitHeight('4rem + 2px');
|
||||
|
||||
const handleSelectTopic = useCallback(
|
||||
(topic: HelpTopic) => {
|
||||
|
@ -67,7 +68,7 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
|
|||
'bg-prim-200'
|
||||
)}
|
||||
style={{
|
||||
maxHeight: calculateHeight('4rem + 2px'),
|
||||
maxHeight: treeHeight,
|
||||
transitionProperty: 'clip-path',
|
||||
transitionDuration: `${PARAMETER.moveDuration}ms`,
|
||||
clipPath: menu.isOpen ? 'inset(0% 0% 0% 0%)' : 'inset(0% 100% 0% 0%)'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import SelectTree from '@/components/ui/SelectTree';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { HelpTopic, topicParent } from '@/models/miscellaneous';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
|
||||
|
||||
|
@ -12,7 +12,7 @@ interface TopicsStaticProps {
|
|||
}
|
||||
|
||||
function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const topicsHeight = useFitHeight('1rem + 2px');
|
||||
return (
|
||||
<SelectTree
|
||||
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',
|
||||
'select-none'
|
||||
)}
|
||||
style={{ maxHeight: calculateHeight('1rem + 2px') }}
|
||||
style={{ maxHeight: topicsHeight }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
'use client';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import TopicPage from '@/pages/ManualsPage/TopicPage';
|
||||
import { useMainHeight } from '@/stores/appLayout';
|
||||
|
||||
interface ViewTopicProps {
|
||||
topic: HelpTopic;
|
||||
}
|
||||
|
||||
function ViewTopic({ topic }: ViewTopicProps) {
|
||||
const { mainHeight } = useConceptOptions();
|
||||
const mainHeight = useMainHeight();
|
||||
return (
|
||||
<div
|
||||
key={topic}
|
||||
|
|
|
@ -18,11 +18,11 @@ import {
|
|||
|
||||
import { CProps } from '@/components/props';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { OssNode } from '@/models/miscellaneous';
|
||||
import { OperationID } from '@/models/oss';
|
||||
import { useMainHeight } from '@/stores/appLayout';
|
||||
import { APP_COLORS } from '@/styling/color';
|
||||
import { PARAMETER, storage } from '@/utils/constants';
|
||||
import { errors } from '@/utils/labels';
|
||||
|
@ -41,7 +41,7 @@ interface OssFlowProps {
|
|||
}
|
||||
|
||||
function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||
const { mainHeight } = useConceptOptions();
|
||||
const mainHeight = useMainHeight();
|
||||
const model = useOSS();
|
||||
const controller = useOssEdit();
|
||||
const flow = useReactFlow();
|
||||
|
|
|
@ -6,7 +6,6 @@ import { toast } from 'react-toastify';
|
|||
import { urls } from '@/app/urls';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
|
@ -32,6 +31,7 @@ import {
|
|||
OperationType
|
||||
} from '@/models/oss';
|
||||
import { UserID, UserLevel } from '@/models/user';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { errors, information } from '@/utils/labels';
|
||||
|
||||
|
@ -95,7 +95,7 @@ interface OssEditStateProps {
|
|||
export const OssEditState = ({ selected, setSelected, children }: React.PropsWithChildren<OssEditStateProps>) => {
|
||||
const router = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
const { adminMode } = useConceptOptions();
|
||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||
const model = useOSS();
|
||||
const library = useLibrary();
|
||||
|
|
|
@ -13,12 +13,12 @@ import Overlay from '@/components/ui/Overlay';
|
|||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import useQueryStrings from '@/hooks/useQueryStrings';
|
||||
import { OperationID } from '@/models/oss';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { information, prompts } from '@/utils/labels';
|
||||
|
||||
import EditorRSForm from './EditorOssCard';
|
||||
|
@ -37,7 +37,7 @@ function OssTabs() {
|
|||
const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH;
|
||||
const { user } = useAuth();
|
||||
|
||||
const { setNoFooter } = useConceptOptions();
|
||||
const hideFooter = useAppLayoutStore(state => state.hideFooter);
|
||||
const { schema, loading, loadingError: errorLoading } = useOSS();
|
||||
const { destroyItem } = useLibrary();
|
||||
|
||||
|
@ -61,8 +61,8 @@ function OssTabs() {
|
|||
}, [schema, schema?.title]);
|
||||
|
||||
useEffect(() => {
|
||||
setNoFooter(activeTab === OssTabID.GRAPH);
|
||||
}, [activeTab, setNoFooter]);
|
||||
hideFooter(activeTab === OssTabID.GRAPH);
|
||||
}, [activeTab, hideFooter]);
|
||||
|
||||
function navigateTab(tab: OssTabID) {
|
||||
if (!schema) {
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { ConstituentaID, IConstituenta } from '@/models/rsform';
|
||||
import { useMainHeight } from '@/stores/appLayout';
|
||||
import { globals, storage } from '@/utils/constants';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
@ -27,7 +27,7 @@ interface EditorConstituentaProps {
|
|||
function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) {
|
||||
const controller = useRSEdit();
|
||||
const windowSize = useWindowSize();
|
||||
const { mainHeight } = useConceptOptions();
|
||||
const mainHeight = useMainHeight();
|
||||
|
||||
const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
|
|
@ -9,10 +9,10 @@ import { type RowSelectionState } from '@/components/ui/DataTable';
|
|||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import SearchBar from '@/components/ui/SearchBar';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { CstMatchMode } from '@/models/miscellaneous';
|
||||
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
|
||||
import { matchConstituenta } from '@/models/rsformAPI';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { information } from '@/utils/labels';
|
||||
import { convertToCSV } from '@/utils/utils';
|
||||
|
||||
|
@ -25,7 +25,6 @@ interface EditorRSListProps {
|
|||
}
|
||||
|
||||
function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const controller = useRSEdit();
|
||||
|
||||
|
@ -136,7 +135,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const tableHeight = calculateHeight('4.05rem + 5px');
|
||||
const tableHeight = useFitHeight('4.05rem + 5px');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -24,12 +24,12 @@ import SelectedCounter from '@/components/info/SelectedCounter';
|
|||
import { CProps } from '@/components/props';
|
||||
import ToolbarGraphSelection from '@/components/select/ToolbarGraphSelection';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import DlgGraphParams from '@/dialogs/DlgGraphParams';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { GraphColoring, GraphFilterParams } from '@/models/miscellaneous';
|
||||
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
|
||||
import { isBasicConcept } from '@/models/rsformAPI';
|
||||
import { useMainHeight } from '@/stores/appLayout';
|
||||
import { APP_COLORS, colorBgGraphNode } from '@/styling/color';
|
||||
import { PARAMETER, storage } from '@/utils/constants';
|
||||
import { errors } from '@/utils/labels';
|
||||
|
@ -53,7 +53,7 @@ interface TGFlowProps {
|
|||
}
|
||||
|
||||
function TGFlow({ onOpenEdit }: TGFlowProps) {
|
||||
const { mainHeight } = useConceptOptions();
|
||||
const mainHeight = useMainHeight();
|
||||
const controller = useRSEdit();
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges] = useEdgesState([]);
|
||||
|
|
|
@ -11,6 +11,7 @@ import useLocalStorage from '@/hooks/useLocalStorage';
|
|||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { GraphColoring } from '@/models/miscellaneous';
|
||||
import { ConstituentaID, IRSForm } from '@/models/rsform';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { APP_COLORS, colorBgGraphNode } from '@/styling/color';
|
||||
import { globals, PARAMETER, prefixes, storage } from '@/utils/constants';
|
||||
|
||||
|
@ -26,11 +27,11 @@ interface ViewHiddenProps {
|
|||
}
|
||||
|
||||
function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) {
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const windowSize = useWindowSize();
|
||||
const localSelected = items.filter(id => selected.includes(id));
|
||||
const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false);
|
||||
const { setHoverCst } = useConceptOptions();
|
||||
const hiddenHeight = useFitHeight(windowSize.isSmall ? '10.4rem + 2px' : '12.5rem + 2px');
|
||||
|
||||
function handleClick(cstID: ConstituentaID, event: CProps.EventMouse) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
|
@ -77,7 +78,7 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
|
|||
'cc-scroll-y'
|
||||
)}
|
||||
style={{
|
||||
maxHeight: calculateHeight(windowSize.isSmall ? '10.4rem + 2px' : '12.5rem + 2px'),
|
||||
maxHeight: hiddenHeight,
|
||||
transitionProperty: 'clip-path',
|
||||
transitionDuration: `${PARAMETER.fastAnimation}ms`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
|
|
|
@ -7,7 +7,6 @@ import { toast } from 'react-toastify';
|
|||
import { urls } from '@/app/urls';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
||||
|
@ -51,6 +50,7 @@ import {
|
|||
} from '@/models/rsform';
|
||||
import { generateAlias } from '@/models/rsformAPI';
|
||||
import { UserID, UserLevel } from '@/models/user';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
||||
import { information, prompts } from '@/utils/labels';
|
||||
import { promptUnsaved } from '@/utils/utils';
|
||||
|
@ -142,7 +142,7 @@ export const RSEditState = ({
|
|||
}: React.PropsWithChildren<RSEditStateProps>) => {
|
||||
const router = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
const { adminMode } = useConceptOptions();
|
||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||
const model = useRSForm();
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ import Loader from '@/components/ui/Loader';
|
|||
import Overlay from '@/components/ui/Overlay';
|
||||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useGlobalOss } from '@/context/GlobalOssContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import useQueryStrings from '@/hooks/useQueryStrings';
|
||||
import { ConstituentaID, IConstituenta, IConstituentaMeta } from '@/models/rsform';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { information, labelVersion, prompts } from '@/utils/labels';
|
||||
|
||||
|
@ -45,7 +45,7 @@ function RSTabs() {
|
|||
const version = query.get('v') ? Number(query.get('v')) : undefined;
|
||||
const cstQuery = query.get('active');
|
||||
|
||||
const { setNoFooter } = useConceptOptions();
|
||||
const hideFooter = useAppLayoutStore(state => state.hideFooter);
|
||||
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
||||
const library = useLibrary();
|
||||
const oss = useGlobalOss();
|
||||
|
@ -73,7 +73,7 @@ function RSTabs() {
|
|||
}, [schema, schema?.title]);
|
||||
|
||||
useEffect(() => {
|
||||
setNoFooter(activeTab !== RSTabID.CARD);
|
||||
hideFooter(activeTab !== RSTabID.CARD);
|
||||
setIsModified(false);
|
||||
if (activeTab === RSTabID.CST_EDIT) {
|
||||
const cstID = Number(cstQuery);
|
||||
|
@ -83,8 +83,8 @@ function RSTabs() {
|
|||
setSelected([]);
|
||||
}
|
||||
}
|
||||
return () => setNoFooter(false);
|
||||
}, [activeTab, cstQuery, setSelected, schema, setNoFooter, setIsModified]);
|
||||
return () => hideFooter(false);
|
||||
}, [activeTab, cstQuery, setSelected, schema, hideFooter, setIsModified]);
|
||||
|
||||
function navigateTab(tab: RSTabID, activeID?: ConstituentaID) {
|
||||
if (!schema) {
|
||||
|
|
|
@ -4,10 +4,10 @@ import clsx from 'clsx';
|
|||
import { useState } from 'react';
|
||||
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
||||
import { UserLevel } from '@/models/user';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import ConstituentsSearch from './ConstituentsSearch';
|
||||
|
@ -26,9 +26,9 @@ interface ViewConstituentsProps {
|
|||
}
|
||||
|
||||
function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit, isMounted }: ViewConstituentsProps) {
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const windowSize = useWindowSize();
|
||||
const { accessLevel } = useAccessMode();
|
||||
const listHeight = useFitHeight(!isBottom ? '8.2rem' : accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem');
|
||||
|
||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||
|
||||
|
@ -57,11 +57,7 @@ function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit,
|
|||
setFiltered={setFilteredData}
|
||||
/>
|
||||
<TableSideConstituents
|
||||
maxHeight={
|
||||
isBottom
|
||||
? calculateHeight(accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem')
|
||||
: calculateHeight('8.2rem')
|
||||
}
|
||||
maxHeight={listHeight}
|
||||
items={filteredData}
|
||||
activeCst={activeCst}
|
||||
onOpenEdit={onOpenEdit}
|
||||
|
|
68
rsconcept/frontend/src/stores/appLayout.ts
Normal file
68
rsconcept/frontend/src/stores/appLayout.ts
Normal 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})`;
|
||||
}
|
||||
}
|
24
rsconcept/frontend/src/stores/preferences.ts
Normal file
24
rsconcept/frontend/src/stores/preferences.ts
Normal 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'
|
||||
}
|
||||
)
|
||||
);
|
|
@ -109,8 +109,6 @@ export const storage = {
|
|||
PREFIX: 'portal.',
|
||||
|
||||
themeDark: 'theme.dark',
|
||||
optionsAdmin: 'options.admin',
|
||||
optionsHelp: 'options.help',
|
||||
|
||||
rseditShowList: 'rsedit.show_list',
|
||||
rseditShowControls: 'rsedit.show_controls',
|
||||
|
|
Loading…
Reference in New Issue
Block a user