fix: error renaming current folder clicking on the last breadcrumb
Massimo Melina committed
Jul 13, 2023 at 22:35 UTC
49b9c986e74126f1637c1b84f56b4a7749cf2600
6 files changed
+40
-30
frontend/src/App.ts
+13
-4
@@ -1,7 +1,7 @@
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 { BrowserRouter, Route, Routes } from "react-router-dom"
4
-import { createElement as h } from 'react'
3
+import { BrowserRouter, Route, Routes, useNavigate } from "react-router-dom"
4
+import { createElement as h, Fragment } from 'react'
5
import { BrowseFiles } from "./BrowseFiles"
6
import { Dialogs } from './dialog'
7
import useTheme from "./useTheme"
@@ -22,8 +22,10 @@ function App() {
22
return h(I18Nprovider, {},
23
h('div', { className: tiles ? 'tiles-mode' : 'list-mode', style },
24
h(BrowserRouter, {},
25
- h(Routes, {},
26
- h(Route, { path:'*', element: h(BrowseFiles) })
25
+ h(NavigationExtractor, {},
26
+ h(Routes, {},
27
+ h(Route, { path:'*', element: h(BrowseFiles) })
28
+ ),
29
),
30
h(Dialogs),
31
)
@@ -31,6 +33,13 @@ function App() {
33
)
34
}
35
36
+// expose navigate function for programmatic usage
37
+export let navigate: ReturnType<typeof useNavigate>
38
+function NavigationExtractor(props: any) {
39
+ navigate = useNavigate()
40
+ return h(Fragment, props)
41
+}
42
+
43
export default App;
44
45
const pageState = proxy({ ready: document.readyState === 'complete' })
frontend/src/BrowseFiles.ts
+2
-3
@@ -179,7 +179,6 @@ const PAGE_SEPARATOR_CLASS = 'page-separator'
179
interface EntryProps { entry: DirEntry, midnight: Date, separator?: string }
180
const Entry = memo(({ entry, midnight, separator }: EntryProps) => {
181
const { uri, isFolder } = entry
182
- const base = usePath()
182
const { showFilter, selected } = useSnapState()
183
const containerDir = isFolder ? '' : uri.substring(0, uri.lastIndexOf('/')+1)
184
const containerName = containerDir && entry.n.slice(0, -entry.name.length)
@@ -203,12 +202,12 @@ const Entry = memo(({ entry, midnight, separator }: EntryProps) => {
202
}),
203
h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
204
isFolder ? h(Fragment, {},
206
- h(Link, { to: base + uri }, ico, entry.n.slice(0,-1)),
205
+ h(Link, { to: uri }, ico, entry.n.slice(0,-1)),
206
menuOnLink && h('button', { className: 'popup-menu-button', onClick: fileMenu }, hIcon('menu'), t`Menu`)
207
)
208
: containerDir ? h(Fragment, {},
209
h('a', { href: uri, onClick }, ico),
211
- h(Link, { to: base + containerDir, className:'container-folder' }, containerName),
210
+ h(Link, { to: containerDir, className:'container-folder' }, containerName),
211
h('a', { href: uri, onClick }, entry.name)
212
) : h('a', { href: uri, onClick }, ico, entry.name),
213
),
frontend/src/fileMenu.ts
+20
-16
@@ -9,6 +9,7 @@ import { Link } from 'react-router-dom'
9
import { fileShow, getShowType } from './show'
10
import { alertDialog, promptDialog } from './dialog'
11
import { apiCall, useApi } from '@hfs/shared/api'
12
+import { navigate } from './App'
13
14
interface FileMenuEntry {
15
label: ReactNode
@@ -19,7 +20,6 @@ interface FileMenuEntry {
20
21
export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMenuEntry | 'open' | 'delete' | 'show')[]) {
22
const { uri, isFolder, s } = entry
22
- const fullUri = uri[0] === '/' ? uri : location.pathname + uri
23
const cantDownload = entry.cantOpen || isFolder && entry.p?.includes('r') // folders needs both list and read
24
const menu = [
25
!cantDownload && { label: t`Download`, href: uri + (isFolder ? '?get=zip' : '?dl'), icon: 'download' },
@@ -27,13 +27,13 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
27
if (x === 'open') {
28
if (entry.cantOpen) return
29
const open = { icon: 'play', label: t('file_open', "Open"), href: uri, target: isFolder ? undefined : '_blank' }
30
- return !isFolder ? open : h(Link, { to: fullUri, onClick: () => close() }, hIcon(open.icon), open.label)
30
+ return !isFolder ? open : h(Link, { to: uri, onClick: () => close() }, hIcon(open.icon), open.label)
31
}
32
if (x === 'delete')
33
return state.can_delete && {
34
label: t`Delete`,
35
icon: 'trash',
36
- onClick: () => deleteFiles([entry.uri], entry.uri[0] === '/' ? '' : location.pathname)
36
+ onClick: () => deleteFiles([entry.uri])
37
}
38
if (x === 'show')
39
return !entry.cantOpen && getShowType(entry) && {
@@ -64,7 +64,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
64
: [ev.pageX, ev.pageY - scrollY] as [number, number],
65
Content() {
66
const {t} = useI18N()
67
- const [details] = useApi('get_file_details', { uris: [fullUri] });
67
+ const [details] = useApi('get_file_details', { uris: [entry.uri] });
68
const showProps = [ ...props,
69
with_(details?.[0]?.upload, x => x && [ t`Uploader`, x.ip + prefix(' (', x.username, ')') ])
70
]
@@ -101,20 +101,24 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
101
async function rename(entry: DirEntry) {
102
const dest = await promptDialog(t`Name`, { def: entry.name, title: t`Rename` })
103
if (!dest) return
104
- const uri = location.pathname + entry.uri
104
try {
105
+ const { n, uri } = entry
106
await apiCall('rename', { uri, dest })
107
- // update state instead of re-getting the list
108
- const { n } = entry
109
- const newN = n.replace(/(.*?)[^/]+(\/?)$/, (_,before,after) => before + dest + after)
110
- const newEntry = new DirEntry(newN, entry)
111
- const i = _.findIndex(state.list, { n })
112
- state.list[i] = newEntry
113
- const j = _.findIndex(state.filteredList, { n })
114
- if (j >= 0)
115
- state.filteredList![j] = newEntry
116
-
117
- alertDialog(t`Operation successful`).then() // don't wait, so it appears after the file-menu closes
107
+ const isCurrentFolder = uri === location.pathname
108
+ if (!isCurrentFolder) {
109
+ // update state instead of re-getting the list
110
+ const newN = n.replace(/(.*?)[^/]+(\/?)$/, (_,before,after) => before + dest + after)
111
+ const newEntry = new DirEntry(newN, entry)
112
+ const i = _.findIndex(state.list, { n })
113
+ state.list[i] = newEntry
114
+ const j = _.findIndex(state.filteredList, { n })
115
+ if (j >= 0)
116
+ state.filteredList![j] = newEntry
117
+ }
118
+ alertDialog(t`Operation successful`).then(() => {
119
+ if (isCurrentFolder)
120
+ navigate(uri + '../' + dest + '/')
121
+ })
122
}
123
catch(e: any) {
124
await alertDialog(e)
frontend/src/menu.ts
+3
-4
@@ -32,7 +32,6 @@ export function MenuPanel() {
32
setStarted1secAgo(false)
33
setTimeout(() => setStarted1secAgo(true), 1000)
34
}, [stopSearch, setStarted1secAgo])
35
- const pathname = usePath()
35
36
useEffect(() => {
37
if (!can_delete || localStorage.warn_can_delete) return
@@ -67,7 +66,7 @@ export function MenuPanel() {
66
icon: 'trash',
67
label: t`Delete`,
68
className: 'show-sliding',
70
- onClick: () => deleteFiles(Object.keys(selected), pathname)
69
+ onClick: () => deleteFiles(Object.keys(selected))
70
} : changingButton === 'upload' ? {
71
id: 'upload-button',
72
icon: 'upload',
@@ -198,7 +197,7 @@ function LoginButton() {
197
})
198
}
199
201
-export async function deleteFiles(uris: string[], root: string='') {
200
+export async function deleteFiles(uris: string[]) {
201
const n = uris.length
202
if (!n) {
203
alertDialog(t('delete_select', "Select something to delete")).then()
@@ -207,7 +206,7 @@ export async function deleteFiles(uris: string[], root: string='') {
206
if (!await confirmDialog(t('delete_confirm', {n}, "Delete {n,plural, one{# item} other{# items}}?")))
207
return false
208
const errors = onlyTruthy(await Promise.all(uris.map(uri =>
210
- apiCall('delete', { uri: root + uri }).then(() => null, err => ({ uri, err }))
209
+ apiCall('delete', { uri }).then(() => null, err => ({ uri, err }))
210
)))
211
reloadList()
212
const e = errors.length
frontend/src/state.ts
+1
-1
@@ -96,7 +96,7 @@ export class DirEntry {
96
constructor(n: string, rest?: object) {
97
Object.assign(this, rest) // we actually allow any custom property to be memorized
98
this.n = n // must do it after rest to avoid overwriting
99
- this.uri = pathEncode(this.n)
99
+ this.uri = (n[0] === '/' ? '' : location.pathname) + pathEncode(this.n)
100
this.isFolder = this.n.endsWith('/')
101
if (!this.isFolder) {
102
const i = this.n.lastIndexOf('.') + 1
plugins/list-uploader/public/main.js
+1
-2
@@ -6,8 +6,7 @@
6
HFS.h(Uploader, entry))
7
8
function Uploader({ uri }) {
9
- const fullUri = location.pathname + uri
10
- const { data } = HFS.useBatch(getDetails, fullUri)
9
+ const { data } = HFS.useBatch(getDetails, uri)
10
const text = React.useMemo(() => {
11
if (!data || data === true) return ''
12
const { upload: x } = data