Skip to content

Commit b7ee04d

Browse files
rootroot
authored andcommitted
Refactor sorting logic + add basic fuzzy search
1 parent f652f30 commit b7ee04d

File tree

5 files changed

+548
-698
lines changed

5 files changed

+548
-698
lines changed

zimui/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
},
1717
"dependencies": {
1818
"pinia": "^2.1.7",
19-
"sql.js": "^1.13.0",
2019
"vite-plugin-vuetify": "^2.0.3",
2120
"vue": "^3.4.21",
2221
"vue-router": "4",

zimui/src/components/AppHeader.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@
6767
</v-container>
6868
</template>
6969

70-
<script setup>
70+
<script setup lang="ts">
7171
import { ref } from 'vue'
7272
7373
const searchQuery = ref('')
74+
const emit = defineEmits<{
75+
(e: 'search', value: string): void
76+
}>()
7477
7578
function handleSearch() {
76-
console.log('Searching for:', searchQuery.value)
77-
// Check: Search Logic
78-
// router.push(`/search?query=${searchQuery.value}`)
79+
emit('search', searchQuery.value)
7980
}
8081
</script>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { ref, onMounted, onUnmounted } from 'vue'
2+
import initSqlJs from 'sql.js/dist/sql-wasm.js'
3+
import type { Book } from '@/types/books'
4+
5+
export function useHomePage() {
6+
const selectedSort = ref('By popularity')
7+
const searchQuery = ref('')
8+
const booksToDisplay = ref<Book[]>([])
9+
const loadMoreTrigger = ref<HTMLElement | null>(null)
10+
const showTopButton = ref(false)
11+
const isLoading = ref(false)
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13+
let db: any = null
14+
let loadedCount = 0
15+
const pageSize = 12
16+
let observer: IntersectionObserver | null = null
17+
18+
const scrollToTop = () => {
19+
window.scrollTo({ top: 0, behavior: 'smooth' })
20+
}
21+
22+
const onScroll = () => {
23+
showTopButton.value = window.scrollY > 300
24+
}
25+
26+
// fuzzy search
27+
const buildQuery = () => {
28+
const baseQuery = ['SELECT book_id AS id, title, author, rating', 'FROM homepage']
29+
const where: string[] = []
30+
const params: (string | number)[] = []
31+
32+
const q = searchQuery.value.trim().toLowerCase()
33+
if (q) {
34+
where.push('(LOWER(title) LIKE ? OR LOWER(author) LIKE ?)')
35+
params.push(`%${q}%`, `%${q}%`)
36+
}
37+
38+
if (where.length > 0) {
39+
baseQuery.push('WHERE ' + where.join(' AND '))
40+
}
41+
42+
if (selectedSort.value === 'By title') {
43+
baseQuery.push('ORDER BY title ASC')
44+
} else if (selectedSort.value === 'By author') {
45+
baseQuery.push('ORDER BY author ASC')
46+
} else {
47+
baseQuery.push('ORDER BY rating DESC')
48+
}
49+
50+
baseQuery.push('LIMIT ? OFFSET ?')
51+
params.push(pageSize, loadedCount)
52+
53+
return { sql: baseQuery.join(' '), params }
54+
}
55+
56+
// pagination control
57+
const loadNextBooks = () => {
58+
if (isLoading.value || !db) return
59+
isLoading.value = true
60+
61+
const { sql, params } = buildQuery()
62+
const stmt = db.prepare(sql)
63+
stmt.bind(params)
64+
65+
const newBooks: Book[] = []
66+
while (stmt.step()) {
67+
newBooks.push(stmt.getAsObject() as Book)
68+
}
69+
70+
stmt.free()
71+
72+
// load more books and add to front page
73+
booksToDisplay.value.push(...newBooks)
74+
loadedCount += pageSize
75+
isLoading.value = false
76+
}
77+
78+
function onSearch(query: string) {
79+
searchQuery.value = query
80+
loadedCount = 0
81+
booksToDisplay.value = []
82+
loadNextBooks()
83+
}
84+
85+
function onSortChanged(sortKey: string) {
86+
selectedSort.value = sortKey
87+
loadedCount = 0
88+
booksToDisplay.value = []
89+
loadNextBooks()
90+
}
91+
92+
onMounted(async () => {
93+
const SQL = await initSqlJs({
94+
locateFile: (file) => new URL(file, document.baseURI).href
95+
})
96+
97+
const dbUrl = new URL('homepage.db', document.baseURI).href
98+
const res = await fetch(dbUrl)
99+
const buffer = await res.arrayBuffer()
100+
db = new SQL.Database(new Uint8Array(buffer))
101+
102+
loadNextBooks()
103+
104+
observer = new IntersectionObserver((entries) => {
105+
if (entries[0].isIntersecting) {
106+
loadNextBooks()
107+
}
108+
})
109+
110+
if (loadMoreTrigger.value) {
111+
observer.observe(loadMoreTrigger.value)
112+
}
113+
114+
window.addEventListener('scroll', onScroll)
115+
})
116+
117+
onUnmounted(() => {
118+
if (observer && loadMoreTrigger.value) {
119+
observer.unobserve(loadMoreTrigger.value)
120+
}
121+
window.removeEventListener('scroll', onScroll)
122+
})
123+
124+
return {
125+
booksToDisplay,
126+
onSortChanged,
127+
onSearch,
128+
loadMoreTrigger,
129+
showTopButton,
130+
scrollToTop
131+
}
132+
}

zimui/src/views/HomePage.vue

Lines changed: 4 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<v-app>
3-
<AppHeader />
3+
<AppHeader @search="onSearch" />
44

55
<v-container class="pt-4">
66
<Sort @sort-changed="onSortChanged" />
@@ -32,102 +32,11 @@
3232
</template>
3333

3434
<script setup lang="ts">
35-
import { ref, onMounted, onUnmounted } from 'vue'
35+
import { useHomePage } from '@/composables/useHomePage'
3636
import AppHeader from '@/components/AppHeader.vue'
3737
import Sort from '@/components/SortControl.vue'
3838
import BookCard from '@/components/BookCard.vue'
3939
40-
import initSqlJs from 'sql.js/dist/sql-wasm.js'
41-
import type { Book } from '@/types/books'
42-
43-
const selectedSort = ref('By popularity')
44-
45-
function sortBooks() {
46-
if (selectedSort.value === 'By title') {
47-
booksToDisplay.value.sort((a, b) => a.title.localeCompare(b.title))
48-
} else if (selectedSort.value === 'By author') {
49-
booksToDisplay.value.sort((a, b) => a.author.localeCompare(b.author))
50-
} else if (selectedSort.value === 'By popularity') {
51-
booksToDisplay.value.sort((a, b) => b.rating - a.rating)
52-
}
53-
}
54-
55-
function onSortChanged(sortKey: string) {
56-
selectedSort.value = sortKey
57-
sortBooks()
58-
}
59-
60-
const booksToDisplay = ref<Book[]>([])
61-
let loadedCount = 0
62-
const pageSize = 12
63-
const loadMoreTrigger = ref<HTMLElement | null>(null)
64-
const showTopButton = ref(false)
65-
const isLoading = ref(false)
66-
67-
const scrollToTop = () => {
68-
window.scrollTo({ top: 0, behavior: 'smooth' })
69-
}
70-
71-
const onScroll = () => {
72-
showTopButton.value = window.scrollY > 300
73-
}
74-
75-
let observer: IntersectionObserver | null = null
76-
77-
onMounted(async () => {
78-
const SQL = await initSqlJs({
79-
locateFile: (file) => new URL(file, document.baseURI).href
80-
})
81-
82-
const dbUrl = new URL('homepage.db', document.baseURI).href
83-
const res = await fetch(dbUrl)
84-
const buffer = await res.arrayBuffer()
85-
const db = new SQL.Database(new Uint8Array(buffer))
86-
87-
const loadNextBooks = () => {
88-
if (isLoading.value) return
89-
isLoading.value = true
90-
91-
const stmt = db.prepare(`
92-
SELECT
93-
book_id AS id,
94-
title,
95-
author,
96-
rating
97-
FROM homepage
98-
LIMIT ? OFFSET ?
99-
`)
100-
101-
stmt.bind([pageSize, loadedCount])
102-
103-
while (stmt.step()) {
104-
booksToDisplay.value.push(stmt.getAsObject() as Book)
105-
}
106-
107-
stmt.free()
108-
loadedCount += pageSize
109-
isLoading.value = false
110-
}
111-
112-
loadNextBooks()
113-
114-
observer = new IntersectionObserver((entries) => {
115-
if (entries[0].isIntersecting) {
116-
loadNextBooks()
117-
}
118-
})
119-
120-
if (loadMoreTrigger.value) {
121-
observer.observe(loadMoreTrigger.value)
122-
}
123-
124-
window.addEventListener('scroll', onScroll)
125-
})
126-
127-
onUnmounted(() => {
128-
if (observer && loadMoreTrigger.value) {
129-
observer.unobserve(loadMoreTrigger.value)
130-
}
131-
window.removeEventListener('scroll', onScroll)
132-
})
40+
const { booksToDisplay, onSortChanged, onSearch, loadMoreTrigger, showTopButton, scrollToTop } =
41+
useHomePage()
13342
</script>

0 commit comments

Comments
 (0)