Skip to content

Commit c1d2efd

Browse files
committed
[native] fix(Tree): Refactor search to not crash the app
... as often, at least Signed-off-by: Marcel Klehr <mklehr@gmx.net>
1 parent a76cb2a commit c1d2efd

1 file changed

Lines changed: 167 additions & 120 deletions

File tree

src/ui/views/native/Tree.vue

Lines changed: 167 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,16 @@
5656
</v-btn>
5757
<v-btn
5858
icon
59+
:class="{ 'search--active': Boolean(searching && showSearch) }"
5960
@click="
6061
showSearch = !showSearch
6162
searchQuery = ''
6263
">
63-
<v-icon>mdi-magnify</v-icon>
64+
<v-icon>
65+
{{
66+
searching && showSearch ? 'mdi-loading' : 'mdi-magnify'
67+
}}
68+
</v-icon>
6469
</v-btn>
6570
<v-menu
6671
v-if="!showSearch"
@@ -183,7 +188,7 @@
183188
</template>
184189
</v-list>
185190
<v-card
186-
v-else
191+
v-else-if="!searching"
187192
flat
188193
tile
189194
:style="{
@@ -321,6 +326,7 @@ import sortBy from 'lodash/sortBy'
321326
import DialogImportBookmarks from '../../components/native/DialogImportBookmarks'
322327
import Breadcrumbs from '../../components/native/Breadcrumbs.vue'
323328
import Item from '../../components/native/Item.vue'
329+
import { yieldToEventLoop } from '../../../lib/yieldToEventLoop'
324330
325331
export default {
326332
name: 'Tree',
@@ -354,6 +360,9 @@ export default {
354360
sortBy: 'index',
355361
syncProgress: 0,
356362
showSearch: false,
363+
otherSearchItems: [],
364+
searchItems: [],
365+
searching: true,
357366
}
358367
},
359368
computed: {
@@ -397,11 +406,8 @@ export default {
397406
return []
398407
}
399408
let items
400-
if (this.searchQuery && this.searchQuery.trim().length >= 2) {
401-
return this.search(
402-
this.searchQuery.toLowerCase().trim(),
403-
this.currentFolder
404-
)
409+
if (this.searchQuery) {
410+
return this.searchItems
405411
} else {
406412
items = this.currentFolder.children
407413
}
@@ -428,18 +434,6 @@ export default {
428434
return items
429435
}
430436
},
431-
otherSearchItems() {
432-
if (
433-
!this.currentFolder &&
434-
(!this.searchQuery || this.searchQuery.trim().length < 2)
435-
) {
436-
return []
437-
}
438-
return this.search(
439-
this.searchQuery.toLowerCase().trim(),
440-
this.tree
441-
).filter((item) => !this.items.includes(item))
442-
},
443437
routes() {
444438
return routes
445439
},
@@ -483,6 +477,34 @@ export default {
483477
sortBy: current,
484478
})
485479
},
480+
async searchQuery(searchQuery) {
481+
if (searchQuery.trim().length < 3) {
482+
this.searchItems = []
483+
this.otherSearchItems = []
484+
return
485+
}
486+
this.searching = true
487+
this.searchItems = []
488+
this.otherSearchItems = []
489+
await this.search(
490+
this.searchItems,
491+
searchQuery.toLowerCase().trim(),
492+
this.currentFolder
493+
)
494+
await this.search(
495+
this.otherSearchItems,
496+
searchQuery.toLowerCase().trim(),
497+
this.tree,
498+
(item) => !this.searchItems.includes(item)
499+
)
500+
this.searching = false
501+
},
502+
showSearch(showSearch, previous) {
503+
if (previous && !showSearch) {
504+
this.searchItems = []
505+
this.otherSearchItems = []
506+
}
507+
},
486508
},
487509
mounted() {
488510
this.$store.dispatch(actions.LOAD_TREE, this.$route.params.accountId)
@@ -541,110 +563,131 @@ export default {
541563
this.searchQuery = query
542564
}, 500)
543565
},
544-
search(query, tree) {
545-
return Object.values(tree.index.folder)
546-
.filter((item) => {
547-
const matchTitleFully = item.title
548-
? query.split(' ').every((term) =>
549-
item.title
550-
.toLowerCase()
551-
.split(' ')
552-
.some((word) => word === term)
553-
)
554-
: false
555-
const matchTitlePartially = item.title
556-
? query
566+
async search(results, query, tree, filterFunction = (item) => true) {
567+
// Refactored to use for loops instead of Object.values/filter
568+
const folderResults = results
569+
for (const key in tree.index.folder) {
570+
const item = tree.index.folder[key]
571+
if (!filterFunction(item)) {
572+
continue
573+
}
574+
let matchTitleFully = false
575+
let matchTitlePartially = false
576+
if (item.title) {
577+
matchTitleFully = query.split(' ').every((term) =>
578+
item.title
579+
.toLowerCase()
557580
.split(' ')
558-
.every((term) => item.title.toLowerCase().includes(term))
559-
: false
560-
return matchTitleFully || matchTitlePartially
561-
})
562-
.sort((a, b) => {
563-
const matchTitlePartiallyA = a.title
564-
? query
581+
.some((word) => word === term)
582+
)
583+
matchTitlePartially = query
584+
.split(' ')
585+
.every((term) => item.title.toLowerCase().includes(term))
586+
}
587+
if (matchTitleFully || matchTitlePartially) {
588+
folderResults.push(item)
589+
}
590+
}
591+
592+
// Sort folderResults by partial match, then by full match
593+
folderResults.sort((a, b) => {
594+
const matchTitlePartiallyA = a.title
595+
? query
596+
.split(' ')
597+
.every((term) => a.title.toLowerCase().includes(term))
598+
: false
599+
const matchTitlePartiallyB = b.title
600+
? query
601+
.split(' ')
602+
.every((term) => b.title.toLowerCase().includes(term))
603+
: false
604+
return matchTitlePartiallyA ? (matchTitlePartiallyB ? 0 : -1) : 1
605+
})
606+
folderResults.sort((a, b) => {
607+
const matchTitleA = a.title
608+
? query.split(' ').every((term) =>
609+
a.title
610+
.toLowerCase()
565611
.split(' ')
566-
.every((term) => a.title.toLowerCase().includes(term))
567-
: false
568-
const matchTitlePartiallyB = b.title
569-
? query
612+
.some((word) => word === term)
613+
)
614+
: false
615+
const matchTitleB = b.title
616+
? query.split(' ').every((term) =>
617+
b.title
618+
.toLowerCase()
570619
.split(' ')
571-
.every((term) => b.title.toLowerCase().includes(term))
572-
: false
573-
return matchTitlePartiallyA ? (matchTitlePartiallyB ? 0 : -1) : 1
574-
})
575-
.sort((a, b) => {
576-
const matchTitleA = a.title
577-
? query.split(' ').every((term) =>
578-
a.title
579-
.toLowerCase()
580-
.split(' ')
581-
.some((word) => word === term)
582-
)
583-
: false
584-
const matchTitleB = b.title
585-
? query.split(' ').every((term) =>
586-
b.title
587-
.toLowerCase()
588-
.split(' ')
589-
.some((word) => word === term)
590-
)
591-
: false
592-
return matchTitleA ? (matchTitleB ? 0 : -1) : 1
593-
})
594-
.concat(
595-
Object.values(tree.index.bookmark)
596-
.filter((item) => {
597-
const matchTitleFully = item.title
598-
? query.split(' ').every((term) =>
599-
item.title
600-
.toLowerCase()
601-
.split(' ')
602-
.some((word) => word === term)
603-
)
604-
: false
605-
const matchTitlePartially = item.title
606-
? query
607-
.split(' ')
608-
.every((term) => item.title.toLowerCase().includes(term))
609-
: false
610-
const matchUrl = query
611-
.split(' ')
612-
.every((term) => item.url.toLowerCase().includes(term))
613-
return matchUrl || matchTitleFully || matchTitlePartially
614-
})
615-
.sort((a, b) => {
616-
const matchTitlePartiallyA = a.title
617-
? query
618-
.split(' ')
619-
.every((term) => a.title.toLowerCase().includes(term))
620-
: false
621-
const matchTitlePartiallyB = b.title
622-
? query
623-
.split(' ')
624-
.every((term) => b.title.toLowerCase().includes(term))
625-
: false
626-
return matchTitlePartiallyA ? (matchTitlePartiallyB ? 0 : -1) : 1
627-
})
628-
.sort((a, b) => {
629-
const matchTitleA = a.title
630-
? query.split(' ').every((term) =>
631-
a.title
632-
.toLowerCase()
633-
.split(' ')
634-
.some((word) => word === term)
635-
)
636-
: false
637-
const matchTitleB = b.title
638-
? query.split(' ').every((term) =>
639-
b.title
640-
.toLowerCase()
641-
.split(' ')
642-
.some((word) => word === term)
643-
)
644-
: false
645-
return matchTitleA ? (matchTitleB ? 0 : -1) : 1
646-
})
647-
)
620+
.some((word) => word === term)
621+
)
622+
: false
623+
return matchTitleA ? (matchTitleB ? 0 : -1) : 1
624+
})
625+
626+
const bookmarkResults = []
627+
for (const key in tree.index.bookmark) {
628+
const item = tree.index.bookmark[key]
629+
if (!filterFunction(item)) {
630+
continue
631+
}
632+
let matchTitleFully = false
633+
let matchTitlePartially = false
634+
let matchUrl = false
635+
if (item.title) {
636+
matchTitleFully = query.split(' ').every((term) =>
637+
item.title
638+
.toLowerCase()
639+
.split(' ')
640+
.some((word) => word === term)
641+
)
642+
matchTitlePartially = query
643+
.split(' ')
644+
.every((term) => item.title.toLowerCase().includes(term))
645+
}
646+
if (item.url) {
647+
matchUrl = query
648+
.split(' ')
649+
.every((term) => item.url.toLowerCase().includes(term))
650+
}
651+
if (matchUrl || matchTitleFully || matchTitlePartially) {
652+
bookmarkResults.push(item)
653+
}
654+
}
655+
656+
// Sort bookmarkResults by partial match, then by full match
657+
bookmarkResults.sort((a, b) => {
658+
const matchTitlePartiallyA = a.title
659+
? query
660+
.split(' ')
661+
.every((term) => a.title.toLowerCase().includes(term))
662+
: false
663+
const matchTitlePartiallyB = b.title
664+
? query
665+
.split(' ')
666+
.every((term) => b.title.toLowerCase().includes(term))
667+
: false
668+
return matchTitlePartiallyA ? (matchTitlePartiallyB ? 0 : -1) : 1
669+
})
670+
bookmarkResults.sort((a, b) => {
671+
const matchTitleA = a.title
672+
? query.split(' ').every((term) =>
673+
a.title
674+
.toLowerCase()
675+
.split(' ')
676+
.some((word) => word === term)
677+
)
678+
: false
679+
const matchTitleB = b.title
680+
? query.split(' ').every((term) =>
681+
b.title
682+
.toLowerCase()
683+
.split(' ')
684+
.some((word) => word === term)
685+
)
686+
: false
687+
return matchTitleA ? (matchTitleB ? 0 : -1) : 1
688+
})
689+
690+
return results.push.apply(results, bookmarkResults)
648691
},
649692
goBack() {
650693
if (this.isAddingBookmark) {
@@ -777,6 +820,10 @@ export default {
777820
animation: spin 2s infinite linear;
778821
}
779822
823+
.search--active {
824+
animation: spin 2s infinite linear;
825+
}
826+
780827
@keyframes spin {
781828
0% {
782829
transform: rotate(360deg);

0 commit comments

Comments
 (0)