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