main
ts 102 lines 3.57 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 { alertDialog, newDialog, promptDialog, toast } from './dialog'
4 import { createElement as h, Fragment } from 'react'
5 import { Box } from '@mui/material'
6 import { reindexVfs, VfsNodeAdmin } from './VfsPage'
7 import { addToChildrenOf } from './VfsTree'
8 import { prepareVfsUndo, state } from './state'
9 import FilePicker from './FilePicker'
10 import { basename, extname, focusSelector, getHFS, Optional } from '@hfs/shared'
11
12 let lastFolder: undefined | string
13 export default function addFiles() {
14 const { close } = newDialog({
15 title: "Add files or folders",
16 dialogProps: { sx:{ minWidth: 'min(80vw, 40em)', minHeight: 'calc(100vh - 9em)' } },
17 Content() {
18 const parent = getFolderFromSelected()
19 return h(Fragment, {},
20 h(Box, { sx:{ typography: 'body1', px: 1, py: 2 } },
21 "Selected elements will be added under ",
22 parent.isRoot ? h('i', {}, "Home") : decodeURI(parent.id)
23 ),
24 h(FilePicker, {
25 from: lastFolder ?? parent.source,
26 async onSelect(sel) {
27 addNodes(parent, sel.map(source => ({ source, name: getFreeName(parent, basename(source)) })))
28 lastFolder = sel[0].slice(0, sel[0].lastIndexOf('/'))
29 close()
30 }
31 })
32 )
33 }
34 })
35 }
36
37 function addNodes(parent: VfsNodeAdmin, nodes: Optional<VfsNodeAdmin, 'id' | 'originalId'>[]) {
38 for (const n of nodes) {
39 if (n.source?.endsWith(getHFS().pathSeparator) || !n.source && !n.url)
40 n.type = 'folder'
41 n.id ||= parent.id + n.name + (n.type === 'folder' ? '/' : '')
42 n.originalId ||= n.id
43 n.parent = parent
44 }
45 prepareVfsUndo()
46 const select = nodes as VfsNodeAdmin[]
47 addToChildrenOf(parent, select)
48 reindexVfs({ select, sortChildren: true })
49 }
50
51 function getFreeName(parent: VfsNodeAdmin, name: string) {
52 const ext = extname(name)
53 const noExt = ext ? name.slice(0, -ext.length) : name
54 let idx = 2
55 while (parent.children?.find(isSameFilenameAs(name)))
56 name = `${noExt} ${idx++}${ext}`
57 return name
58 }
59
60 function normalizeFilename(x: string) {
61 return x.toLocaleLowerCase().normalize() // in this context we always use lowercase for comparison
62 }
63
64 function isSameFilenameAs(name: string) {
65 const normalized = normalizeFilename(name)
66 return (other: string | VfsNodeAdmin) =>
67 normalized === normalizeFilename(typeof other === 'string' ? other : other.name)
68 }
69
70 export async function addVirtual() {
71 try {
72 let name = await promptDialog("Enter folder name")
73 if (!name) return
74 const parent = getFolderFromSelected()
75 name = getFreeName(parent, name)
76 if (!name) return
77 addNodes(parent, [{ name, type: 'folder' }])
78 }
79 catch(e) {
80 await alertDialog(e as Error)
81 }
82 }
83
84 export async function addLink() {
85 try {
86 const parent = getFolderFromSelected()
87 const name = getFreeName(parent, 'new link')
88 if (!name) return
89 addNodes(parent, [{ name, url: 'https://example.com' }])
90 toast("Link created", 'success', {
91 onClose: () => focusSelector('input[name=url]')
92 })
93 }
94 catch(e) {
95 await alertDialog(e as Error)
96 }
97 }
98
99 function getFolderFromSelected() {
100 const f = state.selectedFiles[0] || state.vfs
101 return f.type === 'folder' ? f : f.parent!
102 }