file menu optional
Massimo Melina committed
Mar 25, 2023 at 23:05 UTC
05b4f804104aaa9e2f88b2343a4dad2e672dc34d
5 files changed
+70
-18
admin/src/OptionsPage.ts
+8
-3
@@ -107,18 +107,23 @@ export default function OptionsPage() {
107
values.https_port >= 0 && { k: 'private_key', comp: FileField, label: "HTTPS private key file",
108
...with_(status?.https.error, e => isKeyError(e) ? { error: true, helperText: e } : null)
109
},
110
- { k: 'open_browser_at_start', comp: BoolField, label: "Open Admin-panel at start", helperText: "Browser is automatically launched with HFS" },
111
- { k: 'localhost_admin', comp: BoolField, label: "Admin access for localhost connections",
110
+ { k: 'open_browser_at_start', comp: BoolField, label: "Open Admin-panel at start", md: 4,
111
+ helperText: "Browser is automatically launched with HFS"
112
+ },
113
+ { k: 'localhost_admin', comp: BoolField, label: "Admin access for localhost connections", md: 5,
114
getError: x => !x && admins?.length===0 && "First create at least one admin account",
115
helperText: "To access Admin without entering credentials"
116
},
117
+ { k: 'file_menu_on_link', comp: SelectField, label: "Access file menu", sm: 12, md: 3,
118
+ options: { "by clicking on file name": true, "by dedicated button": false }
119
+ },
120
{ k: 'title', helperText: "You can see this in the tab of your browser" },
121
{ k: 'favicon', comp: FileField, placeholder: "None", fileMask: '*.png|*.ico|*.jpg|*.jpeg|*.gif|*.svg',
122
helperText: "The icon associated to your website" },
123
{ k: 'log', label: logLabels.log, md: 3, helperText: "Requests are logged here" },
124
{ k: 'error_log', label: logLabels.error_log, md: 3, placeholder: "errors go to main log", helperText: "If you want errors in a different log" },
125
{ k: 'log_rotation', comp: SelectField, options: [{ value:'', label:"disabled" }, 'daily', 'weekly', 'monthly' ],
121
- helperText: "To avoid an endlessly-growing single log file, you can opt for rotation"
126
+ helperText: "To avoid an endlessly-growing single log file"
127
},
128
{ k: 'proxies', comp: NumberField, min: 0, max: 9, sm: 6, label: "How many HTTP proxies between this server and users?",
129
error: proxyWarning(values, status),
frontend/src/BrowseFiles.ts
+18
-6
@@ -11,7 +11,7 @@ import {
11
useRef,
12
useState
13
} from 'react'
14
-import { domOn, formatBytes, ErrorMsg, hIcon, isMobile, newDialog, hfsEvent } from './misc'
14
+import { domOn, formatBytes, ErrorMsg, hIcon, isMobile, newDialog, hfsEvent, getHFS } from './misc'
15
import { Checkbox, CustomCode, Spinner } from './components'
16
import { Head } from './Head'
17
import { state, useSnapState } from './state'
@@ -191,6 +191,7 @@ const Entry = memo((entry: DirEntry & { midnight: Date, separator?: string }) =>
191
if (separator)
192
className += ' ' + PAGE_SEPARATOR_CLASS
193
const ico = hIcon(isFolder ? 'folder' : 'file')
194
+ const menuOnLink = getHFS().fileMenuOnLink
195
return h('li', { className, label: separator },
196
showFilter && h(Checkbox, {
197
value: selected[relativePath],
@@ -200,20 +201,29 @@ const Entry = memo((entry: DirEntry & { midnight: Date, separator?: string }) =>
201
delete state.selected[relativePath]
202
},
203
}),
203
- isFolder ? h(Link, { to: base+href }, ico, relativePath.slice(0,-1))
204
+ isFolder ? h(Link, { to: base + href }, ico, relativePath.slice(0,-1))
205
: h(Fragment, {},
206
containerDir && h(Link, { to: base+fixUrl(containerDir), className:'container-folder' }, ico, containerDir ),
206
- h('a', { href, onClick: openFileMenu }, !containerDir && ico, name)
207
+ h('a', {
208
+ href,
209
+ onClick(ev: Event) {
210
+ if (!menuOnLink) return
211
+ ev.preventDefault()
212
+ openFileMenu(ev)
213
+ }
214
+ }, !containerDir && ico, name)
215
),
216
h(CustomCode, { name: 'afterEntryName', props: { entry } }),
209
- h(EntryProps, entry),
217
+ h('div', { className: 'entry-panel' },
218
+ h(EntryProps, entry),
219
+ !menuOnLink && h('button', { className: 'file-menu-button', onClick: openFileMenu }, hIcon('menu')),
220
+ ),
221
h('div'),
222
)
223
224
function openFileMenu(ev: Event) {
214
- ev.preventDefault()
225
const menu = [
216
- { label: "Open", href, target: '_blank', icon: 'play' },
226
+ menuOnLink && { label: "Open", href, target: '_blank', icon: 'play' },
227
{ label: "Download", href: href + '?dl', icon: 'download' },
228
can_delete && { label: "Delete", icon: 'trash', onClick: () => deleteFiles([href], base) }
229
]
@@ -221,6 +231,8 @@ const Entry = memo((entry: DirEntry & { midnight: Date, separator?: string }) =>
231
const close = newDialog({
232
title: "File menu",
233
icon: () => ico,
234
+ position: isMobile() ? undefined
235
+ : [ev.pageX, ev.pageY] as [number, number],
236
Content() {
237
const {t} = useI18N()
238
return h(Fragment, {},
frontend/src/index.scss
+21
-6
@@ -109,6 +109,13 @@ input, select, ul a {
109
}
110
}
111
112
+.icon-button {
113
+ font-size: 0.7em;
114
+ padding: 0.2em 0.4em;
115
+ margin-left: 0.4em;
116
+ vertical-align: bottom;
117
+}
118
+
119
.error-msg {
120
background-color: #faa;
121
color: #833;
@@ -224,17 +231,25 @@ ul.dir {
231
text-decoration: underline;
232
}
233
}
227
- & .entry-props {
234
+ & .entry-panel {
235
float: right;
229
- font-size: 90%;
230
- margin-left: 4px;
236
margin-top: 0.3em;
232
- font-variant-numeric: tabular-nums; /* trick to get stuff aligned */
237
+ display: flex;
238
+ align-items: center;
239
+ & .file-menu-button {
240
+ @extend .icon-button;
241
+ }
242
+ & .entry-props {
243
+ font-size: 90%;
244
+ margin-left: 4px;
245
+ font-variant-numeric: tabular-nums; /* trick to get stuff aligned */
246
234
- & .entry-size-unit {
235
- margin-left: .3em;
247
+ & .entry-size-unit {
248
+ margin-left: .3em;
249
+ }
250
}
251
}
252
+
253
&>div:last-of-type { clear:both }
254
&.page-separator {
255
margin-top: 1em;
shared/dialogs.ts
+17
-1
@@ -14,7 +14,8 @@ export interface DialogOptions {
14
noFrame?: boolean
15
title?: ReactNode | FunctionComponent
16
padding?: boolean
17
- dialogProps?: Record<string, any>,
17
+ position?: [number, number]
18
+ dialogProps?: Record<string, any>
19
20
Container?: FunctionComponent<DialogOptions>
21
}
@@ -52,6 +53,10 @@ function Dialog(d:DialogOptions) {
53
d.noFrame ? h(d.Content || 'div')
54
: h('div', {
55
className: 'dialog',
56
+ style: {
57
+ ...position(),
58
+ ...d.dialogProps?.style,
59
+ },
60
onClick(ev:any){
61
ev.stopPropagation()
62
},
@@ -68,6 +73,17 @@ function Dialog(d:DialogOptions) {
73
h('div', { className: 'dialog-content' }, h(d.Content || 'div'))
74
)
75
)
76
+
77
+ function position() {
78
+ const { innerWidth: w, innerHeight: h } = window
79
+ const pos = d.position
80
+ return pos && {
81
+ margin: '1em',
82
+ position: 'absolute',
83
+ ...pos[0] < w / 2 ? { left: pos[0] } : { right: w - pos[0] },
84
+ ...pos[1] < h / 2 ? { top: pos[1] } : { bottom: h - pos[1] },
85
+ }
86
+ }
87
}
88
89
export function componentOrNode(x: ReactNode | FunctionComponent) {
src/serveGuiFiles.ts
+6
-2
@@ -21,6 +21,7 @@ import { favicon, title } from './adminApis'
21
import { subscribe } from 'valtio'
22
import { customHtmlState, getSection } from './customHtml'
23
import _ from 'lodash'
24
+import { defineConfig } from './config'
25
26
// in case of dev env we have our static files within the 'dist' folder'
27
const DEV_STATIC = process.env.DEV ? 'dist/' : ''
@@ -101,8 +102,9 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
102
plugins,
103
prefixUrl: ctx.state.revProxyPath,
104
customHtml: _.omit(Object.fromEntries(customHtmlState.sections),
104
- ['top','bottom']), // excluding sections we apply in this phase
105
- }, null, 4)}
105
+ ['top','bottom']), // excluding sections we apply in this phase
106
+ fileMenuOnLink: fileMenuOnLink.get()
107
+ }, null, 4)}
108
document.documentElement.setAttribute('ver', '${VERSION.split('-')[0] /*for style selectors*/}')
109
</script>
110
<style>
@@ -148,3 +150,5 @@ function serveProxied(port: string | undefined, uri: string) { // used for devel
150
export function serveGuiFiles(proxyPort:string | undefined, uri:string) {
151
return serveProxied(proxyPort, uri) || serveStatic(uri)
152
}
153
+
154
+const fileMenuOnLink = defineConfig('file_menu_on_link', true)
\ No newline at end of file