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 (
-
-
-
- - 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")}
-
-
- )
+ } 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 (
-
- - 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")}
-
- )
+ 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 @@
-