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',