optimization: precalculate string to sort
Massimo Melina committed
Feb 16, 2025 at 11:00 UTC
e38ba1b6c37473c95489d3061a1c7b85e8d524cb
5 files changed
+14
-16
frontend/src/BrowseFiles.ts
+2
-2
@@ -288,7 +288,7 @@ const Entry = ({ entry, midnight, separator }: EntryProps) => {
288
const { uri, isFolder, name, n } = entry
289
const { showFilter, selected, file_menu_on_link } = useSnapState()
290
const isLink = Boolean(entry.url)
291
- const containerName = n.slice(0, -name.length - (isFolder ? 1 : 0)).replaceAll('/', '/ ')
291
+ const containerName = n.slice(0, -name.length).replaceAll('/', '/ ')
292
let className = isFolder ? 'folder' : 'file'
293
if (entry.cantOpen)
294
className += ' cant-open'
@@ -303,7 +303,7 @@ const Entry = ({ entry, midnight, separator }: EntryProps) => {
303
return h(CustomCode, {
304
name: 'entry',
305
entry,
306
- render: x => x ? h('li', { className, label: separator }, x) : _.remove(state.list, { n }) && null
306
+ render: x => x ? h('li', { className, label: separator }, x) : _.remove(state.list, { n }) && null // custom-code wants us to skip this entry
307
}, showFilter && h(Checkbox, {
308
disabled: !entry.canSelect(),
309
'aria-labelledby': ariaId,
frontend/src/components.ts
+1
@@ -76,6 +76,7 @@ export function Select<T extends string>({ onChange, value, options, ...props }:
76
}, options.map(({ value, label }) => h('option', { key: value, value }, label)))
77
}
78
79
+// @param render always gets a truthy, even with empty children will get empty array, unless the custom-code is requiring to cancel the whole entry
80
export function CustomCode({ name, children, render, ...props }: {
81
name: string,
82
children?: ReactNode,
frontend/src/fileMenu.ts
+2
-2
@@ -71,7 +71,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (Falsy
71
state.props?.can_delete && { id: 'cut', label: t`Cut`, icon: 'cut', onClick: () => close(cut([entry])) },
72
isFolder && !entry.web && !entry.cantOpen && { id: 'list', label: t`Get list`, href: uri + '?get=list&folders=*', icon: 'list' },
73
].filter(Boolean)
74
- const folder = entry.n.slice(0, -entry.name.length - (entry.isFolder ? 2 : 1))
74
+ const folder = entry.n.slice(0, -1 - entry.name.length)
75
const props = [
76
{ id: 'name', label: t`Name`, value: entry.name },
77
typeof s === 'number' && { id: 'size', label: t`Size`,
@@ -181,7 +181,7 @@ async function rename(entry: DirEntry) {
181
return alertDialog(MSG).then(() =>
182
getHFS().navigate(uri + '../' + pathEncode(dest) + '/') )
183
// update state instead of re-getting the list
184
- const newN = n.replace(/(.*?)[^/]+(\/?)$/, (_,before,after) => before + dest + after)
184
+ const newN = n.replace(/(.*?)[^/]+$/, (_,before) => before + dest)
185
const newEntry = new DirEntry(newN, { key: n, ...entry }) // by keeping old key, we avoid unmounting the element, that's causing focus lost
186
const i = _.findIndex(state.list, { n })
187
state.list[i] = newEntry
frontend/src/state.ts
+8
-7
@@ -115,18 +115,19 @@ export class DirEntry implements ServerDirEntry {
115
116
constructor(n: string, rest?: any) {
117
Object.assign(this, rest) // we actually allow any custom property to be memorized
118
- this.n = n // must do it after rest to avoid overwriting
119
- this.uri = rest?.url || ((n[0] === '/' ? '' : location.pathname) + pathEncode(this.n))
120
- this.isFolder = this.n.endsWith('/')
118
+ this.isFolder = n.endsWith('/')
119
+ if (this.isFolder)
120
+ n = n.slice(0, -1)
121
+ this.n = n // must do it after 'rest' to avoid overwriting
122
+ this.uri = rest?.url || ((n[0] === '/' ? '' : location.pathname) + pathEncode(n) + (this.isFolder ? '/' : ''))
123
if (!this.isFolder) {
122
- const i = this.n.lastIndexOf('.') + 1
123
- this.ext = i ? this.n.substring(i).toLowerCase() : ''
124
+ const i = n.lastIndexOf('.') + 1
125
+ this.ext = i ? n.substring(i).toLowerCase() : ''
126
}
127
this.c &&= new Date(this.c)
128
this.m &&= new Date(this.m)
129
this.t = this.m || this.c
128
- this.name = this.isFolder ? this.n.slice(this.n.lastIndexOf('/', this.n.length - 2) + 1, -1)
129
- : this.n.slice(this.n.lastIndexOf('/') + 1)
130
+ this.name = n.slice(n.lastIndexOf('/') + 1)
131
const x = this.isFolder && !this.web ? 'L' : 'R' // to open we need list for folders and read for files
132
this.cantOpen = this.p?.match(x) ? true : this.p?.match(x.toLowerCase()) ? DirEntry.FORBIDDEN : undefined
133
}
frontend/src/useFetchList.ts
+1
-5
@@ -189,13 +189,9 @@ function sort(list: DirList) {
189
: 0
190
)
191
|| sort_numerics && (invert * compareNumerics(a.n, b.n))
192
- || invert * localCompare(nameToCompare(a), nameToCompare(b)) // fallback to name/path
192
+ || invert * localCompare(a.n, b.n) // fallback to name/path
193
)
194
195
- function nameToCompare(x: DirEntry) { // try to avoid slicing. When searching, we need to consider the path
196
- return !x.isFolder ? x.n : !state.remoteSearch ? x.name : x.n.slice(0, -1)
197
- }
198
-
195
function compareNumerics(a: string, b: string) {
196
const re = /\d/g
197
if (!re.exec(a)) return 0