feat: poisson user page with fuzzy search

dev
Antonio De Lucreziis 2 months ago
parent 2b09c6c29c
commit 87d65b52b5

@ -19,9 +19,9 @@ export default defineConfig({
}, },
}, },
integrations: [preact(), mdx()], integrations: [preact(), mdx()],
adapter: node({ // adapter: node({
mode: 'standalone', // mode: 'standalone',
}), // }),
output: 'hybrid', output: 'hybrid',
outDir: './out/astro', outDir: './out/astro',
build: { build: {

Binary file not shown.

@ -26,6 +26,7 @@
"astro": "^4.15.11", "astro": "^4.15.11",
"better-sqlite3": "^9.4.3", "better-sqlite3": "^9.4.3",
"drizzle-orm": "^0.29.4", "drizzle-orm": "^0.29.4",
"fuse.js": "^7.0.0",
"katex": "^0.16.9", "katex": "^0.16.9",
"preact": "^10.19.6", "preact": "^10.19.6",
"typescript": "^5.3.3" "typescript": "^5.3.3"

@ -0,0 +1,31 @@
import { useComputed, useSignal, type ReadonlySignal } from '@preact/signals'
import type { JSX } from 'preact/jsx-runtime'
export const ShowMore = <T extends any>({
items,
pageSize,
children,
}: {
items: ReadonlySignal<T[]>
pageSize: number
children: (item: T) => JSX.Element
}) => {
const $shownItems = useSignal(pageSize)
const $paginatedItems = useComputed(() => {
return items.value.slice(0, $shownItems.value)
})
console.log($paginatedItems.value)
return (
<>
{$paginatedItems.value.map(children)}
<div class="show-more">
{$shownItems.value < items.value.length && (
<button onClick={() => ($shownItems.value += pageSize)}>Mostra altri</button>
)}
</div>
</>
)
}

@ -1,5 +1,8 @@
import { useSignal } from '@preact/signals' import { useComputed, useSignal } from '@preact/signals'
import Fuse from 'fuse.js'
import { useEffect } from 'preact/hooks' import { useEffect } from 'preact/hooks'
import { trottleDebounce } from './lib/util'
import { ShowMore } from './Paginate'
type User = { type User = {
uid: string uid: string
@ -20,37 +23,63 @@ function applyPatches(users: User[]) {
} }
export const UtentiPage = () => { export const UtentiPage = () => {
const utentiSignal = useSignal<User[]>([]) const $utentiData = useSignal<User[]>([])
const $fuse = useSignal<Fuse<User> | null>(null)
const $searchText = useSignal('')
const $searchResults = useComputed(() =>
$searchText.value.trim().length > 0
? ($fuse.value?.search($searchText.value).map(result => result.item) ?? [])
: $utentiData.value,
)
useEffect(() => { useEffect(() => {
fetch('https://poisson.phc.dm.unipi.it/users.json') fetch('https://poisson.phc.dm.unipi.it/users.json')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
applyPatches(data) applyPatches(data)
utentiSignal.value = data
$utentiData.value = data
$fuse.value = new Fuse(data, {
keys: ['gecos'],
})
}) })
}, []) }, [])
return ( return (
<> <>
<div class="search"> <div class="search">
<input type="text" placeholder="Cerca un utente Poisson..." /> <input
type="text"
placeholder="Cerca un utente Poisson..."
onInput={e => ($searchText.value = e.currentTarget.value)}
value={$searchText.value}
/>
<span class="material-symbols-outlined">search</span> <span class="material-symbols-outlined">search</span>
</div> </div>
<div class="search-results"> <div class="search-results">
{utentiSignal.value.map(user => ( {$searchResults.value ? (
<div class="search-result"> <ShowMore items={$searchResults} pageSize={10}>
<div class="icon"> {poissonUser => (
<span class="material-symbols-outlined">person</span> console.log(poissonUser),
</div> (
<div class="text">{user.gecos}</div> <div class="search-result">
<div class="right"> <div class="icon">
<a href={`https://poisson.phc.dm.unipi.it/~${user.uid}`}> <span class="material-symbols-outlined">person</span>
<span class="material-symbols-outlined">globe</span> </div>
</a> <div class="text">{poissonUser.gecos}</div>
</div> <div class="right">
</div> <a href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`}>
))} <span class="material-symbols-outlined">globe</span>
</a>
</div>
</div>
)
)}
</ShowMore>
) : (
<>Nessun risultato</>
)}
</div> </div>
</> </>
) )

@ -0,0 +1,34 @@
export const trottleDebounce = <T extends any[], R>(
fn: (...args: T) => R,
delay: number,
options: { leading?: boolean; trailing?: boolean } = {},
): ((...args: T) => R | undefined) => {
let lastCall = 0
let lastResult: R | undefined
let lastArgs: T | undefined
let timeout: NodeJS.Timeout | undefined
const leading = options.leading ?? true
const trailing = options.trailing ?? true
return (...args: T): R | undefined => {
lastArgs = args
if (leading && Date.now() - lastCall >= delay) {
lastCall = Date.now()
lastResult = fn(...args)
} else {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
if (trailing && lastArgs) {
lastCall = Date.now()
lastResult = fn(...lastArgs)
}
}, delay)
}
console.log(lastResult)
return lastResult
}
}

@ -1,4 +1,5 @@
--- ---
# Questo documento è utilizzato nella homepage del sito nella card principale
title: Cos'è il PHC? title: Cos'è il PHC?
--- ---

@ -594,4 +594,8 @@
max-width: 25rem; max-width: 25rem;
} }
} }
.show-more {
place-self: center;
}
} }

@ -239,6 +239,8 @@
} }
.utenti { .utenti {
background: #ffffe4;
main { main {
justify-self: center; justify-self: center;
@ -250,6 +252,14 @@
padding: 5rem 0; padding: 5rem 0;
gap: 5rem; gap: 5rem;
.search-result {
background: #ffeabc;
}
button {
background: #ffd270;
}
} }
} }

Loading…
Cancel
Save