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)