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