@samitouri / QOSami-HFS / commits / f9c50b1c

plugins: new vfs_path api

Massimo Melina committed Jan 31, 2025 at 00:31 UTC f9c50b1c88f5792a37eaad7d089a67087a364aba
7 files changed +34 -12
admin/src/AccountForm.ts
+1 -1
@@ -92,7 +92,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
92 helperText: "When expired, login won't be allowed" },
93 { k: 'days_to_live', xs: 12, sm: 6, comp: NumberField, disabled: expired, step: 'any', min: 1/1000, // 10 minutes
94 helperText: "Used to set expiration on first login" + (expired ? " (already expired)" : '') },
95 - { k: 'redirect', comp: VfsPathField, onlyFolders: false, placeholder: "no",
95 + { k: 'redirect', comp: VfsPathField, placeholder: "no",
96 helperText: "If you want this account to be redirected to a specific folder/address (or even file) at login time" },
97 ],
98 onError: alertDialog,
admin/src/InternetPage.ts
+1 -1
@@ -274,7 +274,7 @@ export default function InternetPage({ setTitleSide }: PageProps) {
274 comp: ArrayField,
275 fields: [
276 { k: 'host', label: "Domain/Host", helperText: "Wildcards supported: *.domain.com|other.com" },
277 - { k: 'root', label: "Home/Root", comp: VfsPathField, placeholder: "default", helperText: "Root path in VFS",
277 + { k: 'root', label: "Home/Root", comp: VfsPathField, files: false, placeholder: "default", helperText: "Root path in VFS",
278 $column: { renderCell({ value }: any) { return value || h('i', {}, 'default') } } },
279 ],
280 toField: x => Object.entries(x || {}).map(([host,root]) => ({ host, root })),
admin/src/VfsMenuBar.ts
+1 -1
@@ -57,7 +57,7 @@ function SystemIntegrationButton({ platform }: { platform: string | undefined })
57 h(Box, {}, "It will also automatically copy the URL, ready to paste!")),
58 )
59 const parent = await promptDialog(msg, {
60 - field: { comp: VfsPathField, label: "Add to this folder", placeholder: "home",
60 + field: { comp: VfsPathField, files: false, label: "Add to this folder", placeholder: "home",
61 autoFocus: sm }, // this dialog is tall, and mobile keyboard will disrupt user's ability to view its content
62 form: { saveOnEnter: false }
63 })
admin/src/VfsPathField.ts
+18 -3
@@ -8,16 +8,31 @@ interface VfsPathFieldProps extends FieldProps<string> {
8 autocompleteProps: Partial<AutocompleteProps<string, false, true, undefined>>
9 }
10
11 -export default function VfsPathField({ value='', onChange, helperText, setApi, autocompleteProps, onlyFolders=true, ...props }: VfsPathFieldProps) {
11 +export default function VfsPathField({ value='', onChange, helperText, setApi, autocompleteProps, folders=true, files=true, ...props }: VfsPathFieldProps) {
12 const uri = dirname(value)
13 - const { list, loading } = useApiList('get_file_list', { uri, admin: true, onlyFolders })
14 - const options = useMemo(() => [uri + '/'].concat(list.map(x => value + x.n)), [list, uri])
13 + const { list, loading } = useApiList('get_file_list', {
14 + uri,
15 + admin: true,
16 + fileMask: typeof files === 'string' ? files : undefined,
17 + onlyFolders: !files
18 + })
19 + const options = useMemo(() => {
20 + const ret = [uri && (dirname(uri) + '/'), uri + '/'].filter(Boolean).concat(list.map(x => uri + '/' + x.n))
21 + if (value && !ret.includes(value)) ret.push(value) // console warning otherwise
22 + return ret
23 + }, [list, uri])
24 + setApi?.({
25 + getError() {
26 + return !folders && value?.endsWith('/') && "must be a file" || false
27 + }
28 + })
29 return h(Autocomplete<string, false, true, undefined>, {
30 value,
31 options,
32 isOptionEqualToValue: (o,v) => o === v || o === v + '/',
33 loading,
34 disableClearable: true,
35 + disableCloseOnSelect: true,
36 renderInput: params => h(TextField, {
37 helperText,
38 onChange(event) {
dev-plugins.md
+6 -1
@@ -192,6 +192,10 @@ Based on `type`, other properties are supported:
192 - `folders: boolean` allow to select a folder. Default is `false`.
193 - `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.
194 - `fileMask: string` restrict files that are displayed. E.g. `*.jpg|*.png`
195 +- `vfs_path` path to VFS
196 + - `folders: boolean` set false to forbid selection of folders. Default is true.
197 + - `files: boolean | string` set force to forbid selection of files. If you set a string, it will be used as a file-mask.
198 + E.g. `*.jpg|*.png` Default is true.
199 - `username`
200 - `groups: undefined | boolean` true if you want only groups, false if you want only users. Default is undefined.
201 - `multiple: boolean` if you set this to true, the field will allow the selection of multiple accounts,
@@ -729,9 +733,10 @@ If you want to override a text regardless of the language, use the special langu
733
734 ## API version history
735
732 -- 11.1 (v0.56.0)
736 +- 11.2 (v0.56.0)
737 - api.setError
738 - frontend events: afterBreadcrumbs, afterFolderStats, afterFilter
739 + - config.type.vfs_path: folders, files
740 - 10.3 (v0.55.0)
741 - HFS.copyTextToClipboard
742 - HFS.urlParams
src/api.get_file_list.ts
+6 -4
@@ -19,11 +19,13 @@ import { SendListReadable } from './SendList'
19
20 export interface DirEntry { n:string, s?:number, m?:Date, c?:Date, p?: string, comment?: string, web?: boolean, url?: string, target?: string, icon?: string | true, order?: number }
21
22 -export function paramsToFilter({ search, wild, searchComment }: any) {
22 +export function paramsToFilter({ search, wild, searchComment, fileMask }: any) {
23 search = String(search || '').toLocaleLowerCase()
24 searchComment = String(searchComment || '').toLocaleLowerCase()
25 return {
26 + depth: search || searchComment ? Infinity : 0,
27 filterName: search > '' && (wild === 'no' ? (s: string) => s.includes(search) : pattern2filter(search)),
28 + fileMask: fileMask > '' && pattern2filter(fileMask),
29 filterComment: searchComment > '' && (wild === 'no' ? (s: string) => s.includes(searchComment) : pattern2filter(searchComment))
30 }
31 }
@@ -41,8 +43,8 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, c, onl
43 return fail()
44 offset = Number(offset)
45 limit = Number(limit)
44 - const { filterName, filterComment } = paramsToFilter(rest)
45 - const walker = walkNode(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth: filterName || filterComment ? Infinity : 0 })
46 + const { filterName, filterComment, fileMask, depth } = paramsToFilter(rest)
47 + const walker = walkNode(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth })
48 const onDirEntryHandlers = mapPlugins(plug => plug.onDirEntry)
49 const can_upload = admin || hasPermission(node, 'can_upload', ctx)
50 const fakeChild = await applyParentToChild({ source: 'dummy-file' }, node) // used to check permission; simple but but can produce false results
@@ -75,7 +77,7 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, c, onl
77 for await (const sub of walker) {
78 let name = getNodeName(sub)
79 name = basename(name) || name // on windows, basename('C:') === ''
78 - if (filterName && !filterName(name)
80 + if (filterName && !filterName(name) || fileMask && !await nodeIsDirectory(sub) && !fileMask(name)
81 || filterComment && !filterComment(await getCommentFor(sub.source) || ''))
82 continue
83 const entry = await nodeToDirEntry(ctx, sub)
src/const.ts
+1 -1
@@ -9,7 +9,7 @@ import { formatTimestamp } from './cross'
9 import { argv } from './argv'
10 export * from './cross-const'
11
12 -export const API_VERSION = 11.1
12 +export const API_VERSION = 11.2
13 export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
14
15 // you can add arguments with this file, currently used for the update process on mac/linux