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