ctrl/command+click on toggle-all will toggle each item

Massimo Melina committed Apr 20, 2023 at 23:07 UTC 70946b98182e7c0fd257f4c75d19f93d66b4537b
3 files changed +41 -18
README.md
+1
@@ -207,6 +207,7 @@ This is enough, but you may want to configure generated links accordingly:
207 - Appending `#LOGIN` to address will bring up the login dialog
208 - Appending ?lang=CODE to address will force a specific language
209 - env `SESSION_DURATION` can be set to any number of seconds. Default is 1 day.
210 +- right/ctrl/command click on toggle-all checkbox will invert each checkbox state
211
212 ## Contribute
213
frontend/src/FilterBar.ts
+22 -13
@@ -4,6 +4,7 @@ import { useDebounce } from 'usehooks-ts'
4 import { Checkbox } from './components'
5 import { useI18N } from './i18n'
6 import { usePath } from './useFetchList'
7 +import { with_ } from './misc'
8
9 export function FilterBar() {
10 const { list, filteredList, selected, patternFilter, showFilter } = useSnapState()
@@ -19,19 +20,13 @@ export function FilterBar() {
20 return h('div', { id: 'filter-bar', className: showFilter ? 'show-sliding' : 'before-sliding' },
21 h(Checkbox, {
22 value: all,
22 - onChange(){
23 - const will = !all
24 - const sel = state.selected
25 - for (const { uri } of state.filteredList || state.list) {
26 - const was = sel[uri]
27 - if (was === will) continue
28 - if (was)
29 - delete sel[uri]
30 - else
31 - sel[uri] = true
32 - }
33 -
34 - setAll(will)
23 + onContextMenu(ev) {
24 + ev.preventDefault()
25 + select(undefined)
26 + },
27 + onChange(v, ev){
28 + const toggle = with_(ev.nativeEvent as any, v => v.ctrlKey || v.metaKey)
29 + select(toggle ? undefined : v)
30 },
31 }),
32 h('input', {
@@ -49,4 +44,18 @@ export function FilterBar() {
44 fil !== undefined && fil < list.length && t('filter_count', {n:fil}, "{n} filtered"),
45 ].filter(Boolean).join(', ') ),
46 )
47 +
48 + function select(will: boolean | undefined) {
49 + const sel = state.selected
50 + for (const { uri } of state.filteredList || state.list) {
51 + const was = sel[uri] || false
52 + if (was === will) continue
53 + if (was)
54 + delete sel[uri]
55 + else
56 + sel[uri] = true
57 + }
58 + if (will !== undefined)
59 + setAll(will)
60 + }
61 }
\ No newline at end of file
frontend/src/components.ts
+18 -5
@@ -1,7 +1,16 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { getHFS, hfsEvent, hIcon } from './misc'
4 -import { createElement as h, Fragment, HTMLAttributes, isValidElement, ReactNode, useMemo } from 'react'
4 +import {
5 + ChangeEvent,
6 + createElement as h,
7 + Fragment,
8 + HTMLAttributes,
9 + InputHTMLAttributes,
10 + isValidElement,
11 + ReactNode,
12 + useMemo
13 +} from 'react'
14
15 export function Spinner(props: any) {
16 return hIcon('spinner', { className:'spinner', ...props })
@@ -23,11 +32,15 @@ export function FlexV(props:any) {
32 return h(Flex, { vert:true, ...props })
33 }
34
26 -interface CheckboxProps { children?:ReactNode, value:any, onChange?:(v:boolean)=>void }
27 -export function Checkbox({ onChange, value, children, ...props }:CheckboxProps) {
28 - const ret = h('input',{
35 +interface CheckboxProps extends Omit<Partial<InputHTMLAttributes<any>>, 'onChange'> {
36 + children?: ReactNode,
37 + value: any,
38 + onChange?: (v: boolean, ev: ChangeEvent) => void
39 +}
40 +export function Checkbox({ onChange, value, children, ...props }: CheckboxProps) {
41 + const ret = h('input', {
42 type: 'checkbox',
30 - onChange: ev => onChange?.(Boolean(ev.target.checked)),
43 + onChange: ev => onChange?.(Boolean(ev.target.checked), ev),
44 checked: Boolean(value),
45 value: 1,
46 ...props