plugins: config.type=real_path now have defaultPath and fileMask options

Massimo Melina committed Jun 5, 2022 at 15:32 UTC f2044af8ab9d9ac2632beb23211d2369e9a94881
6 files changed +18 -14
admin/src/FileField.ts
+3 -2
@@ -7,7 +7,7 @@ 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, title, ...props }: FieldProps<string>) {
10 +export default function FileField({ value, onChange, files=true, folders=false, fileMask, defaultPath, title, ...props }: FieldProps<string>) {
11 return h(StringField, {
12 ...props,
13 value,
@@ -30,7 +30,8 @@ export default function FileField({ value, onChange, files=true, folders=false,
30 multiple: false,
31 folders,
32 files,
33 - from: value,
33 + fileMask,
34 + from: value || defaultPath,
35 async onSelect(sel) {
36 let one = sel?.[0]
37 if (!one) return
admin/src/FilePicker.ts
+3 -2
@@ -29,8 +29,9 @@ interface FilePickerProps {
29 from?: string
30 folders?: boolean
31 files?: boolean
32 + fileMask?: string
33 }
33 -export default function FilePicker({ onSelect, multiple=true, files=true, folders=true, from='' }: FilePickerProps) {
34 +export default function FilePicker({ onSelect, multiple=true, files=true, folders=true, fileMask, from='' }: FilePickerProps) {
35 const [cwd, setCwd] = useState(from)
36 const [ready, setReady] = useState(false)
37 useEffect(() => {
@@ -40,7 +41,7 @@ export default function FilePicker({ onSelect, multiple=true, files=true, folder
41 setReady(true)
42 })
43 }, [from])
43 - const { list, error, loading } = useApiList<DirEntry>(ready && 'ls', { path: cwd, files })
44 + const { list, error, loading } = useApiList<DirEntry>(ready && 'ls', { path: cwd, files, fileMask })
45 useEffect(() => {
46 setSel([])
47 setFilter('')
dev-plugins.md
+4 -2
@@ -87,8 +87,10 @@ Based on `type`, other properties are supported:
87 - `options: { [label]: AnyJsonValue }`
88 - `multiselect` it's like `select` but its result is an array of values.
89 - `real_path` path to server disk
90 - - `files` allow to select a file. Default is `true`.
91 - - `folders` allow to select a folder. Default is `false`.
90 + - `files: boolean` allow to select a file. Default is `true`.
91 + - `folders: boolean` allow to select a folder. Default is `false`.
92 + - `defaultPath: string` what path to start from if no value is set. E.g. __dirname if you want to start with your plugin's folder.
93 + - `fileMask: string` restrict files that are displayed. E.g. `*.jpg|*.png`
94
95 ## api object
96
server/src/api.vfs.ts
+6 -5
@@ -9,6 +9,7 @@ import { dirStream, enforceFinal, isWindowsDrive, objSameKeys } from './misc'
9 import { exec } from 'child_process'
10 import { promisify } from 'util'
11 import { FORBIDDEN, IS_WINDOWS } from './const'
12 +import { isMatch } from 'micromatch'
13
14 type VfsAdmin = {
15 type?: string,
@@ -120,7 +121,7 @@ const apis: ApiHandlers = {
121 return { path }
122 },
123
123 - async *ls({ path, files=true }, ctx) {
124 + async *ls({ path, files=true, fileMask }, ctx) {
125 if (!path && IS_WINDOWS) {
126 try {
127 for (const n of await getDrives())
@@ -138,10 +139,10 @@ const apis: ApiHandlers = {
139 if (ctx.req.aborted)
140 return
141 try {
141 - const full = join(path, name)
142 - const stats = await stat(full)
143 - if (!files && stats.isFile())
144 - continue
142 + const stats = await stat(join(path, name))
143 + if (stats.isFile())
144 + if (!files || fileMask && !isMatch(name, fileMask))
145 + continue
146 yield {
147 add: {
148 n: name,
server/src/listen.ts
-1
@@ -132,7 +132,6 @@ function startServer(srv: typeof httpSrv, { port, host }: StartServer) {
132 const { code } = e as any
133 if (code === 'EADDRINUSE') {
134 srv.busy = findProcess('port', port).then(res => res?.[0]?.name || '', () => '')
135 - console.debug("PROMISE")
135 srv.error = `port ${port} busy: ${await srv.busy || "unknown process"}`
136 }
137 console.error(srv.name, srv.error)
server/src/serveFile.ts
+2 -2
@@ -7,7 +7,7 @@ import { FORBIDDEN, METHOD_NOT_ALLOWED, NO_CONTENT } from './const'
7 import { getNodeName, MIME_AUTO, VfsNode } from './vfs'
8 import mimetypes from 'mime-types'
9 import { defineConfig } from './config'
10 -import mm, { isMatch } from 'micromatch'
10 +import { isMatch } from 'micromatch'
11 import _ from 'lodash'
12 import path from 'path'
13 import { promisify } from 'util'
@@ -48,7 +48,7 @@ export function serveFile(source:string, mime?:string, modifier?:(s:string)=>str
48 const { range } = ctx.request.header
49 ctx.set('Accept-Ranges', 'bytes')
50 const fn = path.basename(source)
51 - mime = mime ?? _.find(mimeCfg.get(), (v,k) => k>'' && mm.isMatch(fn, k)) // isMatch throws on an empty string
51 + mime = mime ?? _.find(mimeCfg.get(), (v,k) => k>'' && isMatch(fn, k)) // isMatch throws on an empty string
52 if (mime === MIME_AUTO)
53 mime = mimetypes.lookup(source) || ''
54 if (mime)