From edb3bb4f10ff380cc9149375c30b52290d6b65f7 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:00:29 +0300 Subject: [PATCH] Implement location selector button --- .../src/components/select/SelectLocation.tsx | 113 ++++++++++++++---- .../select/SelectLocationContext.tsx | 62 ++++++++++ .../frontend/src/components/ui/Dropdown.tsx | 6 +- .../src/dialogs/DlgChangeLocation.tsx | 19 ++- .../src/dialogs/DlgCloneLibraryItem.tsx | 11 +- .../pages/CreateItemPage/FormCreateItem.tsx | 8 +- .../src/pages/LibraryPage/LibraryFolders.tsx | 107 ++--------------- 7 files changed, 196 insertions(+), 130 deletions(-) create mode 100644 rsconcept/frontend/src/components/select/SelectLocationContext.tsx diff --git a/rsconcept/frontend/src/components/select/SelectLocation.tsx b/rsconcept/frontend/src/components/select/SelectLocation.tsx index 43cbcd66..cded1871 100644 --- a/rsconcept/frontend/src/components/select/SelectLocation.tsx +++ b/rsconcept/frontend/src/components/select/SelectLocation.tsx @@ -1,40 +1,109 @@ 'use client'; -import { useCallback } from 'react'; +import clsx from 'clsx'; +import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; -import useDropdown from '@/hooks/useDropdown'; -import { FolderTree } from '@/models/FolderTree'; +import { FolderNode, FolderTree } from '@/models/FolderTree'; +import { labelFolderNode } from '@/utils/labels'; -import { IconFolderTree } from '../Icons'; +import { IconFolder, IconFolderClosed, IconFolderEmpty, IconFolderOpened } from '../Icons'; +import { CProps } from '../props'; import MiniButton from '../ui/MiniButton'; -interface SelectLocationProps { +interface SelectLocationProps extends CProps.Styling { value: string; - onChange: (newValue: string) => void; - folderTree: FolderTree; + prefix: string; + dense?: boolean; + onClick: (event: CProps.EventMouse, target: FolderNode) => void; } -function SelectLocation({ value, onChange, folderTree }: SelectLocationProps) { - const menu = useDropdown(); +function SelectLocation({ value, folderTree, dense, prefix, onClick, className, style }: SelectLocationProps) { + const activeNode = useMemo(() => folderTree.at(value), [folderTree, value]); - const handleChange = useCallback( - (newValue: string) => { - console.log(folderTree.roots.size); - console.log(value); - menu.hide(); - onChange(newValue); + const items = useMemo(() => folderTree.getTree(), [folderTree]); + const [folded, setFolded] = useState(items); + + useLayoutEffect(() => { + setFolded(items.filter(item => item !== activeNode && (!activeNode || !activeNode.hasPredecessor(item)))); + }, [items, activeNode]); + + const onFoldItem = useCallback( + (target: FolderNode, showChildren: boolean) => { + setFolded(prev => + items.filter(item => { + if (item === target) { + return !showChildren; + } + if (!showChildren && item.hasPredecessor(target)) { + return true; + } else { + return prev.includes(item); + } + }) + ); }, - [menu, onChange, value, folderTree] + [items] + ); + + const handleClickFold = useCallback( + (event: CProps.EventMouse, target: FolderNode, showChildren: boolean) => { + event.preventDefault(); + event.stopPropagation(); + onFoldItem(target, showChildren); + }, + [onFoldItem] ); return ( -
- } - onClick={() => handleChange('/U/test')} - /> +
+ {items.map((item, index) => + !item.parent || !folded.includes(item.parent) ? ( +
5 ? 5 : item.rank) * 0.5 + 0.5}rem` }} + onClick={event => onClick(event, item)} + > + {item.children.size > 0 ? ( + + ) : ( + + ) + ) : ( + + ) + } + onClick={event => handleClickFold(event, item, folded.includes(item))} + /> + ) : ( +
+ {item.filesInside ? ( + + ) : ( + + )} +
+ )} +
{labelFolderNode(item)}
+
+ ) : null + )}
); } diff --git a/rsconcept/frontend/src/components/select/SelectLocationContext.tsx b/rsconcept/frontend/src/components/select/SelectLocationContext.tsx new file mode 100644 index 00000000..91e260a8 --- /dev/null +++ b/rsconcept/frontend/src/components/select/SelectLocationContext.tsx @@ -0,0 +1,62 @@ +'use client'; + +import clsx from 'clsx'; +import { useCallback } from 'react'; + +import useDropdown from '@/hooks/useDropdown'; +import { FolderTree } from '@/models/FolderTree'; +import { prefixes } from '@/utils/constants'; + +import { IconFolderTree } from '../Icons'; +import { CProps } from '../props'; +import Dropdown from '../ui/Dropdown'; +import MiniButton from '../ui/MiniButton'; +import SelectLocation from './SelectLocation'; + +interface SelectLocationContextProps extends CProps.Styling { + value: string; + folderTree: FolderTree; + stretchTop?: boolean; + + onChange: (newValue: string) => void; +} + +function SelectLocationContext({ value, folderTree, onChange, className, style }: SelectLocationContextProps) { + const menu = useDropdown(); + + const handleClick = useCallback( + (event: CProps.EventMouse, newValue: string) => { + event.preventDefault(); + event.stopPropagation(); + menu.hide(); + onChange(newValue); + }, + [menu, onChange] + ); + + return ( +
+ } + onClick={() => menu.toggle()} + /> + + handleClick(event, target.getPath())} + /> + +
+ ); +} + +export default SelectLocationContext; diff --git a/rsconcept/frontend/src/components/ui/Dropdown.tsx b/rsconcept/frontend/src/components/ui/Dropdown.tsx index c92f33b7..be089f30 100644 --- a/rsconcept/frontend/src/components/ui/Dropdown.tsx +++ b/rsconcept/frontend/src/components/ui/Dropdown.tsx @@ -7,11 +7,12 @@ import { CProps } from '../props'; interface DropdownProps extends CProps.Styling { stretchLeft?: boolean; + stretchTop?: boolean; isOpen: boolean; children: React.ReactNode; } -function Dropdown({ isOpen, stretchLeft, className, children, ...restProps }: DropdownProps) { +function Dropdown({ isOpen, stretchLeft, stretchTop, className, children, ...restProps }: DropdownProps) { return (
(initial.substring(0, 2) as LocationHead); const [body, setBody] = useState(initial.substring(3)); + const { folders } = useLibrary(); + const location = useMemo(() => combineLocation(head, body), [head, body]); const isValid = useMemo(() => initial !== location && validateLocation(location), [initial, location]); + const handleSelectLocation = useCallback((newValue: string) => { + setHead(newValue.substring(0, 2) as LocationHead); + setBody(newValue.length > 3 ? newValue.substring(3) : ''); + }, []); + function handleSubmit() { onChangeLocation(location); } @@ -41,13 +50,19 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL className={clsx('w-[35rem]', 'pb-3 px-6 flex gap-3')} >
-
+