frontend: "select all" button

Massimo Melina committed Mar 18, 2022 at 20:17 UTC f60c7880a13c01cf716228ed3efda27c4e8ee731
5 files changed +38 -22
frontend/src/BrowseFiles.ts
+1 -5
@@ -2,7 +2,7 @@
2
3 import { Link, useLocation } from 'react-router-dom'
4 import { createElement as h, Fragment, memo, useEffect, useMemo, useState } from 'react'
5 -import { formatBytes, hError, hfsEvent, hIcon } from './misc'
5 +import { formatBytes, hError, hfsEvent, hIcon, isMobile } from './misc'
6 import { Checkbox, Html, Spinner } from './components'
7 import { Head } from './Head'
8 import { state, useSnapState } from './state'
@@ -85,10 +85,6 @@ function useMidnight() {
85 }
86 }
87
88 -function isMobile() {
89 - return window.innerWidth < 800
90 -}
91 -
88 const Entry = memo(function(entry: DirEntry & { midnight: Date }) {
89 let { n: relativePath, isFolder } = entry
90 const base = usePath()
frontend/src/icons.ts
+1
@@ -22,6 +22,7 @@ const SYS_ICONS = {
22 download: ':📥',
23 invert: 'retweet:🙃',
24 admin: 'crown:👑',
25 + check: ':✔️',
26 }
27
28 document.fonts.ready.then(async ()=> {
frontend/src/index.scss
+10 -4
@@ -155,6 +155,7 @@ header input {
155 #filter-bar {
156 display: flex;
157 gap: .3em;
158 + margin: 0.5em 0;
159 & input { flex: 1 }
160 & button {
161 padding: 0 0.5em;
@@ -206,6 +207,11 @@ ul.dir {
207 margin: 0.1em;
208 }
209 }
210 +#menu-bar {
211 + display: flex;
212 + justify-content: space-evenly;
213 + flex-wrap: wrap;
214 +}
215 #searched {
216 margin: .2em;
217 }
@@ -264,14 +270,14 @@ button label {
270 border: 1px solid var(--faint-contrast)
271 }
272
267 -@media (max-width: 40em) {
273 +@media (max-width: 42em) {
274 body, button, select { font-size: 14pt; }
275 #menu-bar {
270 - display: flex;
271 - justify-content: space-between;
272 - align-content: stretch;
276 & button label { display: none } /* icons only */
277 }
278 + #filter-bar {
279 + margin: 0.2em 0;
280 + }
281 #filter-bar label { display:none }
282 #filter-bar button { /* make it same size of top bar */
283 width: 17.6vw;
frontend/src/menu.ts
+22 -13
@@ -4,7 +4,7 @@ import { state, useSnapState } from './state'
4 import { createElement as h, useEffect, useState } from 'react'
5 import { useDebounce } from 'use-debounce'
6 import { confirmDialog, promptDialog } from './dialog'
7 -import { hIcon, prefix } from './misc'
7 +import { hIcon, isMobile, prefix } from './misc'
8 import { login } from './login'
9 import { showOptions } from './options'
10 import showUserPanel from './UserPanel'
@@ -34,7 +34,7 @@ export function MenuPanel() {
34 h(LoginButton),
35 h(MenuButton, {
36 icon: 'filter',
37 - label: 'Filter',
37 + label: "Filter list",
38 toggled: showFilter,
39 onClick() {
40 state.showFilter = !showFilter
@@ -62,7 +62,7 @@ export function MenuPanel() {
62 showFilter && h('div', { id: 'filter-bar' },
63 h('input', {
64 id: 'filter',
65 - placeholder: 'Filter list',
65 + placeholder: "Type here to filter the list below",
66 autoComplete: 'off',
67 value: filter,
68 autoFocus: true,
@@ -70,21 +70,30 @@ export function MenuPanel() {
70 setFilter(ev.target.value)
71 }
72 }),
73 + !isMobile() && h(MenuButton, {
74 + icon: 'check',
75 + label: "Select all",
76 + onClick() { workSel(() => true) }
77 + }),
78 h(MenuButton, {
79 icon: 'invert',
80 label: 'Invert selection',
76 - onClick() {
77 - const sel = state.selected
78 - for (const { n } of state.filteredList || state.list)
79 - if (sel[n])
80 - delete sel[n]
81 - else
82 - sel[n] = true
83 - }
84 - })
81 + onClick() { workSel(x => !x) }
82 + }),
83 )
84 )
85
86 + function workSel(cb: (b:boolean) => boolean) {
87 + const sel = state.selected
88 + for (const { n } of state.filteredList || state.list) {
89 + const was = sel[n]
90 + if (was !== cb(was))
91 + if (was)
92 + delete sel[n]
93 + else
94 + sel[n] = true
95 + }
96 + }
97
98 function getSearchProps() {
99 return stopSearch && started1secAgo ? {
@@ -103,7 +112,7 @@ export function MenuPanel() {
112 }
113 } : {
114 icon: 'search',
106 - label: 'Search',
115 + label: "Search deep",
116 async onClick() {
117 state.remoteSearch = await promptDialog('Search for...') || ''
118 }
frontend/src/misc.ts
+4
@@ -16,6 +16,10 @@ export function hError(err: Error | string | null) {
16 return err && h('div', { className:'error-msg' }, typeof err === 'string' ? err : err.message)
17 }
18
19 +export function isMobile() {
20 + return window.innerWidth < 800
21 +}
22 +
23 let isWorking = false // we want the 'working' thing to be singleton
24 export function working() {
25 if (isWorking)