admin: responsive layout
Massimo Melina committed
Mar 14, 2022 at 15:09 UTC
0fff6599b9e107f58de8c685482cdfd3558f0ca9
7 files changed
+67
-47
admin/src/AccountsPage.ts
+2
-1
@@ -48,6 +48,7 @@ export default function AccountsPage() {
48
h(Grid, { item: true, xs: 12 },
49
h(Box, {
50
display: 'flex',
51
+ flexWrap: 'wrap',
52
gap: 2,
53
mb: 2,
54
sx: {
@@ -85,7 +86,7 @@ export default function AccountsPage() {
86
h(Grid, { item: true, md: 6 },
87
h(TreeView, {
88
multiSelect: true,
88
- sx: { pr: 4, minWidth: '15em' },
89
+ sx: { pr: 4, pb: 2, minWidth: '15em' },
90
selected: sel,
91
onNodeSelect(ev, ids) {
92
switchTo(ids)
admin/src/App.ts
+51
-29
@@ -1,12 +1,13 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { createElement as h } from 'react'
3
+import { createElement as h, Fragment, useState } from 'react'
4
import { HashRouter, Routes, Route, useLocation } from 'react-router-dom'
5
import MainMenu, { getMenuLabel, mainMenu } from './MainMenu'
6
-import { Box, ThemeProvider, Typography } from '@mui/material'
6
+import { AppBar, Box, Drawer, Hidden, IconButton, ThemeProvider, Toolbar, Typography } from '@mui/material'
7
import { Dialogs } from './dialog'
8
import { useMyTheme } from './theme'
9
import { LoginRequired } from './LoginRequired'
10
+import { Menu } from '@mui/icons-material'
11
12
function App() {
13
return h(ThemeProvider, { theme: useMyTheme() },
@@ -18,8 +19,9 @@ function App() {
19
function ApplyTheme(props:any) {
20
return h(Box, {
21
sx: {
21
- bgcolor:'background.default', color: 'text.primary',
22
- position:'absolute', top:0, left:0, bottom:0, right:0,
22
+ bgcolor: 'background.default', color: 'text.primary',
23
+ display: 'flex', flexDirection: 'column',
24
+ minHeight: '100%', flex: 1,
25
},
26
...props
27
})
@@ -29,32 +31,52 @@ function Routed() {
31
const loc = useLocation().pathname.slice(1)
32
const current = mainMenu.find(x => x.path === loc)
33
const title = current && (current.title || getMenuLabel(current))
32
- return h(Box, { display: 'flex' },
33
- h(MainMenu),
34
- h(Box, {
35
- component: 'main',
36
- sx: {
37
- flexGrow: 1,
38
- height: 'calc(100vh - 1em)',
39
- overflow: 'auto',
40
- px: 3,
41
- pb: '1em',
42
- position: 'relative',
43
- display: 'flex',
44
- flexDirection: 'column',
45
- background: 'url(logo.svg) no-repeat center',
46
- backgroundSize: 'contain',
47
- }
48
- },
49
- title && h(Typography, { variant:'h1', mb:2 }, title),
50
- h(Routes, {},
51
- mainMenu.map((it,idx) => {
52
- const element = it.comp ? h(it.comp) : h('div', {}, 'to be done')
53
- return h(Route, { key: idx, path: it.path, element })
54
- })
55
- )
34
+ const [open, setOpen] = useState(false)
35
+ return h(Fragment, {},
36
+ h(Hidden, { mdUp: true },
37
+ h(StickyBar, { title, openMenu: () => setOpen(true) }),
38
+ h(Drawer, { anchor:'left', open, onClose(){ setOpen(false) } },
39
+ h(MainMenu, {
40
+ onSelect: () => setOpen(false)
41
+ }))
42
),
57
- h(Dialogs)
43
+ h(Box, { display: 'flex', flex: 1, }, // horizontal layout for menu-content
44
+ h(Hidden, { mdDown: true }, h(MainMenu) ),
45
+ h(Box, {
46
+ component: 'main',
47
+ sx: {
48
+ background: 'url(logo.svg) no-repeat right fixed',
49
+ backgroundSize: 'contain',
50
+ px: 3,
51
+ pb: '1em',
52
+ position: 'relative',
53
+ display: 'flex',
54
+ flexDirection: 'column',
55
+ width: '100%',
56
+ }
57
+ },
58
+ title && h(Hidden, { mdDown: true }, h(Typography, { variant:'h1', mb:2 }, title) ),
59
+ h(Routes, {}, mainMenu.map((it,idx) =>
60
+ h(Route, { key: idx, path: it.path, element: h(it.comp) })) )
61
+ ),
62
+ h(Dialogs)
63
+ )
64
+ )
65
+}
66
+
67
+function StickyBar({ title, openMenu }: { title?: string, openMenu: ()=>void }) {
68
+ return h(AppBar, { position: 'sticky', sx: { mb: 2 } },
69
+ h(Toolbar, {},
70
+ h(IconButton, {
71
+ size: 'large',
72
+ edge: 'start',
73
+ color: 'inherit',
74
+ sx: { mr: 2 },
75
+ 'aria-label': "menu",
76
+ onClick: openMenu
77
+ }, h(Menu)),
78
+ title,
79
+ )
80
)
81
}
82
admin/src/ConfigPage.ts
+4
-6
@@ -52,11 +52,9 @@ export default function ConfigPage() {
52
startIcon: h(Refresh),
53
}, "Reload")],
54
defaults({ comp }) {
55
- if (comp === ServerPort)
56
- return { md: 6, lg: 3 }
57
- if (comp === NumberField)
58
- return { md:3 }
59
- return { md: 6 }
55
+ return comp === ServerPort ? { sm: 6, lg: 3 }
56
+ : comp === NumberField ? { sm: 3 }
57
+ : { sm: 6 }
58
},
59
fields: [
60
{ k: 'port', comp: ServerPort, label:"HTTP port", status: status?.http||true, suggestedPort: 80 },
@@ -73,7 +71,7 @@ export default function ConfigPage() {
71
{ k: 'accounts', comp: StringField, label: "Accounts file" },
72
{ k: 'open_browser_at_start', comp: BoolField },
73
{ k: 'allowed_referer', placeholder: "any", helperText: "Leave empty to allow any", },
76
- { k: 'zip_calculate_size_for_seconds', comp: NumberField, md: 6, label: "Calculate ZIP size for seconds",
74
+ { k: 'zip_calculate_size_for_seconds', comp: NumberField, sm: 6, label: "Calculate ZIP size for seconds",
75
helperText: "If time is not enough, the browser will not show download percentage" },
76
{ k: 'mime', comp: StringStringField,
77
keyLabel: "Files", keyWidth: 7,
admin/src/MainMenu.ts
+5
-4
@@ -1,6 +1,6 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { createElement as h, FunctionComponent } from 'react';
3
+import { createElement as h, FC } from 'react';
4
import { List, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material'
5
import { AccountTree, Logout, ManageAccounts, Monitor, Public, Settings, SvgIconComponent } from '@mui/icons-material'
6
import _ from 'lodash'
@@ -17,7 +17,7 @@ interface MenuEntry {
17
icon: SvgIconComponent
18
label?: string
19
title?: string
20
- comp?: FunctionComponent
20
+ comp: FC
21
}
22
23
export const mainMenu: MenuEntry[] = [
@@ -29,14 +29,15 @@ export const mainMenu: MenuEntry[] = [
29
{ path: 'Logout', icon: Logout, comp: LogoutPage }
30
]
31
32
-export default function Menu() {
33
- return h(List, { sx:{ pr:1, bgcolor: 'primary.main', color:'primary.contrastText' } },
32
+export default function Menu({ onSelect }: { onSelect: ()=>void }) {
33
+ return h(List, { sx:{ pr:1, bgcolor: 'primary.main', color:'primary.contrastText', minHeight: '100%', boxSizing: 'border-box' } },
34
h(Typography, { variant:'h4', sx:{ p:2 } }, 'HFS'),
35
mainMenu.map(it =>
36
h(ListItemButton, {
37
key: it.path,
38
to: it.path,
39
component: NavLink,
40
+ onClick: onSelect,
41
// @ts-ignore
42
style: ({ isActive }) => isActive ? { textDecoration: 'underline' } : {},
43
children: undefined, // shut up ts
admin/src/MonitorPage.ts
+2
-6
@@ -19,10 +19,6 @@ export default function MonitorPage() {
19
20
const isoDateRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
21
22
-function SectionTitle(props: object) {
23
- return h(Typography, { variant: 'h4', px: 2, ...props })
24
-}
25
-
22
function MoreInfo() {
23
const [res] = useApiComp('get_status')
24
return h(Fragment, {},
@@ -70,8 +66,8 @@ function Connections() {
66
const [filtered, setFiltered] = useState(true)
67
const rows = useMemo(()=> list?.filter((x:any) => !filtered || x.path).map((x:any,id:number) => ({ id, ...x })), [list, filtered])
68
return h(Fragment, {},
73
- h(Box, { display: 'flex', justifyContent: 'space-between' },
74
- h(SectionTitle, {}, "Active connections"),
69
+ h(Box, { display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
70
+ h(Box, { fontSize: { xs:'1em', md:'2em' }, px: 2 }, "Active connections"),
71
h(SelectField as Field<boolean>, {
72
fullWidth: false,
73
value: filtered,
admin/src/VfsPage.ts
+1
-1
@@ -47,7 +47,7 @@ export default function VfsPage() {
47
id2node.clear()
48
return res
49
}
50
- return h(Grid, { container:true },
50
+ return h(Grid, { container:true, rowSpacing: 1 },
51
h(Grid, { item:true, sm: 6, lg: 7 },
52
h(VfsMenuBar),
53
snap.vfs && h(VfsTree, { id2node })),
admin/src/index.css
+2
@@ -5,7 +5,9 @@ body {
5
sans-serif;
6
-webkit-font-smoothing: antialiased;
7
-moz-osx-font-smoothing: grayscale;
8
+ height: 100vh;
9
}
10
+#root { height: 100%; }
11
12
code {
13
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',