main
ts 133 lines 5.34 KB
Raw
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { createElement as h, ReactNode } from 'react'
4 import { Alert, Box, ButtonProps, List, ListItem, ListItemIcon, ListItemText } from '@mui/material'
5 import { Add, Save, Storage, Undo } from '@mui/icons-material'
6 import addFiles, { addLink, addVirtual } from './addFiles'
7 import MenuButton from './MenuButton'
8 import { osIcon } from './LogsPage'
9 import { reloadVfs } from './VfsPage'
10 import { Dict, prefix, VFS_STORED_KEYS } from './misc'
11 import { state, undoVfs, useSnapState } from './state'
12 import _ from 'lodash'
13 import { Btn, Flex, reloadBtn, useBreakpoint, useCtrlShortcutButton } from './mui'
14 import { apiCall, ApiObject, useApi } from './api'
15 import VfsPathField from './VfsPathField'
16 import { alertDialog, promptDialog } from './dialog'
17 import { formatDiskSpace } from './FilePicker'
18 import { getDiskSpaces } from '../../src/util-os'
19 import { adminApis } from '../../src/adminApis'
20
21 export default function VfsMenuBar({ statusApi, add }: { add: ReactNode, statusApi: ApiObject }) {
22 const { vfsModified, vfsUndo } = useSnapState()
23 return h(Flex, {
24 zIndex: 2,
25 gap: 1,
26 backgroundColor: 'background.paper',
27 width: 'fit-content',
28 flexWrap: 'wrap',
29 },
30 h(AddVfsBtn),
31 h(Btn, {
32 ref: useCtrlShortcutButton(['s']).ref,
33 icon: Save,
34 title: "Save\n(ctrl+s)",
35 disabled: !vfsModified && "No changes to save",
36 modified: vfsModified,
37 doneAnimation: true,
38 onClick: () => saveVfs().finally(statusApi.reload)
39 }),
40 h(Btn, {
41 icon: Undo,
42 title: "Undo/redo last change",
43 disabled: !vfsUndo && "No changes to undo",
44 onClick: undoVfs,
45 }),
46 reloadBtn(() => reloadVfs()),
47 h(Btn, {
48 icon: Storage,
49 title: "Disk spaces",
50 onClick: () => apiCall<Awaited<ReturnType<typeof getDiskSpaces>>>('get_disk_spaces').then(res =>
51 alertDialog(h(List, { dense: true }, res.map(x => h(ListItem, { key: x.name },
52 h(ListItemIcon, {}, h(Storage)),
53 h(ListItemText, {
54 sx: { wordBreak: 'break-word' },
55 primary: x.name + prefix(' (', x.description, ')'),
56 secondary: formatDiskSpace(x)
57 }),
58 ))), { title: "Disk spaces" })
59 .then(() => false), // no success-animation for IconBtn
60 alertDialog)
61 }),
62 add,
63 h(SystemIntegrationButton, statusApi.data)
64 )
65 }
66
67 export function AddVfsBtn(props: Partial<ButtonProps>) {
68 return h(MenuButton, {
69 variant: 'contained',
70 icon: Add,
71 title: "Add item to virtual file system",
72 ...props,
73 items: [
74 { children: "virtual folder", onClick: addVirtual },
75 { children: "file or folder from disk", onClick: addFiles },
76 { children: "web-link", onClick: addLink },
77 ]
78 })
79 }
80
81 function SystemIntegrationButton({ platform }: { platform: string | undefined }) {
82 const isWindows = platform === 'win32'
83 const { data: integrated, reload } = useApi<typeof adminApis.windows_integrated>(isWindows && 'windows_integrated')
84 const sm = useBreakpoint('sm')
85 return !isWindows ? null : h(Btn, {
86 icon: osIcon('Windows'),
87 variant: 'outlined',
88 doneMessage: true,
89 ...(!integrated?.is ? {
90 children: "System integration",
91 async onClick() {
92 const msg = h(Box, { sx: { width: { xs: '100%', sm: '34em' } } },
93 h('img', { src: 'win-shell.png', style: { display: 'block', width: '100%' } }),
94 h(Alert, { severity: 'info' }, "We are going to add a command in the right-click of Windows File Manager.",
95 h(Box, {}, "It will also automatically copy the URL, ready to paste!")),
96 )
97 const parent = await promptDialog(msg, {
98 field: { comp: VfsPathField, files: false, label: "Add to this folder", placeholder: "home",
99 autoFocus: sm }, // this dialog is tall, and mobile keyboard will disrupt user's ability to view its content
100 form: { saveOnEnter: false }
101 })
102 return typeof parent === 'string' && apiCall('windows_integration', { parent }).then(reload)
103 }
104 } : {
105 confirm: true,
106 children: "Remove integration",
107 onClick: () => apiCall('windows_remove').then(reload),
108 })
109 })
110 }
111
112 async function saveVfs() {
113 const uriRemaps: Dict<string> = {}
114 recurVfs(n => n.originalId !== n.id && (uriRemaps[n.originalId] = n.id))
115 await apiCall('set_vfs', {
116 uri: '/',
117 uriRemaps,
118 props: (function recur(n=state.vfs) {
119 const ret = _.pick(n, VFS_STORED_KEYS)
120 ret.children = n?.children?.map(recur) as any
121 return ret
122 })()
123 })
124 recurVfs(n => n.originalId = n.id) // reset
125 state.vfsModified = false
126 }
127
128 function recurVfs(cb: (n: NonNullable<typeof state.vfs>) => any, node=state.vfs) {
129 if (!node) return
130 cb(node)
131 node?.children?.forEach(child => recurVfs(cb, child))
132 return node
133 }