R: Migrating to zustand for local state management pt2
This commit is contained in:
parent
26bd0ce16b
commit
08fd26e687
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -119,6 +119,7 @@
|
|||
"NUMR",
|
||||
"Opencorpora",
|
||||
"overscroll",
|
||||
"partialize",
|
||||
"passwordreset",
|
||||
"perfectivity",
|
||||
"PNCT",
|
||||
|
|
|
@ -20,22 +20,6 @@
|
|||
/>
|
||||
|
||||
<title>Концепт Портал</title>
|
||||
|
||||
<!-- <script src="https://unpkg.com/react-scan/dist/auto.global.js"></script> -->
|
||||
<script>
|
||||
let isDark = false;
|
||||
if ('portal.theme.dark' in localStorage) {
|
||||
isDark = localStorage.getItem('portal.theme.dark') === 'true';
|
||||
} else if (window.matchMedia) {
|
||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
localStorage.setItem('portal.theme.dark', isDark ? 'true' : 'false');
|
||||
}
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ToastContainer, type ToastContainerProps } from 'react-toastify';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
interface ToasterThemedProps extends Omit<ToastContainerProps, 'theme'> {}
|
||||
|
||||
function ToasterThemed(props: ToasterThemedProps) {
|
||||
const { darkMode } = useConceptOptions();
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
|
||||
return <ToastContainer theme={darkMode ? 'dark' : 'light'} {...props} />;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
function Logo() {
|
||||
const { darkMode } = useConceptOptions();
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
const size = useWindowSize();
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { IconDarkTheme, IconLightTheme, IconPin, IconUnpin } from '@/components/Icons';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { globals, PARAMETER } from '@/utils/constants';
|
||||
|
||||
function ToggleNavigation() {
|
||||
const { toggleDarkMode, darkMode } = useConceptOptions();
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
const toggleDarkMode = usePreferencesStore(state => state.toggleDarkMode);
|
||||
const noNavigation = useAppLayoutStore(state => state.noNavigation);
|
||||
const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
|
||||
const toggleNoNavigation = useAppLayoutStore(state => state.toggleNoNavigation);
|
||||
|
|
|
@ -16,7 +16,6 @@ import { CProps } from '@/components/props';
|
|||
import Dropdown from '@/components/ui/Dropdown';
|
||||
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';
|
||||
|
||||
|
@ -28,10 +27,11 @@ interface UserDropdownProps {
|
|||
}
|
||||
|
||||
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
|
||||
const { darkMode, toggleDarkMode } = useConceptOptions();
|
||||
const router = useConceptNavigation();
|
||||
const { user, logout } = useAuth();
|
||||
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
const toggleDarkMode = usePreferencesStore(state => state.toggleDarkMode);
|
||||
const showHelp = usePreferencesStore(state => state.showHelp);
|
||||
const toggleShowHelp = usePreferencesStore(state => state.toggleShowHelp);
|
||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
|
|
|
@ -9,10 +9,10 @@ import { EditorView } from 'codemirror';
|
|||
import { forwardRef, useRef } from 'react';
|
||||
|
||||
import Label from '@/components/ui/Label';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { ConstituentaID, IRSForm } from '@/models/rsform';
|
||||
import { generateAlias, getCstTypePrefix, guessCstType } from '@/models/rsformAPI';
|
||||
import { extractGlobals } from '@/models/rslangAPI';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { APP_COLORS } from '@/styling/color';
|
||||
|
||||
import { ccBracketMatching } from './bracketMatching';
|
||||
|
@ -64,7 +64,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
|||
},
|
||||
ref
|
||||
) => {
|
||||
const { darkMode } = useConceptOptions();
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
|
||||
|
|
|
@ -9,10 +9,10 @@ import { EditorView } from 'codemirror';
|
|||
import { forwardRef, useRef, useState } from 'react';
|
||||
|
||||
import Label from '@/components/ui/Label';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import DlgEditReference from '@/dialogs/DlgEditReference';
|
||||
import { ReferenceType } from '@/models/language';
|
||||
import { ConstituentaID, IRSForm } from '@/models/rsform';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { APP_COLORS } from '@/styling/color';
|
||||
import { CodeMirrorWrapper } from '@/utils/codemirror';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
@ -92,7 +92,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
},
|
||||
ref
|
||||
) => {
|
||||
const { darkMode } = useConceptOptions();
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ReactNode } from 'react';
|
|||
import { createPortal } from 'react-dom';
|
||||
import { ITooltip, Tooltip as TooltipImpl } from 'react-tooltip';
|
||||
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
export type { PlacesType } from 'react-tooltip';
|
||||
|
||||
|
@ -29,7 +29,7 @@ function Tooltip({
|
|||
style,
|
||||
...restProps
|
||||
}: TooltipProps) {
|
||||
const { darkMode } = useConceptOptions();
|
||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,26 +1,15 @@
|
|||
'use client';
|
||||
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
|
||||
import InfoConstituenta from '@/components/info/InfoConstituenta';
|
||||
import Loader from '@/components/ui/Loader';
|
||||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { globals, PARAMETER, storage } from '@/utils/constants';
|
||||
import { globals } from '@/utils/constants';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
interface IOptionsContext {
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
|
||||
folderMode: boolean;
|
||||
setFolderMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
location: string;
|
||||
setLocation: React.Dispatch<React.SetStateAction<string>>;
|
||||
|
||||
setHoverCst: (newValue: IConstituenta | undefined) => void;
|
||||
}
|
||||
|
||||
|
@ -34,59 +23,11 @@ export const useConceptOptions = () => {
|
|||
};
|
||||
|
||||
export const OptionsState = ({ children }: React.PropsWithChildren) => {
|
||||
const [darkMode, setDarkMode] = useLocalStorage(storage.themeDark, false);
|
||||
|
||||
const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true);
|
||||
const [location, setLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
|
||||
|
||||
const [hoverCst, setHoverCst] = useState<IConstituenta | undefined>(undefined);
|
||||
|
||||
function setDarkClass(isDark: boolean) {
|
||||
const root = window.document.documentElement;
|
||||
if (isDark) {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDarkClass(darkMode);
|
||||
}, [darkMode]);
|
||||
|
||||
const toggleDarkMode = useCallback(() => {
|
||||
if (!document.startViewTransition) {
|
||||
setDarkMode(prev => !prev);
|
||||
} else {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
* {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.startViewTransition(() => {
|
||||
flushSync(() => {
|
||||
setDarkMode(prev => !prev);
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => document.head.removeChild(style), PARAMETER.moveDuration);
|
||||
}
|
||||
}, [setDarkMode]);
|
||||
|
||||
return (
|
||||
<OptionsContext
|
||||
value={{
|
||||
darkMode,
|
||||
folderMode,
|
||||
setFolderMode,
|
||||
location,
|
||||
setLocation,
|
||||
toggleDarkMode: toggleDarkMode,
|
||||
setHoverCst
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -20,21 +20,23 @@ import SubmitButton from '@/components/ui/SubmitButton';
|
|||
import TextArea from '@/components/ui/TextArea';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
||||
import { ILibraryCreateData } from '@/models/library';
|
||||
import { combineLocation, validateLocation } from '@/models/libraryAPI';
|
||||
import { useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
function FormCreateItem() {
|
||||
const router = useConceptNavigation();
|
||||
const options = useConceptOptions();
|
||||
const { user } = useAuth();
|
||||
const { createItem, processingError, setProcessingError, processing, folders } = useLibrary();
|
||||
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
|
||||
const [itemType, setItemType] = useState(LibraryItemType.RSFORM);
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
|
@ -81,7 +83,7 @@ function FormCreateItem() {
|
|||
file: file,
|
||||
fileName: file?.name
|
||||
};
|
||||
options.setLocation(location);
|
||||
setSearchLocation(location);
|
||||
createItem(data, newItem => {
|
||||
toast.success(information.newLibraryItem);
|
||||
if (itemType == LibraryItemType.RSFORM) {
|
||||
|
@ -108,11 +110,11 @@ function FormCreateItem() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!options.location) {
|
||||
if (!searchLocation) {
|
||||
return;
|
||||
}
|
||||
handleSelectLocation(options.location);
|
||||
}, [options.location, handleSelectLocation]);
|
||||
handleSelectLocation(searchLocation);
|
||||
}, [searchLocation, handleSelectLocation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (itemType !== LibraryItemType.RSFORM) {
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
'use client';
|
||||
|
||||
import fileDownload from 'js-file-download';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { IconCSV } from '@/components/Icons';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import DataLoader from '@/components/wrap/DataLoader';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { ILibraryItem, IRenameLocationData, LocationHead } from '@/models/library';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { UserID } from '@/models/user';
|
||||
import { IRenameLocationData } from '@/models/library';
|
||||
import { useAppLayoutStore } from '@/stores/appLayout';
|
||||
import { storage } from '@/utils/constants';
|
||||
import { useLibraryFilter, useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { information } from '@/utils/labels';
|
||||
import { convertToCSV, toggleTristateFlag } from '@/utils/utils';
|
||||
import { convertToCSV } from '@/utils/utils';
|
||||
|
||||
import TableLibraryItems from './TableLibraryItems';
|
||||
import ToolbarSearch from './ToolbarSearch';
|
||||
|
@ -27,89 +22,29 @@ import ViewSideLocation from './ViewSideLocation';
|
|||
|
||||
function LibraryPage() {
|
||||
const library = useLibrary();
|
||||
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('');
|
||||
const folderMode = useLibrarySearchStore(state => state.folderMode);
|
||||
const location = useLibrarySearchStore(state => state.location);
|
||||
const setLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
|
||||
const filter = useLibraryFilter();
|
||||
const items = library.applyFilter(filter);
|
||||
|
||||
const [head, setHead] = useLocalStorage<LocationHead | undefined>(storage.librarySearchHead, undefined);
|
||||
const [subfolders, setSubfolders] = useLocalStorage<boolean>(storage.librarySearchSubfolders, false);
|
||||
const [isVisible, setIsVisible] = useLocalStorage<boolean | undefined>(storage.librarySearchVisible, true);
|
||||
const [isOwned, setIsOwned] = useLocalStorage<boolean | undefined>(storage.librarySearchOwned, undefined);
|
||||
const [isEditor, setIsEditor] = useLocalStorage<boolean | undefined>(storage.librarySearchEditor, undefined);
|
||||
const [filterUser, setFilterUser] = useLocalStorage<UserID | undefined>(storage.librarySearchUser, undefined);
|
||||
const [showRenameLocation, setShowRenameLocation] = useState(false);
|
||||
|
||||
const filter: ILibraryFilter = useMemo(
|
||||
() => ({
|
||||
head: head,
|
||||
path: path,
|
||||
query: query,
|
||||
isEditor: user ? isEditor : undefined,
|
||||
isOwned: user ? isOwned : undefined,
|
||||
isVisible: user ? isVisible : true,
|
||||
folderMode: options.folderMode,
|
||||
subfolders: subfolders,
|
||||
location: options.location,
|
||||
filterUser: filterUser
|
||||
}),
|
||||
[
|
||||
head,
|
||||
path,
|
||||
query,
|
||||
isEditor,
|
||||
isOwned,
|
||||
isVisible,
|
||||
user,
|
||||
options.folderMode,
|
||||
options.location,
|
||||
subfolders,
|
||||
filterUser
|
||||
]
|
||||
);
|
||||
|
||||
const hasCustomFilter =
|
||||
!!filter.path ||
|
||||
!!filter.query ||
|
||||
filter.head !== undefined ||
|
||||
filter.isEditor !== undefined ||
|
||||
filter.isOwned !== undefined ||
|
||||
filter.isVisible !== true ||
|
||||
filter.filterUser !== undefined ||
|
||||
!!filter.location;
|
||||
|
||||
useEffect(() => {
|
||||
setItems(library.applyFilter(filter));
|
||||
}, [library, library.items.length, filter]);
|
||||
|
||||
const toggleFolderMode = () => options.setFolderMode(prev => !prev);
|
||||
|
||||
const resetFilter = useCallback(() => {
|
||||
setQuery('');
|
||||
setPath('');
|
||||
setHead(undefined);
|
||||
setIsVisible(true);
|
||||
setIsOwned(undefined);
|
||||
setIsEditor(undefined);
|
||||
setFilterUser(undefined);
|
||||
options.setLocation('');
|
||||
}, [setHead, setIsVisible, setIsOwned, setIsEditor, setFilterUser, options]);
|
||||
|
||||
const handleRenameLocation = useCallback(
|
||||
(newLocation: string) => {
|
||||
const data: IRenameLocationData = {
|
||||
target: options.location,
|
||||
target: location,
|
||||
new_location: newLocation
|
||||
};
|
||||
library.renameLocation(data, () => {
|
||||
options.setLocation(newLocation);
|
||||
setLocation(newLocation);
|
||||
toast.success(information.locationRenamed);
|
||||
});
|
||||
},
|
||||
[options, library]
|
||||
[location, setLocation, library]
|
||||
);
|
||||
|
||||
const handleDownloadCSV = useCallback(() => {
|
||||
|
@ -129,7 +64,7 @@ function LibraryPage() {
|
|||
<DataLoader isLoading={library.loading} error={library.loadingError} hasNoData={library.items.length === 0}>
|
||||
{showRenameLocation ? (
|
||||
<DlgChangeLocation
|
||||
initial={options.location}
|
||||
initial={location}
|
||||
onChangeLocation={handleRenameLocation}
|
||||
hideWindow={() => setShowRenameLocation(false)}
|
||||
/>
|
||||
|
@ -145,47 +80,16 @@ function LibraryPage() {
|
|||
onClick={handleDownloadCSV}
|
||||
/>
|
||||
</Overlay>
|
||||
<ToolbarSearch
|
||||
total={library.items.length ?? 0}
|
||||
filtered={items.length}
|
||||
hasCustomFilter={hasCustomFilter}
|
||||
query={query}
|
||||
onChangeQuery={setQuery}
|
||||
path={path}
|
||||
onChangePath={setPath}
|
||||
head={head}
|
||||
onChangeHead={setHead}
|
||||
isVisible={isVisible}
|
||||
isOwned={isOwned}
|
||||
toggleOwned={() => setIsOwned(prev => toggleTristateFlag(prev))}
|
||||
toggleVisible={() => setIsVisible(prev => toggleTristateFlag(prev))}
|
||||
isEditor={isEditor}
|
||||
toggleEditor={() => setIsEditor(prev => toggleTristateFlag(prev))}
|
||||
filterUser={filterUser}
|
||||
onChangeFilterUser={setFilterUser}
|
||||
resetFilter={resetFilter}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
/>
|
||||
<ToolbarSearch total={library.items.length ?? 0} filtered={items.length} />
|
||||
|
||||
<div className='cc-fade-in flex'>
|
||||
<ViewSideLocation
|
||||
isVisible={options.folderMode}
|
||||
activeLocation={options.location}
|
||||
onChangeActiveLocation={options.setLocation}
|
||||
subfolders={subfolders}
|
||||
isVisible={folderMode}
|
||||
folderTree={library.folders}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
toggleSubfolders={() => setSubfolders(prev => !prev)}
|
||||
onRenameLocation={() => setShowRenameLocation(true)}
|
||||
/>
|
||||
|
||||
<TableLibraryItems
|
||||
resetQuery={resetFilter}
|
||||
items={items}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
/>
|
||||
<TableLibraryItems items={items} />
|
||||
</div>
|
||||
</DataLoader>
|
||||
);
|
||||
|
|
|
@ -14,27 +14,30 @@ import MiniButton from '@/components/ui/MiniButton';
|
|||
import TextURL from '@/components/ui/TextURL';
|
||||
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 { useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { APP_COLORS } from '@/styling/color';
|
||||
import { storage } from '@/utils/constants';
|
||||
|
||||
interface TableLibraryItemsProps {
|
||||
items: ILibraryItem[];
|
||||
resetQuery: () => void;
|
||||
folderMode: boolean;
|
||||
toggleFolderMode: () => void;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||
|
||||
function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }: TableLibraryItemsProps) {
|
||||
function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
||||
const router = useConceptNavigation();
|
||||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
const [itemsPerPage, setItemsPerPage] = useLocalStorage<number>(storage.libraryPagination, 50);
|
||||
|
||||
const folderMode = useLibrarySearchStore(state => state.folderMode);
|
||||
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
|
||||
const resetFilter = useLibrarySearchStore(state => state.resetFilter);
|
||||
|
||||
const itemsPerPage = usePreferencesStore(state => state.libraryPagination);
|
||||
const setItemsPerPage = usePreferencesStore(state => state.setLibraryPagination);
|
||||
|
||||
function handleOpenItem(item: ILibraryItem, event: CProps.EventMouse) {
|
||||
const selection = window.getSelection();
|
||||
|
@ -163,7 +166,7 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
|
|||
<p>Список схем пуст</p>
|
||||
<p className='flex gap-6'>
|
||||
<TextURL text='Создать схему' href='/library/create' />
|
||||
<TextURL text='Очистить фильтр' onClick={resetQuery} />
|
||||
<TextURL text='Очистить фильтр' onClick={resetFilter} />
|
||||
</p>
|
||||
</FlexColumn>
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import SelectorButton from '@/components/ui/SelectorButton';
|
|||
import { useUsers } from '@/context/UsersContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { LocationHead } from '@/models/library';
|
||||
import { UserID } from '@/models/user';
|
||||
import { useHasCustomFilter, useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { describeLocationHead, labelLocationHead } from '@/utils/labels';
|
||||
import { tripleToggleColor } from '@/utils/utils';
|
||||
|
@ -30,65 +30,38 @@ import { tripleToggleColor } from '@/utils/utils';
|
|||
interface ToolbarSearchProps {
|
||||
total: number;
|
||||
filtered: number;
|
||||
hasCustomFilter: boolean;
|
||||
|
||||
query: string;
|
||||
onChangeQuery: (newValue: string) => void;
|
||||
path: string;
|
||||
onChangePath: (newValue: string) => void;
|
||||
head: LocationHead | undefined;
|
||||
onChangeHead: (newValue: LocationHead | undefined) => void;
|
||||
|
||||
folderMode: boolean;
|
||||
toggleFolderMode: () => void;
|
||||
|
||||
isVisible: boolean | undefined;
|
||||
toggleVisible: () => void;
|
||||
isOwned: boolean | undefined;
|
||||
toggleOwned: () => void;
|
||||
isEditor: boolean | undefined;
|
||||
toggleEditor: () => void;
|
||||
filterUser: UserID | undefined;
|
||||
onChangeFilterUser: (newValue: UserID | undefined) => void;
|
||||
|
||||
resetFilter: () => void;
|
||||
}
|
||||
|
||||
function ToolbarSearch({
|
||||
total,
|
||||
filtered,
|
||||
hasCustomFilter,
|
||||
|
||||
query,
|
||||
onChangeQuery,
|
||||
path,
|
||||
onChangePath,
|
||||
head,
|
||||
onChangeHead,
|
||||
|
||||
folderMode,
|
||||
toggleFolderMode,
|
||||
|
||||
isVisible,
|
||||
toggleVisible,
|
||||
isOwned,
|
||||
toggleOwned,
|
||||
isEditor,
|
||||
toggleEditor,
|
||||
filterUser,
|
||||
onChangeFilterUser,
|
||||
|
||||
resetFilter
|
||||
}: ToolbarSearchProps) {
|
||||
function ToolbarSearch({ total, filtered }: ToolbarSearchProps) {
|
||||
const headMenu = useDropdown();
|
||||
const userMenu = useDropdown();
|
||||
const { users } = useUsers();
|
||||
|
||||
const query = useLibrarySearchStore(state => state.query);
|
||||
const setQuery = useLibrarySearchStore(state => state.setQuery);
|
||||
const path = useLibrarySearchStore(state => state.path);
|
||||
const setPath = useLibrarySearchStore(state => state.setPath);
|
||||
const head = useLibrarySearchStore(state => state.head);
|
||||
const setHead = useLibrarySearchStore(state => state.setHead);
|
||||
const folderMode = useLibrarySearchStore(state => state.folderMode);
|
||||
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
|
||||
const isOwned = useLibrarySearchStore(state => state.isOwned);
|
||||
const toggleOwned = useLibrarySearchStore(state => state.toggleOwned);
|
||||
const isEditor = useLibrarySearchStore(state => state.isEditor);
|
||||
const toggleEditor = useLibrarySearchStore(state => state.toggleEditor);
|
||||
const isVisible = useLibrarySearchStore(state => state.isVisible);
|
||||
const toggleVisible = useLibrarySearchStore(state => state.toggleVisible);
|
||||
const filterUser = useLibrarySearchStore(state => state.filterUser);
|
||||
const setFilterUser = useLibrarySearchStore(state => state.setFilterUser);
|
||||
|
||||
const resetFilter = useLibrarySearchStore(state => state.resetFilter);
|
||||
const hasCustomFilter = useHasCustomFilter();
|
||||
|
||||
const userActive = isOwned !== undefined || isEditor !== undefined || filterUser !== undefined;
|
||||
|
||||
function handleChange(newValue: LocationHead | undefined) {
|
||||
headMenu.hide();
|
||||
onChangeHead(newValue);
|
||||
setHead(newValue);
|
||||
}
|
||||
|
||||
function handleToggleFolder() {
|
||||
|
@ -157,7 +130,7 @@ function ToolbarSearch({
|
|||
className='min-w-[15rem] text-sm mx-1 mb-1'
|
||||
items={users}
|
||||
value={filterUser}
|
||||
onSelectValue={onChangeFilterUser}
|
||||
onSelectValue={setFilterUser}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
@ -177,7 +150,7 @@ function ToolbarSearch({
|
|||
noBorder
|
||||
className={clsx('min-w-[7rem] sm:min-w-[10rem] max-w-[20rem]', folderMode && 'flex-grow')}
|
||||
query={query}
|
||||
onChangeQuery={onChangeQuery}
|
||||
onChangeQuery={setQuery}
|
||||
/>
|
||||
{!folderMode ? (
|
||||
<div ref={headMenu.ref} className='flex items-center h-full py-1 select-none'>
|
||||
|
@ -236,7 +209,7 @@ function ToolbarSearch({
|
|||
noBorder
|
||||
className='w-[4.5rem] sm:w-[5rem] flex-grow'
|
||||
query={path}
|
||||
onChangeQuery={onChangePath}
|
||||
onChangeQuery={setPath}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -13,45 +13,36 @@ import useWindowSize from '@/hooks/useWindowSize';
|
|||
import { FolderNode, FolderTree } from '@/models/FolderTree';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||
import { information } from '@/utils/labels';
|
||||
|
||||
interface ViewSideLocationProps {
|
||||
folderTree: FolderTree;
|
||||
isVisible: boolean;
|
||||
subfolders: boolean;
|
||||
activeLocation: string;
|
||||
onChangeActiveLocation: (newValue: string) => void;
|
||||
toggleFolderMode: () => void;
|
||||
toggleSubfolders: () => void;
|
||||
onRenameLocation: () => void;
|
||||
}
|
||||
|
||||
function ViewSideLocation({
|
||||
folderTree,
|
||||
activeLocation,
|
||||
subfolders,
|
||||
isVisible,
|
||||
onChangeActiveLocation,
|
||||
toggleFolderMode,
|
||||
toggleSubfolders,
|
||||
onRenameLocation
|
||||
}: ViewSideLocationProps) {
|
||||
function ViewSideLocation({ folderTree, isVisible, onRenameLocation }: ViewSideLocationProps) {
|
||||
const { user } = useAuth();
|
||||
const { items } = useLibrary();
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const location = useLibrarySearchStore(state => state.location);
|
||||
const setLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
|
||||
const subfolders = useLibrarySearchStore(state => state.subfolders);
|
||||
const toggleSubfolders = useLibrarySearchStore(state => state.toggleSubfolders);
|
||||
|
||||
const canRename = (() => {
|
||||
if (activeLocation.length <= 3 || !user) {
|
||||
if (location.length <= 3 || !user) {
|
||||
return false;
|
||||
}
|
||||
if (user.is_staff) {
|
||||
return true;
|
||||
}
|
||||
const owned = items.filter(item => item.owner == user.id);
|
||||
const located = owned.filter(
|
||||
item => item.location == activeLocation || item.location.startsWith(`${activeLocation}/`)
|
||||
);
|
||||
const located = owned.filter(item => item.location == location || item.location.startsWith(`${location}/`));
|
||||
return located.length !== 0;
|
||||
})();
|
||||
|
||||
|
@ -66,7 +57,7 @@ function ViewSideLocation({
|
|||
.then(() => toast.success(information.pathReady))
|
||||
.catch(console.error);
|
||||
} else {
|
||||
onChangeActiveLocation(target.getPath());
|
||||
setLocation(target.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +88,7 @@ function ViewSideLocation({
|
|||
onClick={onRenameLocation}
|
||||
/>
|
||||
) : null}
|
||||
{!!activeLocation ? (
|
||||
{!!location ? (
|
||||
<MiniButton
|
||||
title={subfolders ? 'Вложенные папки: Вкл' : 'Вложенные папки: Выкл'} // prettier: split-lines
|
||||
icon={<SubfoldersIcon value={subfolders} />}
|
||||
|
@ -112,7 +103,7 @@ function ViewSideLocation({
|
|||
</div>
|
||||
</div>
|
||||
<SelectLocation
|
||||
value={activeLocation}
|
||||
value={location}
|
||||
folderTree={folderTree}
|
||||
prefix={prefixes.folders_list}
|
||||
onClick={handleClickFolder}
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
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 { usePreferencesStore } from '@/stores/preferences';
|
||||
import { globals } from '@/utils/constants';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
import ViewConstituents from '../ViewConstituents';
|
||||
|
@ -29,7 +29,8 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
|
|||
const windowSize = useWindowSize();
|
||||
const mainHeight = useMainHeight();
|
||||
|
||||
const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true);
|
||||
const showList = usePreferencesStore(state => state.showCstSideList);
|
||||
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
|
||||
const disabled = !activeCst || !controller.isContentEditable || controller.isProcessing;
|
||||
|
@ -78,10 +79,8 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
|
|||
activeCst={activeCst}
|
||||
disabled={disabled}
|
||||
modified={isModified}
|
||||
showList={showList}
|
||||
onSubmit={initiateSubmit}
|
||||
onReset={() => setToggleReset(prev => !prev)}
|
||||
onToggleList={() => setShowList(prev => !prev)}
|
||||
/>
|
||||
<div
|
||||
tabIndex={-1}
|
||||
|
|
|
@ -20,6 +20,7 @@ import MiniButton from '@/components/ui/MiniButton';
|
|||
import Overlay from '@/components/ui/Overlay';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { prepareTooltip, tooltips } from '@/utils/labels';
|
||||
|
||||
|
@ -29,25 +30,24 @@ interface ToolbarConstituentaProps {
|
|||
activeCst?: IConstituenta;
|
||||
disabled: boolean;
|
||||
modified: boolean;
|
||||
showList: boolean;
|
||||
|
||||
onSubmit: () => void;
|
||||
onReset: () => void;
|
||||
onToggleList: () => void;
|
||||
}
|
||||
|
||||
function ToolbarConstituenta({
|
||||
activeCst,
|
||||
disabled,
|
||||
modified,
|
||||
showList,
|
||||
|
||||
onSubmit,
|
||||
onReset,
|
||||
onToggleList
|
||||
onReset
|
||||
}: ToolbarConstituentaProps) {
|
||||
const controller = useRSEdit();
|
||||
|
||||
const showList = usePreferencesStore(state => state.showCstSideList);
|
||||
const toggleList = usePreferencesStore(state => state.toggleShowCstSideList);
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
position='cc-tab-tools right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
|
||||
|
@ -104,7 +104,7 @@ function ToolbarConstituenta({
|
|||
<MiniButton
|
||||
title='Отображение списка конституент'
|
||||
icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />}
|
||||
onClick={onToggleList}
|
||||
onClick={toggleList}
|
||||
/>
|
||||
|
||||
{controller.isContentEditable ? (
|
||||
|
|
|
@ -13,14 +13,13 @@ import Overlay from '@/components/ui/Overlay';
|
|||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import DlgShowAST from '@/dialogs/DlgShowAST';
|
||||
import useCheckConstituenta from '@/hooks/useCheckConstituenta';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { ConstituentaID, IConstituenta } from '@/models/rsform';
|
||||
import { getDefinitionPrefix } from '@/models/rsformAPI';
|
||||
import { IExpressionParse, IRSErrorDescription, SyntaxTree } from '@/models/rslang';
|
||||
import { TokenID } from '@/models/rslang';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { transformAST } from '@/utils/codemirror';
|
||||
import { storage } from '@/utils/constants';
|
||||
import { errors, labelTypification } from '@/utils/labels';
|
||||
|
||||
import ParsingResult from './ParsingResult';
|
||||
|
@ -64,10 +63,10 @@ function EditorRSExpression({
|
|||
const { resetParse } = parser;
|
||||
const rsInput = useRef<ReactCodeMirrorRef>(null);
|
||||
|
||||
const showControls = usePreferencesStore(state => state.showExpressionControls);
|
||||
const [syntaxTree, setSyntaxTree] = useState<SyntaxTree>([]);
|
||||
const [expression, setExpression] = useState('');
|
||||
const [showAST, setShowAST] = useState(false);
|
||||
const [showControls, setShowControls] = useLocalStorage(storage.rseditShowControls, true);
|
||||
|
||||
useEffect(() => {
|
||||
setIsModified(false);
|
||||
|
@ -154,13 +153,7 @@ function EditorRSExpression({
|
|||
<DlgShowAST expression={expression} syntaxTree={syntaxTree} hideWindow={() => setShowAST(false)} />
|
||||
) : null}
|
||||
|
||||
<ToolbarRSExpression
|
||||
disabled={disabled}
|
||||
showControls={showControls}
|
||||
showAST={handleShowAST}
|
||||
toggleControls={() => setShowControls(prev => !prev)}
|
||||
showTypeGraph={onShowTypeGraph}
|
||||
/>
|
||||
<ToolbarRSExpression disabled={disabled} showAST={handleShowAST} showTypeGraph={onShowTypeGraph} />
|
||||
|
||||
<Overlay
|
||||
position='top-[-0.5rem] right-1/2 translate-x-1/2'
|
||||
|
|
|
@ -3,24 +3,18 @@ import { CProps } from '@/components/props';
|
|||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
||||
interface ToolbarRSExpressionProps {
|
||||
disabled?: boolean;
|
||||
showControls: boolean;
|
||||
|
||||
toggleControls: () => void;
|
||||
showAST: (event: CProps.EventMouse) => void;
|
||||
showTypeGraph: (event: CProps.EventMouse) => void;
|
||||
}
|
||||
|
||||
function ToolbarRSExpression({
|
||||
disabled,
|
||||
showControls,
|
||||
showTypeGraph,
|
||||
toggleControls,
|
||||
showAST
|
||||
}: ToolbarRSExpressionProps) {
|
||||
function ToolbarRSExpression({ disabled, showTypeGraph, showAST }: ToolbarRSExpressionProps) {
|
||||
const model = useRSForm();
|
||||
const showControls = usePreferencesStore(state => state.showExpressionControls);
|
||||
const toggleControls = usePreferencesStore(state => state.toggleShowExpressionControls);
|
||||
|
||||
return (
|
||||
<Overlay position='top-[-0.5rem] right-0' layer='z-pop' className='cc-icons'>
|
||||
|
|
|
@ -18,12 +18,12 @@ import Overlay from '@/components/ui/Overlay';
|
|||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import ValueIcon from '@/components/ui/ValueIcon';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useUsers } from '@/context/UsersContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { ILibraryItemData, ILibraryItemEditor } from '@/models/library';
|
||||
import { UserID, UserLevel } from '@/models/user';
|
||||
import { useLibrarySearchStore } from '@/stores/librarySearch';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { prompts } from '@/utils/labels';
|
||||
|
||||
|
@ -38,7 +38,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
|||
const { accessLevel } = useAccessMode();
|
||||
const intl = useIntl();
|
||||
const router = useConceptNavigation();
|
||||
const { setLocation, setFolderMode } = useConceptOptions();
|
||||
const setLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
|
||||
const ownerSelector = useDropdown();
|
||||
const onSelectUser = useCallback(
|
||||
|
@ -61,10 +61,9 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
|||
return;
|
||||
}
|
||||
setLocation(item.location);
|
||||
setFolderMode(true);
|
||||
router.push(urls.library, event.ctrlKey || event.metaKey);
|
||||
},
|
||||
[setLocation, setFolderMode, item, router]
|
||||
[setLocation, item, router]
|
||||
);
|
||||
|
||||
if (!item) {
|
||||
|
|
|
@ -25,13 +25,12 @@ import { CProps } from '@/components/props';
|
|||
import ToolbarGraphSelection from '@/components/select/ToolbarGraphSelection';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
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 { useTermGraphStore } from '@/stores/termGraph';
|
||||
import { APP_COLORS, colorBgGraphNode } from '@/styling/color';
|
||||
import { PARAMETER, storage } from '@/utils/constants';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { errors } from '@/utils/labels';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
@ -62,29 +61,14 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
const { addSelectedNodes } = store.getState();
|
||||
|
||||
const [showParamsDialog, setShowParamsDialog] = useState(false);
|
||||
const [filterParams, setFilterParams] = useLocalStorage<GraphFilterParams>(storage.rsgraphFilter, {
|
||||
noHermits: true,
|
||||
noTemplates: false,
|
||||
noTransitive: true,
|
||||
noText: false,
|
||||
foldDerived: false,
|
||||
|
||||
focusShowInputs: true,
|
||||
focusShowOutputs: true,
|
||||
|
||||
allowBase: true,
|
||||
allowStruct: true,
|
||||
allowTerm: true,
|
||||
allowAxiom: true,
|
||||
allowFunction: true,
|
||||
allowPredicate: true,
|
||||
allowConstant: true,
|
||||
allowTheorem: true
|
||||
});
|
||||
const [coloring, setColoring] = useLocalStorage<GraphColoring>(storage.rsgraphColoring, 'type');
|
||||
const filter = useTermGraphStore(state => state.filter);
|
||||
const setFilter = useTermGraphStore(state => state.setFilter);
|
||||
const coloring = useTermGraphStore(state => state.coloring);
|
||||
const setColoring = useTermGraphStore(state => state.setColoring);
|
||||
|
||||
const [focusCst, setFocusCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const filteredGraph = useGraphFilter(controller.schema, filterParams, focusCst);
|
||||
const filteredGraph = useGraphFilter(controller.schema, filter, focusCst);
|
||||
const [hidden, setHidden] = useState<ConstituentaID[]>([]);
|
||||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
@ -139,7 +123,7 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
data: {
|
||||
fill: focusCst === cst ? APP_COLORS.bgPurple : colorBgGraphNode(cst, coloring),
|
||||
label: cst.alias,
|
||||
description: !filterParams.noText ? cst.term_resolved : ''
|
||||
description: !filter.noText ? cst.term_resolved : ''
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -167,24 +151,15 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
});
|
||||
});
|
||||
|
||||
applyLayout(newNodes, newEdges, !filterParams.noText);
|
||||
applyLayout(newNodes, newEdges, !filter.noText);
|
||||
|
||||
setNodes(newNodes);
|
||||
setEdges(newEdges);
|
||||
}, [
|
||||
controller.schema,
|
||||
filteredGraph,
|
||||
setNodes,
|
||||
setEdges,
|
||||
filterParams.noText,
|
||||
controller.selected,
|
||||
focusCst,
|
||||
coloring
|
||||
]);
|
||||
}, [controller.schema, filteredGraph, setNodes, setEdges, filter.noText, controller.selected, focusCst, coloring]);
|
||||
|
||||
useEffect(() => {
|
||||
setNeedReset(true);
|
||||
}, [controller.schema, filterParams.noText, focusCst, coloring, flow.viewportInitialized]);
|
||||
}, [controller.schema, filter.noText, focusCst, coloring, flow.viewportInitialized]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!controller.schema || !needReset || !flow.viewportInitialized) {
|
||||
|
@ -198,7 +173,7 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
setTimeout(() => {
|
||||
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||
}, PARAMETER.minimalTimeout);
|
||||
}, [toggleResetView, flow, focusCst, filterParams]);
|
||||
}, [toggleResetView, flow, focusCst, filter]);
|
||||
|
||||
function handleSetSelected(newSelection: number[]) {
|
||||
controller.setSelected(newSelection);
|
||||
|
@ -220,10 +195,6 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
controller.promptDeleteCst();
|
||||
}
|
||||
|
||||
function handleChangeParams(params: GraphFilterParams) {
|
||||
setFilterParams(params);
|
||||
}
|
||||
|
||||
function handleSaveImage() {
|
||||
if (!controller.schema) {
|
||||
return;
|
||||
|
@ -283,10 +254,10 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
}
|
||||
|
||||
function handleFoldDerived() {
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
foldDerived: !prev.foldDerived
|
||||
}));
|
||||
setFilter({
|
||||
...filter,
|
||||
foldDerived: !filter.foldDerived
|
||||
});
|
||||
setTimeout(() => {
|
||||
setToggleResetView(prev => !prev);
|
||||
}, PARAMETER.graphRefreshDelay);
|
||||
|
@ -325,17 +296,13 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
return (
|
||||
<>
|
||||
{showParamsDialog ? (
|
||||
<DlgGraphParams
|
||||
hideWindow={() => setShowParamsDialog(false)}
|
||||
initial={filterParams}
|
||||
onConfirm={handleChangeParams}
|
||||
/>
|
||||
<DlgGraphParams hideWindow={() => setShowParamsDialog(false)} initial={filter} onConfirm={setFilter} />
|
||||
) : null}
|
||||
|
||||
<Overlay position='cc-tab-tools' className='flex flex-col items-center rounded-b-2xl cc-blur'>
|
||||
<ToolbarTermGraph
|
||||
noText={filterParams.noText}
|
||||
foldDerived={filterParams.foldDerived}
|
||||
noText={filter.noText}
|
||||
foldDerived={filter.foldDerived}
|
||||
showParamsDialog={() => setShowParamsDialog(true)}
|
||||
onCreate={handleCreateCst}
|
||||
onDelete={handleDeleteCst}
|
||||
|
@ -343,10 +310,10 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
onSaveImage={handleSaveImage}
|
||||
toggleFoldDerived={handleFoldDerived}
|
||||
toggleNoText={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
noText: !prev.noText
|
||||
}))
|
||||
setFilter({
|
||||
...filter,
|
||||
noText: !filter.noText
|
||||
})
|
||||
}
|
||||
/>
|
||||
{!focusCst ? (
|
||||
|
@ -367,19 +334,19 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
|
|||
<ToolbarFocusedCst
|
||||
center={focusCst}
|
||||
reset={() => handleSetFocus(undefined)}
|
||||
showInputs={filterParams.focusShowInputs}
|
||||
showOutputs={filterParams.focusShowOutputs}
|
||||
showInputs={filter.focusShowInputs}
|
||||
showOutputs={filter.focusShowOutputs}
|
||||
toggleShowInputs={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
focusShowInputs: !prev.focusShowInputs
|
||||
}))
|
||||
setFilter({
|
||||
...filter,
|
||||
focusShowInputs: !filter.focusShowInputs
|
||||
})
|
||||
}
|
||||
toggleShowOutputs={() =>
|
||||
setFilterParams(prev => ({
|
||||
...prev,
|
||||
focusShowOutputs: !prev.focusShowOutputs
|
||||
}))
|
||||
setFilter({
|
||||
...filter,
|
||||
focusShowOutputs: !filter.focusShowOutputs
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
@ -7,13 +7,13 @@ import { CProps } from '@/components/props';
|
|||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
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 { useTermGraphStore } from '@/stores/termGraph';
|
||||
import { APP_COLORS, colorBgGraphNode } from '@/styling/color';
|
||||
import { globals, PARAMETER, prefixes, storage } from '@/utils/constants';
|
||||
import { globals, PARAMETER, prefixes } from '@/utils/constants';
|
||||
|
||||
interface ViewHiddenProps {
|
||||
items: ConstituentaID[];
|
||||
|
@ -29,7 +29,9 @@ interface ViewHiddenProps {
|
|||
function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const localSelected = items.filter(id => selected.includes(id));
|
||||
const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false);
|
||||
|
||||
const isFolded = useTermGraphStore(state => state.foldHidden);
|
||||
const toggleFolded = useTermGraphStore(state => state.toggleFoldHidden);
|
||||
const { setHoverCst } = useConceptOptions();
|
||||
const hiddenHeight = useFitHeight(windowSize.isSmall ? '10.4rem + 2px' : '12.5rem + 2px');
|
||||
|
||||
|
@ -52,7 +54,7 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
|
|||
noHover
|
||||
title={!isFolded ? 'Свернуть' : 'Развернуть'}
|
||||
icon={!isFolded ? <IconDropArrowUp size='1.25rem' /> : <IconDropArrow size='1.25rem' />}
|
||||
onClick={() => setIsFolded(prev => !prev)}
|
||||
onClick={toggleFolded}
|
||||
/>
|
||||
</Overlay>
|
||||
<div className={clsx('pt-2 clr-input border-x pb-2', { 'border-b rounded-b-md': isFolded })}>
|
||||
|
@ -87,7 +89,6 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
|
|||
>
|
||||
{items.map(cstID => {
|
||||
const cst = schema.cstByID.get(cstID)!;
|
||||
const adjustedColoring = coloringScheme === 'none' ? 'status' : coloringScheme;
|
||||
const id = `${prefixes.cst_hidden_list}${cst.alias}`;
|
||||
return (
|
||||
<button
|
||||
|
@ -95,7 +96,7 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
|
|||
type='button'
|
||||
className='min-w-[3rem] rounded-md text-center select-none'
|
||||
style={{
|
||||
backgroundColor: colorBgGraphNode(cst, adjustedColoring),
|
||||
backgroundColor: colorBgGraphNode(cst, coloringScheme),
|
||||
...(localSelected.includes(cstID)
|
||||
? {
|
||||
outlineWidth: '2px',
|
||||
|
|
152
rsconcept/frontend/src/stores/librarySearch.ts
Normal file
152
rsconcept/frontend/src/stores/librarySearch.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { LocationHead } from '@/models/library';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { UserID } from '@/models/user';
|
||||
import { toggleTristateFlag } from '@/utils/utils';
|
||||
|
||||
interface LibrarySearchStore {
|
||||
folderMode: boolean;
|
||||
toggleFolderMode: () => void;
|
||||
|
||||
subfolders: boolean;
|
||||
toggleSubfolders: () => void;
|
||||
|
||||
query: string;
|
||||
setQuery: (value: string) => void;
|
||||
|
||||
path: string;
|
||||
setPath: (value: string) => void;
|
||||
|
||||
location: string;
|
||||
setLocation: (value: string) => void;
|
||||
|
||||
head: LocationHead | undefined;
|
||||
setHead: (value: LocationHead | undefined) => void;
|
||||
|
||||
isVisible: boolean | undefined;
|
||||
toggleVisible: () => void;
|
||||
|
||||
isOwned: boolean | undefined;
|
||||
toggleOwned: () => void;
|
||||
|
||||
isEditor: boolean | undefined;
|
||||
toggleEditor: () => void;
|
||||
|
||||
filterUser: UserID | undefined;
|
||||
setFilterUser: (value: UserID | undefined) => void;
|
||||
|
||||
resetFilter: () => void;
|
||||
}
|
||||
|
||||
export const useLibrarySearchStore = create<LibrarySearchStore>()(
|
||||
persist(
|
||||
set => ({
|
||||
folderMode: true,
|
||||
toggleFolderMode: () => set(state => ({ folderMode: !state.folderMode })),
|
||||
|
||||
subfolders: false,
|
||||
toggleSubfolders: () => set(state => ({ subfolders: !state.subfolders })),
|
||||
|
||||
query: '',
|
||||
setQuery: value => set({ query: value }),
|
||||
|
||||
path: '',
|
||||
setPath: value => set({ path: value }),
|
||||
|
||||
location: '',
|
||||
setLocation: value => set(!!value ? { location: value, folderMode: true } : { location: '' }),
|
||||
|
||||
head: undefined,
|
||||
setHead: value => set({ head: value }),
|
||||
|
||||
isVisible: true,
|
||||
toggleVisible: () => set(state => ({ isVisible: toggleTristateFlag(state.isVisible) })),
|
||||
|
||||
isOwned: undefined,
|
||||
toggleOwned: () => set(state => ({ isOwned: toggleTristateFlag(state.isOwned) })),
|
||||
|
||||
isEditor: undefined,
|
||||
toggleEditor: () => set(state => ({ isEditor: toggleTristateFlag(state.isEditor) })),
|
||||
|
||||
filterUser: undefined,
|
||||
setFilterUser: value => set({ filterUser: value }),
|
||||
|
||||
resetFilter: () =>
|
||||
set(() => ({
|
||||
query: '',
|
||||
path: '',
|
||||
location: '',
|
||||
head: undefined,
|
||||
isVisible: true,
|
||||
isOwned: undefined,
|
||||
isEditor: undefined,
|
||||
filterUser: undefined
|
||||
}))
|
||||
}),
|
||||
{
|
||||
version: 1,
|
||||
partialize: state => ({
|
||||
folderMode: state.folderMode,
|
||||
subfolders: state.subfolders,
|
||||
|
||||
location: state.location,
|
||||
head: state.head,
|
||||
isVisible: state.isVisible,
|
||||
isOwned: state.isOwned,
|
||||
isEditor: state.isEditor,
|
||||
filterUser: state.filterUser
|
||||
}),
|
||||
name: 'portal.library.search'
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
/** Utility function that indicates if custom filter is set. */
|
||||
export function useHasCustomFilter(): boolean {
|
||||
const path = useLibrarySearchStore(state => state.path);
|
||||
const query = useLibrarySearchStore(state => state.query);
|
||||
const head = useLibrarySearchStore(state => state.head);
|
||||
const isEditor = useLibrarySearchStore(state => state.isEditor);
|
||||
const isOwned = useLibrarySearchStore(state => state.isOwned);
|
||||
const isVisible = useLibrarySearchStore(state => state.isVisible);
|
||||
const filterUser = useLibrarySearchStore(state => state.filterUser);
|
||||
const location = useLibrarySearchStore(state => state.location);
|
||||
return (
|
||||
!!path ||
|
||||
!!query ||
|
||||
!!location ||
|
||||
head !== undefined ||
|
||||
isEditor !== undefined ||
|
||||
isOwned !== undefined ||
|
||||
isVisible !== true ||
|
||||
filterUser !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
/** Utility function that returns the current library filter. */
|
||||
export function useLibraryFilter(): ILibraryFilter {
|
||||
const head = useLibrarySearchStore(state => state.head);
|
||||
const path = useLibrarySearchStore(state => state.path);
|
||||
const query = useLibrarySearchStore(state => state.query);
|
||||
const isEditor = useLibrarySearchStore(state => state.isEditor);
|
||||
const isOwned = useLibrarySearchStore(state => state.isOwned);
|
||||
const isVisible = useLibrarySearchStore(state => state.isVisible);
|
||||
const folderMode = useLibrarySearchStore(state => state.folderMode);
|
||||
const subfolders = useLibrarySearchStore(state => state.subfolders);
|
||||
const location = useLibrarySearchStore(state => state.location);
|
||||
const filterUser = useLibrarySearchStore(state => state.filterUser);
|
||||
return {
|
||||
head: head,
|
||||
path: path,
|
||||
query: query,
|
||||
isEditor: isEditor,
|
||||
isOwned: isOwned,
|
||||
isVisible: isVisible,
|
||||
folderMode: folderMode,
|
||||
subfolders: subfolders,
|
||||
location: location,
|
||||
filterUser: filterUser
|
||||
};
|
||||
}
|
|
@ -1,20 +1,70 @@
|
|||
import { flushSync } from 'react-dom';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
interface PreferencesStore {
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
|
||||
showHelp: boolean;
|
||||
adminMode: boolean;
|
||||
toggleShowHelp: () => void;
|
||||
|
||||
adminMode: boolean;
|
||||
toggleAdminMode: () => void;
|
||||
|
||||
libraryPagination: number;
|
||||
setLibraryPagination: (value: number) => void;
|
||||
|
||||
showCstSideList: boolean;
|
||||
toggleShowCstSideList: () => void;
|
||||
|
||||
showExpressionControls: boolean;
|
||||
toggleShowExpressionControls: () => void;
|
||||
}
|
||||
|
||||
export const usePreferencesStore = create<PreferencesStore>()(
|
||||
persist(
|
||||
set => ({
|
||||
darkMode: initializeDarkMode(),
|
||||
toggleDarkMode: () => {
|
||||
if (!document.startViewTransition) {
|
||||
set(state => applyDarkMode(!state.darkMode));
|
||||
return;
|
||||
}
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
* {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.startViewTransition(() => {
|
||||
flushSync(() => {
|
||||
set(state => applyDarkMode(!state.darkMode));
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => document.head.removeChild(style), PARAMETER.moveDuration);
|
||||
},
|
||||
|
||||
showHelp: true,
|
||||
adminMode: false,
|
||||
toggleShowHelp: () => set(state => ({ showHelp: !state.showHelp })),
|
||||
toggleAdminMode: () => set(state => ({ adminMode: !state.adminMode }))
|
||||
|
||||
adminMode: false,
|
||||
toggleAdminMode: () => set(state => ({ adminMode: !state.adminMode })),
|
||||
|
||||
libraryPagination: 50,
|
||||
setLibraryPagination: value => set({ libraryPagination: value }),
|
||||
|
||||
showCstSideList: true,
|
||||
toggleShowCstSideList: () => set(state => ({ showCstSideList: !state.showCstSideList })),
|
||||
|
||||
showExpressionControls: true,
|
||||
toggleShowExpressionControls: () => set(state => ({ showExpressionControls: !state.showExpressionControls }))
|
||||
}),
|
||||
{
|
||||
version: 1,
|
||||
|
@ -22,3 +72,31 @@ export const usePreferencesStore = create<PreferencesStore>()(
|
|||
}
|
||||
)
|
||||
);
|
||||
|
||||
function initializeDarkMode(): boolean {
|
||||
let isDark = false;
|
||||
if ('portal.preferences' in localStorage) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const preferences = JSON.parse(localStorage.getItem('portal.preferences') ?? '{}').state as PreferencesStore;
|
||||
isDark = preferences.darkMode;
|
||||
} else if (window.matchMedia) {
|
||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
return isDark;
|
||||
}
|
||||
|
||||
function applyDarkMode(isDark: boolean) {
|
||||
const root = window.document.documentElement;
|
||||
if (isDark) {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
|
||||
return { darkMode: isDark };
|
||||
}
|
||||
|
|
52
rsconcept/frontend/src/stores/termGraph.ts
Normal file
52
rsconcept/frontend/src/stores/termGraph.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { GraphColoring, GraphFilterParams } from '@/models/miscellaneous';
|
||||
|
||||
interface TermGraphStore {
|
||||
filter: GraphFilterParams;
|
||||
setFilter: (value: GraphFilterParams) => void;
|
||||
|
||||
foldHidden: boolean;
|
||||
toggleFoldHidden: () => void;
|
||||
|
||||
coloring: GraphColoring;
|
||||
setColoring: (value: GraphColoring) => void;
|
||||
}
|
||||
|
||||
export const useTermGraphStore = create<TermGraphStore>()(
|
||||
persist(
|
||||
set => ({
|
||||
filter: {
|
||||
noTemplates: false,
|
||||
noHermits: true,
|
||||
noTransitive: true,
|
||||
noText: false,
|
||||
foldDerived: false,
|
||||
|
||||
focusShowInputs: true,
|
||||
focusShowOutputs: true,
|
||||
|
||||
allowBase: true,
|
||||
allowStruct: true,
|
||||
allowTerm: true,
|
||||
allowAxiom: true,
|
||||
allowFunction: true,
|
||||
allowPredicate: true,
|
||||
allowConstant: true,
|
||||
allowTheorem: true
|
||||
},
|
||||
setFilter: value => set({ filter: value }),
|
||||
|
||||
foldHidden: false,
|
||||
toggleFoldHidden: () => set(state => ({ foldHidden: !state.foldHidden })),
|
||||
|
||||
coloring: 'type',
|
||||
setColoring: value => set({ coloring: value })
|
||||
}),
|
||||
{
|
||||
version: 1,
|
||||
name: 'portal.termGraph'
|
||||
}
|
||||
)
|
||||
);
|
|
@ -108,25 +108,6 @@ export const external_urls = {
|
|||
export const storage = {
|
||||
PREFIX: 'portal.',
|
||||
|
||||
themeDark: 'theme.dark',
|
||||
|
||||
rseditShowList: 'rsedit.show_list',
|
||||
rseditShowControls: 'rsedit.show_controls',
|
||||
|
||||
librarySearchHead: 'library.search.head',
|
||||
librarySearchFolderMode: 'library.search.folder_mode',
|
||||
librarySearchSubfolders: 'library.search.subfolders',
|
||||
librarySearchLocation: 'library.search.location',
|
||||
librarySearchVisible: 'library.search.visible',
|
||||
librarySearchOwned: 'library.search.owned',
|
||||
librarySearchEditor: 'library.search.editor',
|
||||
librarySearchUser: 'library.search.user',
|
||||
libraryPagination: 'library.pagination',
|
||||
|
||||
rsgraphFilter: 'rsgraph.filter2',
|
||||
rsgraphColoring: 'rsgraph.coloring',
|
||||
rsgraphFoldHidden: 'rsgraph.fold_hidden',
|
||||
|
||||
ossShowGrid: 'oss.show_grid',
|
||||
ossEdgeStraight: 'oss.edge_straight',
|
||||
ossEdgeAnimate: 'oss.edge_animate',
|
||||
|
|
Loading…
Reference in New Issue
Block a user