fix: file picker dialog didn't fit smaller screens

Massimo Melina committed Jun 15, 2022 at 20:59 UTC 5bc8e1ade0b12a5474cc6a6a269b9f30ebb7c24b
5 files changed +50 -49
admin/src/FileField.ts
+24 -22
@@ -2,12 +2,13 @@ import { FieldProps, StringField } from '@hfs/mui-grid-form'
2 import { createElement as h } from 'react'
3 import { InputAdornment } from '@mui/material'
4 import { Eject } from '@mui/icons-material'
5 -import { IconBtn } from './misc'
5 +import { IconBtn, useBreakpoint } from './misc'
6 import { newDialog } from '@hfs/shared'
7 import FilePicker from './FilePicker'
8 import { apiCall } from './api'
9
10 export default function FileField({ value, onChange, files=true, folders=false, fileMask, defaultPath, title, ...props }: FieldProps<string>) {
11 + const large = useBreakpoint('md')
12 return h(StringField, {
13 ...props,
14 value,
@@ -21,28 +22,29 @@ export default function FileField({ value, onChange, files=true, folders=false,
22 onClick() {
23 const close = newDialog({
24 title: title ?? (files ? "Pick a file" : "Pick a folder"),
24 - dialogProps: { sx:{ minWidth:'min(90vw, 40em)', minHeight: 'calc(100vh - 9em)' } },
25 - Content,
25 + dialogProps: {
26 + fullScreen: !large,
27 + sx: { minWidth: 'min(90vw, 40em)', minHeight: 'calc(100vh - 9em)' }
28 + },
29 + Content() {
30 + return h(FilePicker, {
31 + multiple: false,
32 + folders,
33 + files,
34 + fileMask,
35 + from: value || defaultPath,
36 + async onSelect(sel) {
37 + let one = sel?.[0]
38 + if (!one) return
39 + const cwd = (await apiCall('get_cwd'))?.path
40 + if (one.startsWith(cwd))
41 + one = one.slice(cwd.length+1)
42 + onChange(one, { was: value, event: 'picker' })
43 + close()
44 + }
45 + })
46 + },
47 })
27 -
28 - function Content() {
29 - return h(FilePicker, {
30 - multiple: false,
31 - folders,
32 - files,
33 - fileMask,
34 - from: value || defaultPath,
35 - async onSelect(sel) {
36 - let one = sel?.[0]
37 - if (!one) return
38 - const cwd = (await apiCall('get_cwd'))?.path
39 - if (one.startsWith(cwd))
40 - one = one.slice(cwd.length+1)
41 - onChange(one, { was: value, event: 'picker' })
42 - close()
43 - }
44 - })
45 - }
48 },
49 }))
50 }
admin/src/FilePicker.ts
+2 -3
@@ -90,7 +90,7 @@ export default function FilePicker({ onSelect, multiple=true, files=true, folder
90 const it: DirEntry = filteredList[index]
91 const isFolder = it.k === 'd'
92 return h(MenuItem, {
93 - style,
93 + style: { ...style, padding: 0 },
94 key: it.n,
95 onClick() {
96 if (isFolder)
@@ -136,8 +136,7 @@ export default function FilePicker({ onSelect, multiple=true, files=true, folder
136 onChange(ev) {
137 setFilterBounced(ev.target.value)
138 },
139 - sx: { minWidth: '20em' },
140 - fullWidth: true,
139 + sx: { flex: 1 },
140 }),
141 ),
142 )
admin/src/addFiles.ts
+19 -22
@@ -11,29 +11,26 @@ import { onlyTruthy } from './misc'
11
12 export default function addFiles() {
13 const close = newDialog({
14 - title: 'Add files or folders',
15 - dialogProps: { sx:{ minWidth:'min(90vw, 40em)', minHeight: 'calc(100vh - 9em)' } },
16 - Content,
14 + title: "Add files or folders",
15 + dialogProps: { sx:{ minWidth: 'min(90vw, 40em)', minHeight: 'calc(100vh - 9em)' } },
16 + Content() {
17 + const under = getUnder()
18 + return h(Fragment, {},
19 + h(Box, { sx:{ typography: 'body1', px: 1, py: 2 } }, "Selected elements will be added to " + (under || '(home)')),
20 + h(FilePicker, {
21 + async onSelect(sel) {
22 + let failed = await Promise.all(sel.map(source =>
23 + apiCall('add_vfs', { under, source }).then(() => '', () => source) ))
24 + failed = onlyTruthy(failed)
25 + if (failed.length)
26 + await alertDialog("Some elements have been rejected: "+failed.join(', '), 'error')
27 + reloadVfs()
28 + close()
29 + }
30 + })
31 + )
32 + }
33 })
18 -
19 - function Content() {
20 - const under = getUnder()
21 - return h(Fragment, {},
22 - h(Box, { sx:{ typography: 'body1', px: 1, py: 2 } }, "Selected elements will be added to " + (under || '(home)')),
23 - h(FilePicker, {
24 - async onSelect(sel) {
25 - let failed = await Promise.all(sel.map(source =>
26 - apiCall('add_vfs', { under, source }).then(() => '', () => source) ))
27 - failed = onlyTruthy(failed)
28 - if (failed.length)
29 - await alertDialog("Some elements have been rejected: "+failed.join(', '), 'error')
30 - reloadVfs()
31 - close()
32 - }
33 - })
34 - )
35 - }
36 -
34 }
35
36 export async function addVirtual() {
admin/src/dialog.ts
+2
@@ -21,6 +21,7 @@ import {
21 import { Check, Close, Error as ErrorIcon, Forward, Info, Warning } from '@mui/icons-material'
22 import { newDialog, closeDialog, dialogsDefaults, DialogOptions } from '@hfs/shared'
23 import { Form, FormProps } from '@hfs/mui-grid-form'
24 +import { useBreakpoint } from './misc'
25 export * from '@hfs/shared/lib/dialogs'
26
27 dialogsDefaults.Container = function Container(d:DialogOptions) {
@@ -34,6 +35,7 @@ dialogsDefaults.Container = function Container(d:DialogOptions) {
35 return h(MuiDialog, {
36 open: true,
37 maxWidth: 'lg',
38 + fullScreen: !useBreakpoint('sm'),
39 ...rest,
40 ...root,
41 onClose: ()=> closeDialog(),
admin/src/misc.ts
+3 -2
@@ -123,8 +123,9 @@ export function xlate(input: any, table: Record<string, any>) {
123 return table[input] ?? input
124 }
125
126 -export function useBreakpoint(name: Breakpoint) {
127 - return useMediaQuery((theme: any) => theme.breakpoints.up(name), { noSsr:true }) // without noSsr, first execution always returns false
126 +// return true if same size or larger
127 +export function useBreakpoint(breakpoint: Breakpoint) {
128 + return useMediaQuery((theme: any) => theme.breakpoints.up(breakpoint), { noSsr:true }) // without noSsr, first execution always returns false
129 }
130
131 export function err2msg(code: string) {