M: Improve Library location navigation
This commit is contained in:
parent
b3ecae5d3d
commit
0a0342efc6
|
@ -34,6 +34,12 @@ interface IOptionsContext {
|
|||
showHelp: boolean;
|
||||
toggleShowHelp: () => void;
|
||||
|
||||
folderMode: boolean;
|
||||
setFolderMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
location: string;
|
||||
setLocation: React.Dispatch<React.SetStateAction<string>>;
|
||||
|
||||
calculateHeight: (offset: string, minimum?: string) => string;
|
||||
}
|
||||
|
||||
|
@ -56,6 +62,9 @@ export const OptionsState = ({ children }: OptionsStateProps) => {
|
|||
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 [colors, setColors] = useState<IColorTheme>(lightT);
|
||||
|
||||
const [noNavigationAnimation, setNoNavigationAnimation] = useState(false);
|
||||
|
@ -131,6 +140,10 @@ export const OptionsState = ({ children }: OptionsStateProps) => {
|
|||
noNavigationAnimation,
|
||||
noNavigation,
|
||||
noFooter,
|
||||
folderMode,
|
||||
setFolderMode,
|
||||
location,
|
||||
setLocation,
|
||||
showScroll,
|
||||
showHelp,
|
||||
toggleDarkMode: toggleDarkMode,
|
||||
|
|
|
@ -20,17 +20,18 @@ 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 useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
||||
import { ILibraryCreateData } from '@/models/library';
|
||||
import { combineLocation, validateLocation } from '@/models/libraryAPI';
|
||||
import { EXTEOR_TRS_FILE, storage } from '@/utils/constants';
|
||||
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();
|
||||
|
||||
|
@ -46,7 +47,6 @@ function FormCreateItem() {
|
|||
|
||||
const location = useMemo(() => combineLocation(head, body), [head, body]);
|
||||
const isValid = useMemo(() => validateLocation(location), [location]);
|
||||
const [initLocation, setInitLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
|
||||
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [file, setFile] = useState<File | undefined>();
|
||||
|
@ -81,7 +81,7 @@ function FormCreateItem() {
|
|||
file: file,
|
||||
fileName: file?.name
|
||||
};
|
||||
setInitLocation(location);
|
||||
options.setLocation(location);
|
||||
createItem(data, newItem => {
|
||||
toast.success(information.newLibraryItem);
|
||||
if (itemType == LibraryItemType.RSFORM) {
|
||||
|
@ -108,11 +108,11 @@ function FormCreateItem() {
|
|||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!initLocation) {
|
||||
if (!options.location) {
|
||||
return;
|
||||
}
|
||||
handleSelectLocation(initLocation);
|
||||
}, [initLocation, handleSelectLocation]);
|
||||
handleSelectLocation(options.location);
|
||||
}, [options.location, handleSelectLocation]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (itemType !== LibraryItemType.RSFORM) {
|
||||
|
|
|
@ -10,6 +10,7 @@ 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';
|
||||
|
@ -27,14 +28,13 @@ function LibraryPage() {
|
|||
const library = useLibrary();
|
||||
const { user } = useAuth();
|
||||
const [items, setItems] = useState<ILibraryItem[]>([]);
|
||||
const options = useConceptOptions();
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const [path, setPath] = useState('');
|
||||
|
||||
const [head, setHead] = useLocalStorage<LocationHead | undefined>(storage.librarySearchHead, undefined);
|
||||
const [folderMode, setFolderMode] = useLocalStorage<boolean>(storage.librarySearchFolderMode, true);
|
||||
const [subfolders, setSubfolders] = useLocalStorage<boolean>(storage.librarySearchSubfolders, false);
|
||||
const [location, setLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
|
||||
const [isVisible, setIsVisible] = useLocalStorage<boolean | undefined>(storage.librarySearchVisible, true);
|
||||
const [isOwned, setIsOwned] = useLocalStorage<boolean | undefined>(storage.librarySearchEditor, undefined);
|
||||
const [isEditor, setIsEditor] = useLocalStorage<boolean | undefined>(storage.librarySearchEditor, undefined);
|
||||
|
@ -48,11 +48,11 @@ function LibraryPage() {
|
|||
isEditor: user ? isEditor : undefined,
|
||||
isOwned: user ? isOwned : undefined,
|
||||
isVisible: user ? isVisible : true,
|
||||
folderMode: folderMode,
|
||||
folderMode: options.folderMode,
|
||||
subfolders: subfolders,
|
||||
location: location
|
||||
location: options.location
|
||||
}),
|
||||
[head, path, query, isEditor, isOwned, isVisible, user, folderMode, location, subfolders]
|
||||
[head, path, query, isEditor, isOwned, isVisible, user, options.folderMode, options.location, subfolders]
|
||||
);
|
||||
|
||||
const hasCustomFilter = useMemo(
|
||||
|
@ -74,7 +74,7 @@ function LibraryPage() {
|
|||
const toggleVisible = useCallback(() => setIsVisible(prev => toggleTristateFlag(prev)), [setIsVisible]);
|
||||
const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]);
|
||||
const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]);
|
||||
const toggleFolderMode = useCallback(() => setFolderMode(prev => !prev), [setFolderMode]);
|
||||
const toggleFolderMode = useCallback(() => options.setFolderMode(prev => !prev), [options.setFolderMode]);
|
||||
const toggleSubfolders = useCallback(() => setSubfolders(prev => !prev), [setSubfolders]);
|
||||
|
||||
const resetFilter = useCallback(() => {
|
||||
|
@ -84,8 +84,8 @@ function LibraryPage() {
|
|||
setIsVisible(true);
|
||||
setIsOwned(undefined);
|
||||
setIsEditor(undefined);
|
||||
setLocation('');
|
||||
}, [setHead, setIsVisible, setIsOwned, setIsEditor, setLocation]);
|
||||
options.setLocation('');
|
||||
}, [setHead, setIsVisible, setIsOwned, setIsEditor, options.setLocation]);
|
||||
|
||||
const promptRenameLocation = useCallback(() => {
|
||||
setShowRenameLocation(true);
|
||||
|
@ -94,11 +94,11 @@ function LibraryPage() {
|
|||
const handleRenameLocation = useCallback(
|
||||
(newLocation: string) => {
|
||||
const data: IRenameLocationData = {
|
||||
target: location,
|
||||
target: options.location,
|
||||
new_location: newLocation
|
||||
};
|
||||
library.renameLocation(data, () => {
|
||||
setLocation(newLocation);
|
||||
options.setLocation(newLocation);
|
||||
toast.success(information.locationRenamed);
|
||||
});
|
||||
},
|
||||
|
@ -123,18 +123,18 @@ function LibraryPage() {
|
|||
<TableLibraryItems
|
||||
resetQuery={resetFilter}
|
||||
items={items}
|
||||
folderMode={folderMode}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
/>
|
||||
),
|
||||
[resetFilter, items, folderMode, toggleFolderMode]
|
||||
[resetFilter, items, options.folderMode, toggleFolderMode]
|
||||
);
|
||||
|
||||
const viewLocations = useMemo(
|
||||
() => (
|
||||
<ViewSideLocation
|
||||
active={location}
|
||||
setActive={setLocation}
|
||||
active={options.location}
|
||||
setActive={options.setLocation}
|
||||
subfolders={subfolders}
|
||||
folderTree={library.folders}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
|
@ -142,7 +142,7 @@ function LibraryPage() {
|
|||
onRenameLocation={promptRenameLocation}
|
||||
/>
|
||||
),
|
||||
[location, library.folders, setLocation, toggleFolderMode, subfolders]
|
||||
[options.location, library.folders, options.setLocation, toggleFolderMode, subfolders]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -154,12 +154,16 @@ function LibraryPage() {
|
|||
>
|
||||
{showRenameLocation ? (
|
||||
<DlgChangeLocation
|
||||
initial={location}
|
||||
initial={options.location}
|
||||
onChangeLocation={handleRenameLocation}
|
||||
hideWindow={() => setShowRenameLocation(false)}
|
||||
/>
|
||||
) : null}
|
||||
<Overlay position='top-[0.25rem] right-0' layer='z-tooltip'>
|
||||
<Overlay
|
||||
position={options.noNavigation ? 'top-[0.25rem] right-[3rem]' : 'top-[0.25rem] right-0'}
|
||||
layer='z-tooltip'
|
||||
className='transition-all'
|
||||
>
|
||||
<MiniButton
|
||||
title='Выгрузить в формате CSV'
|
||||
icon={<IconCSV size='1.25rem' className='icon-green' />}
|
||||
|
@ -183,12 +187,12 @@ function LibraryPage() {
|
|||
isEditor={isEditor}
|
||||
toggleEditor={toggleEditor}
|
||||
resetFilter={resetFilter}
|
||||
folderMode={folderMode}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
/>
|
||||
|
||||
<div className='flex'>
|
||||
<AnimatePresence initial={false}>{folderMode ? viewLocations : null}</AnimatePresence>
|
||||
<AnimatePresence initial={false}>{options.folderMode ? viewLocations : null}</AnimatePresence>
|
||||
{viewLibrary}
|
||||
</div>
|
||||
</DataLoader>
|
||||
|
|
|
@ -96,15 +96,20 @@ function ToolbarSearch({
|
|||
<div
|
||||
className={clsx(
|
||||
'sticky top-0', // prettier: split lines
|
||||
'w-full h-[2.2rem]',
|
||||
'flex items-center',
|
||||
'h-[2.2rem]',
|
||||
'flex items-center gap-3',
|
||||
'border-b',
|
||||
'text-sm',
|
||||
'clr-input'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx('px-3 pt-1 self-center', 'min-w-[6rem] sm:min-w-[7rem]', 'select-none', 'whitespace-nowrap')}
|
||||
className={clsx(
|
||||
'ml-3 pt-1 self-center',
|
||||
'min-w-[4.5rem] sm:min-w-[5.5rem]',
|
||||
'select-none',
|
||||
'whitespace-nowrap'
|
||||
)}
|
||||
>
|
||||
{filtered} из {total}
|
||||
</div>
|
||||
|
@ -138,7 +143,7 @@ function ToolbarSearch({
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
<div className='flex items-center h-full mx-auto'>
|
||||
<div className='flex h-full'>
|
||||
<SearchBar
|
||||
id='library_search'
|
||||
placeholder='Поиск'
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { IconDateCreate, IconDateUpdate, IconEditor, IconFolder, IconOwner } from '@/components/Icons';
|
||||
import { urls } from '@/app/urls';
|
||||
import {
|
||||
IconDateCreate,
|
||||
IconDateUpdate,
|
||||
IconEditor,
|
||||
IconFolderEdit,
|
||||
IconFolderOpened,
|
||||
IconOwner
|
||||
} from '@/components/Icons';
|
||||
import InfoUsers from '@/components/info/InfoUsers';
|
||||
import { CProps } from '@/components/props';
|
||||
import SelectUser from '@/components/select/SelectUser';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
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';
|
||||
|
@ -25,6 +37,8 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
|||
const { getUserLabel, users } = useUsers();
|
||||
const { accessLevel } = useAccessMode();
|
||||
const intl = useIntl();
|
||||
const router = useConceptNavigation();
|
||||
const options = useConceptOptions();
|
||||
|
||||
const ownerSelector = useDropdown();
|
||||
const onSelectUser = useCallback(
|
||||
|
@ -41,20 +55,43 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
|||
[controller, item?.owner, ownerSelector]
|
||||
);
|
||||
|
||||
const handleOpenLibrary = useCallback(
|
||||
(event: CProps.EventMouse) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
options.setLocation(item.location);
|
||||
options.setFolderMode(true);
|
||||
router.push(urls.library, event.ctrlKey || event.metaKey);
|
||||
},
|
||||
[options.setLocation, options.setFolderMode, item, router]
|
||||
);
|
||||
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<ValueIcon
|
||||
className='sm:mb-1 text-ellipsis max-w-[30rem]'
|
||||
icon={<IconFolder size='1.25rem' className='icon-primary' />}
|
||||
value={item.location}
|
||||
title={controller.isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Путь'}
|
||||
onClick={controller.promptLocation}
|
||||
disabled={isModified || controller.isProcessing || controller.isAttachedToOSS || accessLevel < UserLevel.OWNER}
|
||||
/>
|
||||
<div className='flex justify-stretch sm:mb-1 max-w-[30rem] gap-3'>
|
||||
<MiniButton
|
||||
noHover
|
||||
noPadding
|
||||
title='Открыть в библиотеке'
|
||||
icon={<IconFolderOpened size='1.25rem' className='icon-primary' />}
|
||||
onClick={handleOpenLibrary}
|
||||
/>
|
||||
<ValueIcon
|
||||
className='text-ellipsis flex-grow'
|
||||
icon={<IconFolderEdit size='1.25rem' className='icon-primary' />}
|
||||
value={item.location}
|
||||
title={controller.isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Путь'}
|
||||
onClick={controller.promptLocation}
|
||||
disabled={
|
||||
isModified || controller.isProcessing || controller.isAttachedToOSS || accessLevel < UserLevel.OWNER
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ownerSelector.isOpen ? (
|
||||
<Overlay position='top-[-0.5rem] left-[2.5rem] cc-icons'>
|
||||
|
|
|
@ -27,9 +27,9 @@ import {
|
|||
IconUpload
|
||||
} from '@/components/Icons';
|
||||
import Button from '@/components/ui/Button';
|
||||
import DropdownDivider from '@/components/ui/DropdownDivider';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
import DropdownButton from '@/components/ui/DropdownButton';
|
||||
import DropdownDivider from '@/components/ui/DropdownDivider';
|
||||
import { useAccessMode } from '@/context/AccessModeContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useGlobalOss } from '@/context/GlobalOssContext';
|
||||
|
|
Loading…
Reference in New Issue
Block a user