admin/fs: link
Massimo Melina committed
Feb 23, 2023 at 09:26 UTC
5296d6feac95324eb733c2ce1c098dcf4938bad9
3 files changed
+93
-14
admin/src/FileForm.ts
+85
-5
@@ -2,21 +2,38 @@
2
3
import { state } from './state'
4
import { createElement as h, ReactNode, useEffect, useMemo, useState } from 'react'
5
-import { Alert } from '@mui/material'
6
-import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, SelectField } from '@hfs/mui-grid-form'
5
+import { Alert, Box, MenuItem, MenuList, } from '@mui/material'
6
+import {
7
+ BoolField,
8
+ DisplayField,
9
+ Field,
10
+ FieldProps,
11
+ Form,
12
+ MultiSelectField,
13
+ SelectField,
14
+ StringField
15
+} from '@hfs/mui-grid-form'
16
import { apiCall, useApiEx } from './api'
8
-import { formatBytes, IconBtn, isEqualLax, modifiedSx, onlyTruthy } from './misc'
17
+import { formatBytes, IconBtn, isEqualLax, modifiedSx, newDialog, onlyTruthy } from './misc'
18
import { reloadVfs, VfsNode, VfsPerms, Who } from './VfsPage'
19
import md from './md'
20
import _ from 'lodash'
21
import FileField from './FileField'
22
import { alertDialog, useDialogBarColors } from './dialog'
23
import yaml from 'yaml'
15
-import { Delete } from '@mui/icons-material'
24
+import { ContentCopy, Delete, Edit } from '@mui/icons-material'
25
26
interface Account { username: string }
27
19
-export default function FileForm({ file, anyMask, defaultPerms, addToBar }: { file: VfsNode, anyMask?: boolean, defaultPerms: VfsPerms, addToBar?: ReactNode }) {
28
+interface FileFormProps {
29
+ file: VfsNode
30
+ anyMask?: boolean
31
+ defaultPerms: VfsPerms
32
+ addToBar?: ReactNode
33
+ urls: string[] | false
34
+}
35
+
36
+export default function FileForm({ file, anyMask, defaultPerms, addToBar, urls }: FileFormProps) {
37
const { parent, children, isRoot, ...rest } = file
38
const [values, setValues] = useState(rest)
39
useEffect(() => {
@@ -49,6 +66,7 @@ export default function FileForm({ file, anyMask, defaultPerms, addToBar }: { fi
66
return h(Form, {
67
values,
68
set(v, k) {
69
+ if (k === 'link') return
70
setValues({ ...values, [k]: v })
71
},
72
barSx: { gap: 2, width: '100%', ...barColors },
@@ -81,6 +99,7 @@ export default function FileForm({ file, anyMask, defaultPerms, addToBar }: { fi
99
fields: [
100
isRoot ? h(Alert,{ severity: 'info' }, "This is Home, the root of your shared files. Options set here will be applied to all files.")
101
: { k: 'name', required: true, helperText: source && "You can decide a name that's different from the one on your disk" },
102
+ { k: 'id', comp: LinkField, urls },
103
{ k: 'source', label: "Source on disk", comp: FileField, files: !isDir, folders: isDir, multiline: true,
104
placeholder: "Not on disk, this is a virtual folder",
105
},
@@ -155,3 +174,64 @@ function who2desc(who: any) {
174
: Array.isArray(who) ? who.join(', ')
175
: "*UNKNOWN*" + JSON.stringify(who)
176
}
177
+
178
+interface LinkFieldProps extends FieldProps<string> {
179
+ urls: string[]
180
+}
181
+function LinkField({ value, urls, }: LinkFieldProps) {
182
+ const { data, error, reload } = useApiEx('get_config', { only: ['base_url'] })
183
+ const base = data?.base_url
184
+ const link = (base || (urls ? urls[0] : '')) + encodeURI(value||'')
185
+ return h(Box, { display: 'flex', },
186
+ h(DisplayField, {
187
+ label: "Link", value: link,
188
+ error,
189
+ end: h(Box, {},
190
+ h(IconBtn, {
191
+ icon: ContentCopy,
192
+ title: "Copy",
193
+ onClick: () => navigator.clipboard.writeText(link)
194
+ }),
195
+ h(IconBtn, {
196
+ icon: Edit,
197
+ title: "Change",
198
+ onClick: edit,
199
+ }),
200
+ )
201
+ }),
202
+ )
203
+
204
+ function edit() {
205
+ const proto = new URL(urls[0]).protocol + '//'
206
+ newDialog({
207
+ title: "Change link",
208
+ onClose: reload,
209
+ Content() {
210
+ const [v, setV] = useState(base)
211
+ return h(Box, { display: 'flex', flexDirection: 'column' },
212
+ h(Box, { mb: 2 }, "You can choose a different base address for your links"),
213
+ h(MenuList, {},
214
+ urls.map(u => h(MenuItem, {
215
+ key: u,
216
+ selected: u === v,
217
+ onClick: () => set(u),
218
+ }, u))
219
+ ),
220
+ h(StringField, {
221
+ label: "Custom address",
222
+ helperText: "Use this field if you need to enter a different address",
223
+ value: urls.includes(v) ? '' : v.slice(proto.length),
224
+ onChange: v => set(proto + v),
225
+ start: proto,
226
+ sx: { mt: 2 }
227
+ }),
228
+ )
229
+
230
+ async function set(u: string) {
231
+ await apiCall('set_config', { values: { base_url: u } })
232
+ setV(u)
233
+ }
234
+ }
235
+ })
236
+ }
237
+}
\ No newline at end of file
admin/src/VfsPage.ts
+8
-8
@@ -38,6 +38,13 @@ export default function VfsPage() {
38
}, [vfs])
39
const sideBreakpoint = 'md'
40
const isSideBreakpoint = useBreakpoint(sideBreakpoint)
41
+ const [status] = useApi(window.location.host === 'localhost' && 'get_status')
42
+ const urls = useMemo(() =>
43
+ typeof status === 'object' && _.sortBy(
44
+ Object.values(status.urls?.https || status.urls?.http || {}) as string[],
45
+ url => url.includes('[')
46
+ ),
47
+ [status])
48
49
function close() {
50
state.selectedFiles = []
@@ -52,6 +59,7 @@ export default function VfsPage() {
59
}),
60
defaultPerms: data?.defaultPerms as VfsPerms,
61
anyMask,
62
+ urls,
63
file: selectedFiles[0] as VfsNode // it's actually Snapshot<VfsNode> but it's easier this way
64
})
65
: h(Fragment, {},
@@ -101,14 +109,6 @@ export default function VfsPage() {
109
}
110
111
}, [data, id2node])
104
- const [status] = useApi(window.location.host === 'localhost' && 'get_status')
105
- const urls = useMemo(() =>
106
- typeof status === 'object'
107
- && _.sortBy(
108
- onlyTruthy(Object.values(status.urls?.https || status.urls?.http || {}).map(u => typeof u === 'string' && u)),
109
- url => url.includes('[')
110
- ),
111
- [status])
112
if (element) {
113
id2node.clear()
114
return element
todo.md
-1
@@ -1,7 +1,6 @@
1
# To do
2
- blacklist of plugins (as a temporary measure until GitHub's intervention)
3
- admin: warn in case of items with same name
4
-- admin/fs: button "copy url to clipboard"
4
- frontend search supporting masks
5
- plugin: list of IPs seen
6
- admin/fs: check if source exists when set