@samitouri / QOSami-HFS / commits / 49b9c986

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