From 9f1ee6f4914a807eeb68d423223ccbe7db49e099 Mon Sep 17 00:00:00 2001 From: Jacob Rief Date: Thu, 26 Sep 2024 18:03:20 +0200 Subject: [PATCH] refactor towards menu bar with extra item --- client/components/menu/Audio.tsx | 7 +- client/finder-admin.scss | 48 +++---- client/finder/DropDownMenu.tsx | 36 ++++++ client/finder/MenuBar.tsx | 210 +++++++++++++++++++------------ client/finder/Search.tsx | 47 +++---- client/icons/add-folder.svg | 2 +- 6 files changed, 216 insertions(+), 134 deletions(-) create mode 100644 client/finder/DropDownMenu.tsx diff --git a/client/components/menu/Audio.tsx b/client/components/menu/Audio.tsx index 010522d78..8ee537149 100644 --- a/client/components/menu/Audio.tsx +++ b/client/components/menu/Audio.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React from 'react'; import ArchiveIcon from 'icons/archive.svg'; @@ -6,11 +6,12 @@ export default function Audio(props) { console.log(props); function archiveSelectedIcons() { + console.log("archiveSelectedIcons", props); } return ( -
  • archiveSelectedIcons()} className={props.numSelectedInodes ? null : "disabled"} data-tooltip-id="django-finder-tooltip" data-tooltip-content={gettext("Create archive from selection")}> - +
  • + {gettext("Create archive from selection")}
  • ); } diff --git a/client/finder-admin.scss b/client/finder-admin.scss index 19eb42760..9b6d4fbf1 100644 --- a/client/finder-admin.scss +++ b/client/finder-admin.scss @@ -123,7 +123,6 @@ $active-rectangle: rgb(210, 210, 112); .search-realm { [role="combobox"] { - width: 225px; li:nth-child(2) { padding-bottom: 4px; border-bottom: 1px solid lightgrey; @@ -143,24 +142,11 @@ $active-rectangle: rgb(210, 210, 112); margin-left: auto; margin-right: auto; width: auto; + } - &::after { - right: -12px; - } - - [role="combobox"] { - width: 150px; - - svg { - color: grey; - } - span { - margin-left: 40px; - } - svg + span { - margin-left: 10px; - } - } + &:last-of-type [role="combobox"] { + right: 0; + margin-inline-end: inherit; } svg { @@ -169,13 +155,12 @@ $active-rectangle: rgb(210, 210, 112); height: 30px; margin: 1px; } - } - [aria-haspopup="true"] { + [aria-haspopup="true"]:has([role="combobox"]) { position: relative; display: flex; - &::after { + &.with-caret::after { content: " "; align-self: center; border: 5px solid transparent; @@ -189,14 +174,19 @@ $active-rectangle: rgb(210, 210, 112); z-index: 1; background-color: white; top: 100%; - left: calc(32px - 150px / 2); + left: -250px; + right:-250px; + margin-inline: auto; border: 1px solid rgb(192, 192, 192); border-radius: 4px; box-shadow: 0 0 8px rgb(192, 192, 192); display: flex; flex-direction: column; - margin: 0; - padding: 3px 0; + width: max-content; + padding-top: 3px; + padding-bottom: 3px; + padding-inline-start: 3px; + padding-inline-end: 30px; &[aria-expanded="false"] { display: none; @@ -216,6 +206,16 @@ $active-rectangle: rgb(210, 210, 112); right: 10px; } } + + svg { + color: grey; + } + span { + margin-left: 40px; + } + svg + span { + margin-left: 10px; + } } } } diff --git a/client/finder/DropDownMenu.tsx b/client/finder/DropDownMenu.tsx new file mode 100644 index 000000000..af343b665 --- /dev/null +++ b/client/finder/DropDownMenu.tsx @@ -0,0 +1,36 @@ +import React, {useEffect, useRef} from 'react'; + + +export default function DropDownMenu(props) { + const ref = useRef(null); + const Wrapper = props.wrapperElement ?? 'li'; + + useEffect(() => { + const closeSubmenu = (event) => { + if (!ref.current?.parentElement?.contains(event.target)) { + ref.current.setAttribute('aria-expanded', 'false'); + } + }; + window.addEventListener('click', closeSubmenu); + return () => window.removeEventListener('click', closeSubmenu); + }, []); + + function toggleSubmenu() { + ref.current.setAttribute('aria-expanded', ref.current.ariaExpanded === 'true' ? 'false': 'true'); + } + + return ( + + {props.icon} + + + ) +} diff --git a/client/finder/MenuBar.tsx b/client/finder/MenuBar.tsx index 0086b1b3d..f74de93b6 100644 --- a/client/finder/MenuBar.tsx +++ b/client/finder/MenuBar.tsx @@ -1,5 +1,4 @@ import React, { - useRef, forwardRef, useState, useImperativeHandle, @@ -10,7 +9,8 @@ import React, { } from 'react'; import {useClipboard, useCookie} from './Storage'; import {SearchField} from './Search'; -import {FinderSettings} from './FinderSettings'; +import DropDownMenu from './DropDownMenu'; +import MoreVerticalIcon from 'icons/more-vertical.svg'; import CopyIcon from 'icons/copy.svg'; import TilesIcon from 'icons/tiles.svg'; import MosaicIcon from 'icons/mosaic.svg'; @@ -32,23 +32,8 @@ const useSorting = () => useCookie('django-finder-sorting', ''); function SortingOptionsItem(props: any) { - const ref = useRef(null); const [sorting, setSorting] = useSorting(); - useEffect(() => { - const closeSorting = (event) => { - if (!ref.current?.parentElement?.contains(event.target)) { - ref.current.setAttribute('aria-expanded', 'false'); - } - }; - window.addEventListener('click', closeSorting); - return () => window.removeEventListener('click', closeSorting); - }, []); - - function toggleSorting() { - ref.current.setAttribute('aria-expanded', ref.current.ariaExpanded === 'true' ? 'false': 'true'); - } - function isActive(value) { return sorting === value ? 'active' : null; } @@ -63,22 +48,34 @@ function SortingOptionsItem(props: any) { } return ( -
  • - - -
  • - ) + } className="sorting-dropdown with-caret" tooltip={gettext("Change sorting order")}> +
  • changeSorting('')} className={isActive('')}>{gettext("Unsorted")}
  • +
  • changeSorting('name_asc')} className={isActive('name_asc')}> + {gettext("Name")} +
  • +
  • changeSorting('name_desc')} className={isActive('name_desc')}> + {gettext("Name")} +
  • +
  • changeSorting('date_asc')} className={isActive('date_asc')}> + {gettext("Date")} +
  • +
  • changeSorting('date_desc')} className={isActive('date_desc')}> + {gettext("Date")} +
  • +
  • changeSorting('size_asc')} className={isActive('size_asc')}> + {gettext("Size")} +
  • +
  • changeSorting('size_desc')} className={isActive('size_desc')}> + {gettext("Size")} +
  • +
  • changeSorting('type_asc')} className={isActive('type_asc')}> + {gettext("Type")} +
  • +
  • changeSorting('type_desc')} className={isActive('type_desc')}> + {gettext("Type")} +
  • +
    + ); } @@ -101,11 +98,91 @@ function MenuExtension(props) { } +function ExtraMenu(props) { + const { + settings, + columnRefs, + openUploader, + downloadFiles, + numSelectedFiles, + numSelectedInodes, + numClippedInodes, + currentFolderId, + copyInodes, + clearClipboard, + } = props; + + async function addFolder() { + const folderName = window.prompt("Enter folder name"); + if (!folderName) + return; + const addFolderUrl = `${settings.base_url}${settings.folder_id}/add_folder`; + const response = await fetch(addFolderUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': settings.csrf_token, + }, + body: JSON.stringify({ + name: folderName, + }), + }); + if (response.ok) { + const current = columnRefs[settings.folder_id].current; + const body = await response.json(); + current.setInodes([...current.inodes, body.new_folder]); // adds new folder to the end of the list + } else if (response.status === 409) { + alert(await response.text()); + } else { + console.error(response); + } + } + + function downloadSelectedFiles() { + const current = columnRefs[currentFolderId].current; + downloadFiles(current.inodes.filter(inode => !inode.is_folder && inode.selected)); + current.deselectinodes(); + } + + return ( + } tooltip={gettext("Extra options")}> +
  • + {gettext("Add new folder")} +
  • +
  • + {gettext("Download selected files")} +
  • +
  • + {gettext("Upload files from local host")} +
  • +
  • + {gettext("Copy selected to clipboard")} +
  • +
  • + {gettext("Clear clipboard")} +
  • + {settings.menu_extensions.map((extension, index) => ( + + ))} +
    + ); +} + + export const MenuBar = forwardRef((props: any, forwardedRef) => { - const {currentFolderId, columnRefs, folderTabsRef, openUploader, downloadFiles, layout, setLayout, setSearchResult, settings} = props; + const { + currentFolderId, + columnRefs, + folderTabsRef, + layout, + setLayout, + setSearchResult, + settings + } = props; const [numSelectedInodes, setNumSelectedInodes] = useState(0); const [numSelectedFiles, setNumSelectedFiles] = useState(0); const [clipboard, setClipboard] = useClipboard(); + //const [numClippedInodes, setNumClippedInodes] = useState(clipboard.length); useImperativeHandle(forwardedRef, () => ({ setSelected: (selectedInodes) => { @@ -145,24 +222,28 @@ export const MenuBar = forwardRef((props: any, forwardedRef) => { function clearClipboard() { setClipboard([]); + // setNumClippedInodes(0); } function selectAllInodes() { const current = columnRefs[currentFolderId].current; current.setInodes(current.inodes.map(inode => ({...inode, selected: true, copied: false}))); - setNumSelectedInodes(current.selectedInodes.length); - setNumSelectedFiles(current.selectedInodes.filter(inode => !inode.is_folder).length); + setNumSelectedInodes(current.inodes.length); + setNumSelectedFiles(current.inodes.filter(inode => !inode.is_folder).length); + clearClipboard(); } function copyInodes() { const current = columnRefs[currentFolderId].current; if (current.inodes.find(inode => inode.selected)) { - setClipboard(current.inodes.filter(inode => inode.selected).map(inode => ({ + const clipboard = current.inodes.filter(inode => inode.selected).map(inode => ({ id: inode.id, parent: inode.parent, selected: false, copied: true - }))); + })); + setClipboard(clipboard); + // setNumClippedInodes(clipboard.length); current.setInodes(current.inodes.map(inode => ({...inode, selected: false, copied: inode.selected}))); setNumSelectedInodes(0); setNumSelectedFiles(0); @@ -172,12 +253,14 @@ export const MenuBar = forwardRef((props: any, forwardedRef) => { function cutInodes() { const current = columnRefs[currentFolderId].current; if (current.inodes.find(inode => inode.selected)) { - setClipboard(current.inodes.filter(inode => inode.selected).map(inode => ({ + const clipboard = current.inodes.filter(inode => inode.selected).map(inode => ({ id: inode.id, parent: inode.parent, selected: false, cutted: true - }))); + })); + setClipboard(clipboard); + // setNumClippedInodes(clipboard.length); current.setInodes(current.inodes.map(inode => ({...inode, selected: false, cutted: inode.selected}))); setNumSelectedInodes(0); setNumSelectedFiles(0); @@ -246,38 +329,6 @@ export const MenuBar = forwardRef((props: any, forwardedRef) => { } } - async function addFolder() { - const folderName = window.prompt("Enter folder name"); - if (!folderName) - return; - const addFolderUrl = `${settings.base_url}${settings.folder_id}/add_folder`; - const response = await fetch(addFolderUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': settings.csrf_token, - }, - body: JSON.stringify({ - name: folderName, - }), - }); - if (response.ok) { - const current = columnRefs[settings.folder_id].current; - const body = await response.json(); - current.setInodes([...current.inodes, body.new_folder]); // adds new folder to the end of the list - } else if (response.status === 409) { - alert(await response.text()); - } else { - console.error(response); - } - } - - function downloadSelectedFiles() { - const current = columnRefs[currentFolderId].current; - downloadFiles(current.inodes.filter(inode => !inode.is_folder && inode.selected)); - current.deselectinodes(); - } - async function undoDiscardInodes() { const current = columnRefs[currentFolderId].current; const inodeIds = current.inodes.filter(inode => inode.selected).map(inode => inode.id); @@ -335,15 +386,16 @@ export const MenuBar = forwardRef((props: any, forwardedRef) => {
  • ) : (<> -
  • -
  • -
  • -
  • - {settings.menu_extensions.map((extension, index) => ( - - ))} + )} diff --git a/client/finder/Search.tsx b/client/finder/Search.tsx index 3a5231dfa..ac722d18d 100644 --- a/client/finder/Search.tsx +++ b/client/finder/Search.tsx @@ -1,5 +1,6 @@ import React, {useEffect, useRef, useState} from 'react'; import {useCookie} from './Storage'; +import DropDownMenu from './DropDownMenu'; import SearchIcon from 'icons/search.svg'; const useSearchRealm = initial => useCookie('django-finder-search-realm', initial); @@ -35,20 +36,9 @@ function useSearchParam(key) : [string, (value: string) => any] { export function SearchField(props) { const {columnRefs, setSearchResult, settings} = props; const searchRef = useRef(null); - const searchRealmRef = useRef(null); const [searchQuery, setSearchQuery] = useSearchParam('q'); const [searchRealm, setSearchRealm] = useSearchRealm('current'); - useEffect(() => { - const closeSearching = (event) => { - if (!searchRealmRef.current?.parentElement?.contains(event.target)) { - searchRealmRef.current.setAttribute('aria-expanded', 'false'); - } - } - window.addEventListener('click', closeSearching); - return () => window.removeEventListener('click', closeSearching); - }, []); - function handleSearch(event) { const performSearch = () => { setSearchQuery(searchRef.current.value); @@ -85,26 +75,29 @@ export function SearchField(props) { } } - function renderSearchRealmOptions() { - const isActive = (value) => searchRealm === value ? 'active' : null; - - return ( - - ) + function isActive(value) { + return searchRealm === value ? 'active' : null; } return (<>
    - - searchRealmRef.current.setAttribute('aria-expanded', searchRealmRef.current.ariaExpanded === 'true' ? 'false': 'true')} aria-haspopup="true" data-tooltip-id="django-finder-tooltip" data-tooltip-content={gettext("Restrict search")}> - {renderSearchRealmOptions()} - + + +
  • changeSearchRealm('current')} + className={isActive('current')}>{gettext("From current folder")} +
  • +
  • changeSearchRealm('everywhere')} + className={isActive('everywhere')}>{gettext("In all folders")} +
  • +
  • changeSearchRealm('filename')} + className={isActive('filename')}>{gettext("Filename only")} +
  • +
  • changeSearchRealm('content')} + className={isActive('content')}>{gettext("Also file content")} +
  • +
    - ); + +); } diff --git a/client/icons/add-folder.svg b/client/icons/add-folder.svg index d6efa0aee..6de3fbbf2 100644 --- a/client/icons/add-folder.svg +++ b/client/icons/add-folder.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file