From 0a5fb483370fa71944f88c168e0c56a1ac956f5b Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:49:30 +0800 Subject: [PATCH 01/21] perf: use batch loading for Await-based loadMore in news --- .../src/lib/components/loading/Await.svelte | 63 ++++++++----------- .../src/routes/news/+page.server.ts | 49 +++++++++++++-- .../src/routes/news/[slug]/+page.server.ts | 53 ++++++++++++++-- 3 files changed, 117 insertions(+), 48 deletions(-) diff --git a/website-frontend/src/lib/components/loading/Await.svelte b/website-frontend/src/lib/components/loading/Await.svelte index 569b6aeb..b8384aa6 100644 --- a/website-frontend/src/lib/components/loading/Await.svelte +++ b/website-frontend/src/lib/components/loading/Await.svelte @@ -1,28 +1,29 @@
@@ -47,33 +48,21 @@
{#if data.length < count} - - {/if} - {#if data.length > limit} - +
+ +
{/if}
Showing {Math.min(currentLimit, count)} items out of {count}Showing {data.length} items out of {count}
{:catch} diff --git a/website-frontend/src/routes/news/+page.server.ts b/website-frontend/src/routes/news/+page.server.ts index b61abd0f..baa36de9 100644 --- a/website-frontend/src/routes/news/+page.server.ts +++ b/website-frontend/src/routes/news/+page.server.ts @@ -3,13 +3,14 @@ import { aggregate, readItems, readSingleton } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; import { News } from '$lib/models/news'; -import { redirect } from '@sveltejs/kit'; import { NewsOverview } from '$lib/models/news_overview.js'; +import type { Actions } from './$types'; -export async function load({ fetch, url }) { +const news_limit = 12; + +export async function load({ fetch }) { const directus = getDirectusInstance(fetch); const news_overview = parse(NewsOverview, await directus.request(readSingleton('news_overview'))); - const news_limit = 12; const news_count = await directus .request( aggregate('news', { @@ -17,8 +18,6 @@ export async function load({ fetch, url }) { }) ) .then((res) => parseInt(res[0].count ?? '0')); - if (parseInt(url.searchParams.get('limit') ?? news_limit.toString()) < news_limit) - throw redirect(302, url.pathname); const news = parseAsync( pipeAsync(promise(), awaitAsync(), News), directus.request( @@ -40,10 +39,48 @@ export async function load({ fetch, url }) { } ], sort: ['-date_created'], - limit: Math.max(news_limit, parseInt(url.searchParams.get('limit') ?? '0')) + limit: news_limit }) ) ); return { news_overview, news_limit, news_count, news }; } + +export const actions = { + loadMore: async ({ request }) => { + const data = await request.formData(); + const directus = getDirectusInstance(null); + const news = parse( + News, + await directus.request( + readItems('news', { + fields: [ + '*', + { + user_created: ['first_name', 'last_name'] + }, + { + user_updated: ['first_name', 'last_name'] + }, + { + news_tags: [ + { + news_tags_id: ['name'] + } + ] + } + ], + sort: ['-date_created'], + offset: parseInt((data.get('offset') ?? '0') as string), + limit: news_limit + }) + ) + ); + + return { + success: true, + items: news + }; + } +} satisfies Actions; diff --git a/website-frontend/src/routes/news/[slug]/+page.server.ts b/website-frontend/src/routes/news/[slug]/+page.server.ts index ac5a0318..eda32940 100644 --- a/website-frontend/src/routes/news/[slug]/+page.server.ts +++ b/website-frontend/src/routes/news/[slug]/+page.server.ts @@ -1,9 +1,12 @@ /** @type {import('./$types').PageServerLoad} */ import { aggregate, readItems } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; -import { error, redirect } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit'; import { parse } from 'valibot'; import { News, NewsItem } from '$lib/models/news'; +import type { Actions } from './$types'; + +const other_news_limit = 12; export async function load({ url, params, fetch }) { const directus = getDirectusInstance(fetch); @@ -43,7 +46,6 @@ export async function load({ url, params, fetch }) { const news_item = parse(NewsItem, news[0]); - const other_news_limit = 12; const other_news_count = await directus .request( aggregate('news', { @@ -60,8 +62,6 @@ export async function load({ url, params, fetch }) { }) ) .then((res) => res[0].count); - if (parseInt(url.searchParams.get('limit') ?? other_news_limit.toString()) < other_news_limit) - throw redirect(302, url.pathname); const other_news = parse( News, await directus.request( @@ -88,7 +88,7 @@ export async function load({ url, params, fetch }) { } }, sort: ['-date_created'], - limit: Math.max(other_news_limit, parseInt(url.searchParams.get('limit') ?? '0')) + limit: other_news_limit }) ) ); @@ -97,3 +97,46 @@ export async function load({ url, params, fetch }) { return { link, other_news_limit, other_news_count, other_news, news_item }; } + +export const actions = { + loadMore: async ({ request, params }) => { + const data = await request.formData(); + const directus = getDirectusInstance(null); + const other_news = parse( + News, + await directus.request( + readItems('news', { + fields: [ + '*', + { + user_created: ['first_name', 'last_name'] + }, + { + user_updated: ['first_name', 'last_name'] + }, + { + news_tags: [ + { + news_tags_id: ['name'] + } + ] + } + ], + filter: { + slug: { + _neq: params.slug + } + }, + sort: ['-date_created'], + offset: parseInt((data.get('offset') ?? '0') as string), + limit: other_news_limit + }) + ) + ); + + return { + success: true, + items: other_news + }; + } +} satisfies Actions; From 76144c72d7fc11ef81e655d24485a15a8619e9ef Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:55:35 +0800 Subject: [PATCH 02/21] feat: use fetch parameter for getDirectusInstance in loadMore server action --- website-frontend/src/routes/news/+page.server.ts | 4 ++-- website-frontend/src/routes/news/[slug]/+page.server.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/website-frontend/src/routes/news/+page.server.ts b/website-frontend/src/routes/news/+page.server.ts index baa36de9..964306a4 100644 --- a/website-frontend/src/routes/news/+page.server.ts +++ b/website-frontend/src/routes/news/+page.server.ts @@ -48,9 +48,9 @@ export async function load({ fetch }) { } export const actions = { - loadMore: async ({ request }) => { + loadMore: async ({ request, fetch }) => { const data = await request.formData(); - const directus = getDirectusInstance(null); + const directus = getDirectusInstance(fetch); const news = parse( News, await directus.request( diff --git a/website-frontend/src/routes/news/[slug]/+page.server.ts b/website-frontend/src/routes/news/[slug]/+page.server.ts index eda32940..106def18 100644 --- a/website-frontend/src/routes/news/[slug]/+page.server.ts +++ b/website-frontend/src/routes/news/[slug]/+page.server.ts @@ -99,9 +99,9 @@ export async function load({ url, params, fetch }) { } export const actions = { - loadMore: async ({ request, params }) => { + loadMore: async ({ request, params, fetch }) => { const data = await request.formData(); - const directus = getDirectusInstance(null); + const directus = getDirectusInstance(fetch); const other_news = parse( News, await directus.request( From 6511e8edc94bc618391650dad1a16b16229fa9a1 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 01:39:54 +0800 Subject: [PATCH 03/21] fix: lazy load other_news --- website-frontend/src/routes/news/[slug]/+page.server.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website-frontend/src/routes/news/[slug]/+page.server.ts b/website-frontend/src/routes/news/[slug]/+page.server.ts index 106def18..e65183b7 100644 --- a/website-frontend/src/routes/news/[slug]/+page.server.ts +++ b/website-frontend/src/routes/news/[slug]/+page.server.ts @@ -2,7 +2,7 @@ import { aggregate, readItems } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; import { error } from '@sveltejs/kit'; -import { parse } from 'valibot'; +import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; import { News, NewsItem } from '$lib/models/news'; import type { Actions } from './$types'; @@ -62,9 +62,9 @@ export async function load({ url, params, fetch }) { }) ) .then((res) => res[0].count); - const other_news = parse( - News, - await directus.request( + const other_news = parseAsync( + pipeAsync(promise(), awaitAsync(), News), + directus.request( readItems('news', { fields: [ '*', From 82bac70bc46e7282ff488d585c3b315b0bb5539b Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 01:40:38 +0800 Subject: [PATCH 04/21] fix: set data as Promise> in Await component --- website-frontend/src/lib/components/loading/Await.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website-frontend/src/lib/components/loading/Await.svelte b/website-frontend/src/lib/components/loading/Await.svelte index b8384aa6..ef1cd034 100644 --- a/website-frontend/src/lib/components/loading/Await.svelte +++ b/website-frontend/src/lib/components/loading/Await.svelte @@ -7,7 +7,7 @@ export let onDark = false; export let layout; - export let data: Array; + export let data: Promise>; export let text; export let component; export let count; @@ -19,7 +19,7 @@ formData.set('offset', (offset + limit).toString()); return async ({ result }: { result: ActionResult }) => { if (result.type === 'success' && result.data) { - data = [...(await data), ...result.data.items]; + data = Promise.all([...(await data), ...result.data.items]); offset = offset + limit; } }; From c28ed4e9ee4c54d5d46c2ec6e7349169ccd13bf6 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 09:09:54 +0800 Subject: [PATCH 05/21] feat: add data stream and query support for Await --- .../src/lib/components/loading/Await.svelte | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/website-frontend/src/lib/components/loading/Await.svelte b/website-frontend/src/lib/components/loading/Await.svelte index ef1cd034..70228433 100644 --- a/website-frontend/src/lib/components/loading/Await.svelte +++ b/website-frontend/src/lib/components/loading/Await.svelte @@ -4,22 +4,25 @@ import { Frown, Plus } from 'lucide-svelte'; import { enhance } from '$app/forms'; import type { ActionResult } from '@sveltejs/kit'; + import { page } from '$app/stores'; export let onDark = false; export let layout; - export let data: Promise>; + export let data: Promise>; export let text; export let component; export let count; export let limit: number; + $: query = new URLSearchParams($page.url.searchParams.toString()); $: offset = 0; - const handleLoadMore = ({ formData }: { formData: FormData }) => { + const handleLoadMore = async ({ formData }: { formData: FormData }) => { + formData.set('data', JSON.stringify(await data)); formData.set('offset', (offset + limit).toString()); return async ({ result }: { result: ActionResult }) => { if (result.type === 'success' && result.data) { - data = Promise.all([...(await data), ...result.data.items]); + data = result.data.items; offset = offset + limit; } }; @@ -48,7 +51,7 @@
{#if data.length < count} -
+
- {:then data} + {:then}
- {#each data as item} + {#each items as item} {/each}
- {#if data.length < count} + {#if items.length < count}
{:catch} From 32858090bd62f2216b850ed58e49e33c72349854 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:12:38 +0800 Subject: [PATCH 08/21] fix: apply Await fixes for /news --- website-frontend/src/routes/news/+page.server.ts | 12 +++++++----- website-frontend/src/routes/news/+page.svelte | 3 +-- website-frontend/src/routes/news/+page.ts | 1 - .../src/routes/news/[slug]/+page.server.ts | 12 +++++++----- website-frontend/src/routes/news/[slug]/+page.svelte | 3 +-- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/website-frontend/src/routes/news/+page.server.ts b/website-frontend/src/routes/news/+page.server.ts index 81b42891..be429914 100644 --- a/website-frontend/src/routes/news/+page.server.ts +++ b/website-frontend/src/routes/news/+page.server.ts @@ -6,7 +6,7 @@ import { News } from '$lib/models/news'; import { NewsOverview } from '$lib/models/news_overview.js'; import type { Actions } from './$types'; -const news_limit = 12; +const limit = 12; export async function load({ fetch }) { const directus = getDirectusInstance(fetch); @@ -39,18 +39,19 @@ export async function load({ fetch }) { } ], sort: ['-date_created'], - limit: news_limit + limit }) ) ); - return { news_overview, news_limit, news_count, news }; + return { news_overview, news_count, news }; } export const actions = { loadMore: async ({ request, fetch }) => { const data = await request.formData(); const directus = getDirectusInstance(fetch); + const offset = parseInt((data.get('offset') ?? '0') as string) + limit; const news = parse( News, await directus.request( @@ -72,8 +73,8 @@ export const actions = { } ], sort: ['-date_created'], - offset: parseInt((data.get('offset') ?? '0') as string), - limit: news_limit + offset, + limit }) ) ); @@ -81,6 +82,7 @@ export const actions = { const items = [...JSON.parse(data.get('data') as string), ...news]; return { success: true, + offset, items }; } diff --git a/website-frontend/src/routes/news/+page.svelte b/website-frontend/src/routes/news/+page.svelte index 4c1e0267..4000e2cb 100644 --- a/website-frontend/src/routes/news/+page.svelte +++ b/website-frontend/src/routes/news/+page.svelte @@ -7,7 +7,7 @@ export let data; - $: ({ news_overview, news_limit, news_count, news } = data); + $: ({ news_overview, news_count, news } = data); @@ -25,7 +25,6 @@ text="news" component={NewsCard} count={news_count} - limit={news_limit} />
diff --git a/website-frontend/src/routes/news/+page.ts b/website-frontend/src/routes/news/+page.ts index 110d7348..1114c213 100644 --- a/website-frontend/src/routes/news/+page.ts +++ b/website-frontend/src/routes/news/+page.ts @@ -2,7 +2,6 @@ export async function load({ data }) { return { news_overview: data.news_overview, - news_limit: data.news_limit, news_count: data.news_count, news: data.news }; diff --git a/website-frontend/src/routes/news/[slug]/+page.server.ts b/website-frontend/src/routes/news/[slug]/+page.server.ts index e5edd4d4..3e97b47a 100644 --- a/website-frontend/src/routes/news/[slug]/+page.server.ts +++ b/website-frontend/src/routes/news/[slug]/+page.server.ts @@ -6,7 +6,7 @@ import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; import { News, NewsItem } from '$lib/models/news'; import type { Actions } from './$types'; -const other_news_limit = 12; +const limit = 12; export async function load({ url, params, fetch }) { const directus = getDirectusInstance(fetch); @@ -88,20 +88,21 @@ export async function load({ url, params, fetch }) { } }, sort: ['-date_created'], - limit: other_news_limit + limit }) ) ); const link = new URL(url.toString()).toString(); - return { link, other_news_limit, other_news_count, other_news, news_item }; + return { link, other_news_count, other_news, news_item }; } export const actions = { loadMore: async ({ request, params, fetch }) => { const data = await request.formData(); const directus = getDirectusInstance(fetch); + const offset = parseInt((data.get('offset') ?? '0') as string) + limit; const other_news = parse( News, await directus.request( @@ -128,8 +129,8 @@ export const actions = { } }, sort: ['-date_created'], - offset: parseInt((data.get('offset') ?? '0') as string), - limit: other_news_limit + offset, + limit }) ) ); @@ -137,6 +138,7 @@ export const actions = { const items = [...JSON.parse(data.get('data') as string), ...other_news]; return { success: true, + offset, items }; } diff --git a/website-frontend/src/routes/news/[slug]/+page.svelte b/website-frontend/src/routes/news/[slug]/+page.svelte index c9d080e5..81336967 100644 --- a/website-frontend/src/routes/news/[slug]/+page.svelte +++ b/website-frontend/src/routes/news/[slug]/+page.svelte @@ -10,7 +10,7 @@ export let data; let banner_height: number; - $: ({ link, other_news_limit, other_news_count, other_news, news_item } = data); + $: ({ link, other_news_count, other_news, news_item } = data); $: news_tags = news_item.news_tags ? news_item.news_tags @@ -110,7 +110,6 @@ text="other news" component={NewsCard} count={other_news_count} - limit={other_news_limit} />
From 2dd00c417caa480de1934238e723c05d76c092a8 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:40:57 +0800 Subject: [PATCH 09/21] fix: remove offset dependency in Await component --- website-frontend/src/lib/components/loading/Await.svelte | 4 ---- 1 file changed, 4 deletions(-) diff --git a/website-frontend/src/lib/components/loading/Await.svelte b/website-frontend/src/lib/components/loading/Await.svelte index d2013e25..e736d096 100644 --- a/website-frontend/src/lib/components/loading/Await.svelte +++ b/website-frontend/src/lib/components/loading/Await.svelte @@ -14,18 +14,14 @@ export let count; let items: object[] = []; - let offset: number = 0; $: query = new URLSearchParams($page.url.searchParams.toString()); - $: offset = 0; $: data.then((res) => (items = res)); const handleLoadMore = ({ formData }: { formData: FormData }) => { formData.set('data', JSON.stringify(items)); - formData.set('offset', offset.toString()); return async ({ result }: { result: ActionResult }) => { if (result.type === 'success' && result.data) { items = result.data.items; - offset = result.data.offset; } }; }; From a8fdb6e655429dd257daf5bd0a7ea5141e6dd797 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:41:56 +0800 Subject: [PATCH 10/21] fix: remove offset form dependency for server actions in /news --- website-frontend/src/routes/news/+page.server.ts | 7 +++---- website-frontend/src/routes/news/[slug]/+page.server.ts | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/website-frontend/src/routes/news/+page.server.ts b/website-frontend/src/routes/news/+page.server.ts index be429914..c6aa1549 100644 --- a/website-frontend/src/routes/news/+page.server.ts +++ b/website-frontend/src/routes/news/+page.server.ts @@ -50,8 +50,8 @@ export async function load({ fetch }) { export const actions = { loadMore: async ({ request, fetch }) => { const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); const directus = getDirectusInstance(fetch); - const offset = parseInt((data.get('offset') ?? '0') as string) + limit; const news = parse( News, await directus.request( @@ -73,16 +73,15 @@ export const actions = { } ], sort: ['-date_created'], - offset, + offset: current.length, limit }) ) ); - const items = [...JSON.parse(data.get('data') as string), ...news]; + const items = [...current, ...news]; return { success: true, - offset, items }; } diff --git a/website-frontend/src/routes/news/[slug]/+page.server.ts b/website-frontend/src/routes/news/[slug]/+page.server.ts index 3e97b47a..7c55c5c4 100644 --- a/website-frontend/src/routes/news/[slug]/+page.server.ts +++ b/website-frontend/src/routes/news/[slug]/+page.server.ts @@ -101,8 +101,8 @@ export async function load({ url, params, fetch }) { export const actions = { loadMore: async ({ request, params, fetch }) => { const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); const directus = getDirectusInstance(fetch); - const offset = parseInt((data.get('offset') ?? '0') as string) + limit; const other_news = parse( News, await directus.request( @@ -129,16 +129,15 @@ export const actions = { } }, sort: ['-date_created'], - offset, + offset: current.length, limit }) ) ); - const items = [...JSON.parse(data.get('data') as string), ...other_news]; + const items = [...current, ...other_news]; return { success: true, - offset, items }; } From 0e62d919b6d316b3597fe90af09fb95a942f94a4 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 20:37:48 +0800 Subject: [PATCH 11/21] feat: use Await loadMore for publications --- .../research/publications/+page.server.ts | 188 +++++++++++++++--- .../routes/research/publications/+page.svelte | 65 +++--- .../src/routes/research/publications/+page.ts | 5 +- 3 files changed, 192 insertions(+), 66 deletions(-) diff --git a/website-frontend/src/routes/research/publications/+page.server.ts b/website-frontend/src/routes/research/publications/+page.server.ts index af04e2f8..30da9f49 100644 --- a/website-frontend/src/routes/research/publications/+page.server.ts +++ b/website-frontend/src/routes/research/publications/+page.server.ts @@ -1,11 +1,14 @@ /** @type {import('./$types').PageServerLoad} */ -import { readItem, readItems, readSingleton } from '@directus/sdk'; +import { aggregate, readItems, readSingleton } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; -import { parse } from 'valibot'; +import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; import { Publications } from '$lib/models/publications'; import { Laboratories } from '$lib/models/laboratories'; import { PublicationsTags } from '$lib/models/publications_tags.js'; import { Research } from '$lib/models/research'; +import type { Actions } from '@sveltejs/kit'; + +const limit = 12; export async function load({ fetch, url }) { const directus = getDirectusInstance(fetch); @@ -18,6 +21,10 @@ export async function load({ fetch, url }) { tags: url.searchParams.getAll('tags') }; + const people = new Map( + (await directus.request(readItems('people'))).map((person) => [person.id, person]) + ); + const years_filters = await (async () => { const res = parse( Publications, @@ -60,10 +67,31 @@ export async function load({ fetch, url }) { ) ).map(({ name }) => name); - const publications = await (async () => { - const res = parse( - Publications, - await directus.request( + const publications_count = await directus + .request( + aggregate('publications', { + aggregate: { count: '*' }, + query: { + filter: { + 'year(publish_date)': { _in: filters.years.length !== 0 ? filters.years : undefined }, + laboratory: { + name: { _in: filters.laboratories.length !== 0 ? filters.laboratories : undefined } + }, + publication_tags: { + publications_tags_id: { + name: { _in: filters.tags.length !== 0 ? filters.tags : undefined } + } + } + } + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + + const publications = parseAsync( + pipeAsync(promise(), awaitAsync(), Publications), + directus + .request( readItems('publications', { fields: [ '*', @@ -86,33 +114,131 @@ export async function load({ fetch, url }) { } } }, - sort: ['-publish_date'] + sort: ['-publish_date'], + limit }) ) - ); - - return await Promise.all( - res.map(async (item) => { - return await { - ...item, - authors: await Promise.all( - item.authors - ? item.authors.map(async (author) => { - if (typeof author.link === 'undefined') return author; - if (typeof author.link === 'object') { - const person = await directus.request(readItem('people', author.link.key)); - return { - ...author, - link: `/people/${person.username}` - }; - } - }) - : [] + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = people.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) ) - }; - }) - ); - })(); + ) + ); - return { research, publications, years_filters, laboratories_filters, tags_filters }; + return { + research, + years_filters, + laboratories_filters, + tags_filters, + publications_count, + publications + }; } + +export const actions = { + loadMore: async ({ request, fetch, url }) => { + const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); + const directus = getDirectusInstance(fetch); + const filters = { + years: url.searchParams.getAll('year'), + laboratories: url.searchParams.getAll('laboratory'), + tags: url.searchParams.getAll('tags') + }; + const people = new Map( + (await directus.request(readItems('people'))).map((person) => [person.id, person]) + ); + const publications = parse( + Publications, + await directus + .request( + readItems('publications', { + fields: [ + '*', + { + publication_tags: [ + { + publications_tags_id: ['name'] + } + ] + } + ], + filter: { + 'year(publish_date)': { _in: filters.years.length !== 0 ? filters.years : undefined }, + laboratory: { + name: { _in: filters.laboratories.length !== 0 ? filters.laboratories : undefined } + }, + publication_tags: { + publications_tags_id: { + name: { _in: filters.tags.length !== 0 ? filters.tags : undefined } + } + } + }, + sort: ['-publish_date'], + offset: current.length, + limit + }) + ) + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = people.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) + ) + ) + ); + const items = [...current, ...publications].sort((a, b) => { + if ((url.searchParams.get('sort') ?? '') === 'author') { + const aLastName = a.authors[0]?.last_name || ''; + const bLastName = b.authors[0]?.last_name || ''; + return aLastName.localeCompare(bLastName); + } else if ((url.searchParams.get('sort') ?? '') === 'title') { + const a_title = a.title || ''; + const b_title = b.title || ''; + return a_title.localeCompare(b_title); + } else { + return (b.publish_date ?? '1970-01-01').localeCompare(a.publish_date ?? '1970-01-01'); + } + }); + return { + success: true, + items + }; + } +} satisfies Actions; diff --git a/website-frontend/src/routes/research/publications/+page.svelte b/website-frontend/src/routes/research/publications/+page.svelte index aee27199..c97b9ace 100644 --- a/website-frontend/src/routes/research/publications/+page.svelte +++ b/website-frontend/src/routes/research/publications/+page.svelte @@ -1,20 +1,26 @@ @@ -62,16 +52,25 @@ -
- {#each publicationsList as publication} - - {/each} -
- {#if shown < publications.length} -
- -
- {/if} + + res.sort((a, b) => { + if (($page.url.searchParams.get('sort') ?? '') === 'author') { + const aLastName = a.authors ? (a.authors[0].last_name ?? '') : ''; + const bLastName = b.authors ? (b.authors[0].last_name ?? '') : ''; + return aLastName.localeCompare(bLastName); + } else if (($page.url.searchParams.get('sort') ?? '') === 'title') { + const a_title = a.title || ''; + const b_title = b.title || ''; + return a_title.localeCompare(b_title); + } else { + return (b.publish_date ?? '1970-01-01').localeCompare(a.publish_date ?? '1970-01-01'); + } + }) + )} + text="publications" + component={PublicationCard} + count={publications_count} + /> diff --git a/website-frontend/src/routes/research/publications/+page.ts b/website-frontend/src/routes/research/publications/+page.ts index c703b3fb..542c24c8 100644 --- a/website-frontend/src/routes/research/publications/+page.ts +++ b/website-frontend/src/routes/research/publications/+page.ts @@ -2,9 +2,10 @@ export async function load({ data }) { return { research: data.research, - publications: data.publications, years_filters: data.years_filters, laboratories_filters: data.laboratories_filters, - tags_filters: data.tags_filters + tags_filters: data.tags_filters, + publications_count: data.publications_count, + publications: data.publications }; } From 5a1470928b6e5336f664afdafc6263e49428e728 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 20:38:52 +0800 Subject: [PATCH 12/21] feat: enhance PublicationsCard display for mobile --- .../components/cards/PublicationCard.svelte | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/website-frontend/src/lib/components/cards/PublicationCard.svelte b/website-frontend/src/lib/components/cards/PublicationCard.svelte index 99af21fe..e95d5346 100644 --- a/website-frontend/src/lib/components/cards/PublicationCard.svelte +++ b/website-frontend/src/lib/components/cards/PublicationCard.svelte @@ -85,33 +85,35 @@ {/if} {#if item.authors} - {#if item.authors.length !== 1} - Authors: - {:else} - Author: - {/if} - {#each item.authors as author, i} - {#if author.link && typeof author.link === 'string'} - { - $reloading = true; - }} - >{`${author.last_name}, ${author.first_name}`} - +
+ {#if item.authors.length !== 1} + Authors: {:else} - {`${author.last_name}, ${author.first_name}`} - {/if} - {#if i + 1 !== item.authors.length} - {` & `} + Author: {/if} - {/each} + {#each item.authors as author, i} + {#if author.link && typeof author.link === 'string'} + { + $reloading = true; + }} + >{`${author.last_name}, ${author.first_name}`} + + {:else} + {`${author.last_name}, ${author.first_name}`} + {/if} + {#if i + 1 !== item.authors.length} + {` & `} + {/if} + {/each} +
{/if} {#if item.abstract} -
+
Abstract: {item.abstract}
{/if} From fca0ce67988bc539a4492c61847df8ffeefb618e Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 21:46:25 +0800 Subject: [PATCH 13/21] fix: use number model as base model for PeopleLevels --- website-frontend/src/lib/models/junctions/people_related.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website-frontend/src/lib/models/junctions/people_related.ts b/website-frontend/src/lib/models/junctions/people_related.ts index 6066df35..487dee59 100644 --- a/website-frontend/src/lib/models/junctions/people_related.ts +++ b/website-frontend/src/lib/models/junctions/people_related.ts @@ -1,17 +1,17 @@ -import { array, lazy, object, partial, string, union, type GenericSchema } from 'valibot'; +import { array, lazy, number, object, partial, string, union, type GenericSchema } from 'valibot'; import { Person } from '../people'; import { Level } from '../people_levels'; export type PeopleRelated = { people_id?: string | Person; - people_levels_id?: string | Level; + people_levels_id?: number | Level; }[]; export const PeopleRelated: GenericSchema = array( partial( object({ people_id: union([string(), lazy(() => Person)]), - people_levels_id: union([string(), lazy(() => Level)]) + people_levels_id: union([number(), lazy(() => Level)]) }) ) ); From fd82e407ca81378f0b5eee465dfc3dc11b897343 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sat, 19 Jul 2025 21:47:00 +0800 Subject: [PATCH 14/21] fix: use span for displaying people position and laboratory --- .../src/lib/components/banners/PeopleBanner.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website-frontend/src/lib/components/banners/PeopleBanner.svelte b/website-frontend/src/lib/components/banners/PeopleBanner.svelte index cd70f4de..3dca6e55 100644 --- a/website-frontend/src/lib/components/banners/PeopleBanner.svelte +++ b/website-frontend/src/lib/components/banners/PeopleBanner.svelte @@ -65,13 +65,13 @@
-
-

{position}

+ + {position} {#if laboratory} -

·

-

{laboratory}

+ · + {laboratory} {/if} -
+
From 468464eafbf9be4b73fea5945bcf0927973054e1 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:09:18 +0800 Subject: [PATCH 15/21] chore: set null default for null layout --- website-frontend/src/lib/components/loading/Await.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website-frontend/src/lib/components/loading/Await.svelte b/website-frontend/src/lib/components/loading/Await.svelte index e736d096..ad6fef09 100644 --- a/website-frontend/src/lib/components/loading/Await.svelte +++ b/website-frontend/src/lib/components/loading/Await.svelte @@ -7,7 +7,7 @@ import { type ActionResult } from '@sveltejs/kit'; export let onDark = false; - export let layout; + export let layout = ''; export let data: Promise; export let text; export let component; From db66774ffdcb384d29854a3aaee661b7258c1194 Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:10:15 +0800 Subject: [PATCH 16/21] feat: add specific SearchResult components --- .../search/EventsSearchResult.svelte | 36 ++++ .../search/LaboratoriesSearchResult.svelte | 36 ++++ .../components/search/NewsSearchResult.svelte | 36 ++++ .../search/PeopleSearchResult.svelte | 47 +++++ .../search/PublicationsSearchResult.svelte | 143 +++++++++++++++ .../lib/components/search/SearchResult.svelte | 167 ------------------ 6 files changed, 298 insertions(+), 167 deletions(-) create mode 100644 website-frontend/src/lib/components/search/EventsSearchResult.svelte create mode 100644 website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte create mode 100644 website-frontend/src/lib/components/search/NewsSearchResult.svelte create mode 100644 website-frontend/src/lib/components/search/PeopleSearchResult.svelte create mode 100644 website-frontend/src/lib/components/search/PublicationsSearchResult.svelte delete mode 100644 website-frontend/src/lib/components/search/SearchResult.svelte diff --git a/website-frontend/src/lib/components/search/EventsSearchResult.svelte b/website-frontend/src/lib/components/search/EventsSearchResult.svelte new file mode 100644 index 00000000..e41b8408 --- /dev/null +++ b/website-frontend/src/lib/components/search/EventsSearchResult.svelte @@ -0,0 +1,36 @@ + + + +
+ {#if item.hero_image} +
+ {#if general_banner_height} + {item.event_headline} + {/if} +
+ {/if} + +
+

{item.event_headline}

+ {#if item.event_content} +

{@html item.event_content}

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte b/website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte new file mode 100644 index 00000000..2779f5b8 --- /dev/null +++ b/website-frontend/src/lib/components/search/LaboratoriesSearchResult.svelte @@ -0,0 +1,36 @@ + + + +
+ {#if item.logo} +
+ {#if general_banner_height} + {item.name} + {/if} +
+ {/if} + +
+

{item.name}

+ {#if item.brief_description} +

{item.brief_description}

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/NewsSearchResult.svelte b/website-frontend/src/lib/components/search/NewsSearchResult.svelte new file mode 100644 index 00000000..0206e461 --- /dev/null +++ b/website-frontend/src/lib/components/search/NewsSearchResult.svelte @@ -0,0 +1,36 @@ + + + +
+ {#if item.background_image} +
+ {#if general_banner_height} + {item.title} + {/if} +
+ {/if} + +
+

{item.title}

+ {#if item.summary} +

{item.summary}

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/PeopleSearchResult.svelte b/website-frontend/src/lib/components/search/PeopleSearchResult.svelte new file mode 100644 index 00000000..90edb906 --- /dev/null +++ b/website-frontend/src/lib/components/search/PeopleSearchResult.svelte @@ -0,0 +1,47 @@ + + + +
+ {#if item.profile_image} +
+ {#if general_banner_height} + {`${item.first_name} + {/if} +
+ {/if} + +
+

{`${item.first_name} ${item.last_name}`}

+ {#if item.position} +

+ {`${item.position} ${laboratory ? ` · ${laboratory}` : ``}`} +

+ {/if} +
+
+
diff --git a/website-frontend/src/lib/components/search/PublicationsSearchResult.svelte b/website-frontend/src/lib/components/search/PublicationsSearchResult.svelte new file mode 100644 index 00000000..2db43c70 --- /dev/null +++ b/website-frontend/src/lib/components/search/PublicationsSearchResult.svelte @@ -0,0 +1,143 @@ + + + + +
+ {#if item.hero_image} +
+ {#if pubs_banner_height} + {item.title} + {/if} +
+ {/if} + +
+

{item.title}

+ {#if item.abstract} +

+ {item.abstract} +

+ {/if} +
+
+
+ + + {item.title} + + {#if publications_tags.length !== 0} +
+ + {#each publications_tags.slice(0, -1) as publications_tag} + {`${publications_tag}, `} + {/each} + {publications_tags.at(-1)} + +
+ {/if} +
+ {#if item.publish_date} +
+ Date published: + {new Date(item.publish_date).toLocaleDateString('en-EN', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} +
+ {/if} + {#if item.authors} + {#if item.authors.length !== 1} + Authors: + {:else} + Author: + {/if} + {#each item.authors as author, i} + {#if author.link && typeof author.link === 'string'} + { + $reloading = true; + }} + >{`${author.last_name}, ${author.first_name}`} + + {:else} + {`${author.last_name}, ${author.first_name}`} + {/if} + {#if i + 1 !== item.authors.length} + {` & `} + {/if} + {/each} + {/if} +
+ {#if item.abstract} +
+ Abstract: {item.abstract} +
+ {/if} + {#if item.access_links} +
+ + {#if item.access_links.length !== 1} + Access Links: + {:else} + Access Link: + {/if} + {#each item.access_links as access_link, i} + + {/each} + +
+ {/if} +
+
+
+
diff --git a/website-frontend/src/lib/components/search/SearchResult.svelte b/website-frontend/src/lib/components/search/SearchResult.svelte deleted file mode 100644 index eac8f919..00000000 --- a/website-frontend/src/lib/components/search/SearchResult.svelte +++ /dev/null @@ -1,167 +0,0 @@ - - -{#if !publication} - -
-
- {#if image && general_banner_height} - {name} - {/if} -
- -
- -
-
-
-{:else} - - -
-
- {#if image && pubs_banner_height} - {name} - {/if} -
- -
- -
-
-
- - - {publication.title} - - {#if publications_tags.length !== 0} -
- - {#each publications_tags.slice(0, -1) as publications_tag} - {`${publications_tag}, `} - {/each} - {publications_tags.at(-1)} - -
- {/if} -
- {#if publication.publish_date} -
- Date published: - {new Date(publication.publish_date).toLocaleDateString('en-EN', { - year: 'numeric', - month: 'long', - day: 'numeric' - })} -
- {/if} - {#if publication.authors} - {#if publication.authors.length !== 1} - Authors: - {:else} - Author: - {/if} - {#each publication.authors as author, i} - {#if author.link && typeof author.link === 'string'} - { - $reloading = true; - }} - >{`${author.last_name}, ${author.first_name}`} - - {:else} - {`${author.last_name}, ${author.first_name}`} - {/if} - {#if i + 1 !== publication.authors.length} - {` & `} - {/if} - {/each} - {/if} -
- {#if publication.abstract} -
- Abstract: {publication.abstract} -
- {/if} - {#if publication.access_links} -
- - {#if publication.access_links.length !== 1} - Access Links: - {:else} - Access Link: - {/if} - {#each publication.access_links as access_link, i} - - {/each} - -
- {/if} -
-
-
-
-{/if} From e0239492fbe877b9a57354117dc1c96255662b7e Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:10:50 +0800 Subject: [PATCH 17/21] chore: rename Peoples type to People --- website-frontend/src/lib/models/people.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website-frontend/src/lib/models/people.ts b/website-frontend/src/lib/models/people.ts index dbd69042..33e023f2 100644 --- a/website-frontend/src/lib/models/people.ts +++ b/website-frontend/src/lib/models/people.ts @@ -58,4 +58,4 @@ export const People = array(Person); export type EducationalAttainment = InferOutput; export type Person = InferOutput; -export type Peoples = InferOutput; +export type People = InferOutput; From 45a101bca0f6864155d800dbd4edd385890072fb Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:11:52 +0800 Subject: [PATCH 18/21] feat: use Await and lazy loading for loading search results --- .../src/routes/search/+page.server.ts | 290 ++++++++++++++--- .../src/routes/search/+page.svelte | 296 +++++++++--------- website-frontend/src/routes/search/+page.ts | 5 + 3 files changed, 391 insertions(+), 200 deletions(-) diff --git a/website-frontend/src/routes/search/+page.server.ts b/website-frontend/src/routes/search/+page.server.ts index 6c6c6c0e..a1cb2fff 100644 --- a/website-frontend/src/routes/search/+page.server.ts +++ b/website-frontend/src/routes/search/+page.server.ts @@ -1,53 +1,113 @@ /** @type {import('./$types').PageServerLoad} */ -import { readItem, readItems } from '@directus/sdk'; +import { aggregate, readItems } from '@directus/sdk'; import getDirectusInstance from '$lib/directus'; -import { parse } from 'valibot'; -import { News } from '$lib/models/news'; -import { Events } from '$lib/models/events'; -import { People } from '$lib/models/people'; -import { Laboratories } from '$lib/models/laboratories'; -import { Publications } from '$lib/models/publications'; +import { awaitAsync, parse, parseAsync, pipeAsync, promise } from 'valibot'; +import { News, NewsItem } from '$lib/models/news'; +import { Events, Event } from '$lib/models/events'; +import { People, Person } from '$lib/models/people'; +import { Laboratories, Laboratory } from '$lib/models/laboratories'; +import { Publication, Publications } from '$lib/models/publications'; +import type { Actions } from '@sveltejs/kit'; + +const limit = 6; export async function load({ fetch, url }) { const directus = getDirectusInstance(fetch); - const news = parse( - News, - await directus.request( + const news_count = await directus + .request( + aggregate('news', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const news = parseAsync( + pipeAsync(promise(), awaitAsync(), News), + directus.request( readItems('news', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const events = parse( - Events, - await directus.request( + const events_count = await directus + .request( + aggregate('events', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const events = parseAsync( + pipeAsync(promise(), awaitAsync(), Events), + directus.request( readItems('events', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const people = parse( - People, - await directus.request( + const people_count = await directus + .request( + aggregate('people', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const people = parseAsync( + pipeAsync(promise(), awaitAsync(), People), + directus.request( readItems('people', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const laboratories = parse( - Laboratories, - await directus.request( + const laboratories_count = await directus + .request( + aggregate('laboratories', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const laboratories = parseAsync( + pipeAsync(promise(), awaitAsync(), Laboratories), + directus.request( readItems('laboratories', { - search: url.searchParams.get('q') ?? undefined + search: url.searchParams.get('q') ?? undefined, + limit }) ) ); - const publications = await (async () => { - const publications = parse( - Publications, - await directus.request( + const publications_count = await directus + .request( + aggregate('publications', { + aggregate: { count: '*' }, + query: { + search: url.searchParams.get('q') ?? undefined + } + }) + ) + .then((res) => parseInt(res[0].count ?? '0')); + const peopleMap = new Map((await people).map((person) => [person.id, person])); + const publications = parseAsync( + pipeAsync(promise(), awaitAsync(), Publications), + directus + .request( readItems('publications', { search: url.searchParams.get('q') ?? undefined, + sort: ['-publish_date'], + limit, fields: [ '*', { @@ -60,32 +120,38 @@ export async function load({ fetch, url }) { ] }) ) - ); - - return await Promise.all( - publications.map(async (item) => { - return await { - ...item, - authors: await Promise.all( - item.authors - ? item.authors.map(async (author) => { - if (typeof author.link === 'undefined') return author; - if (typeof author.link === 'object') { - const person = await directus.request(readItem('people', author.link.key)); - return { - ...author, - link: `/people/${person.username}` - }; - } - }) - : [] + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = peopleMap.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) ) - }; - }) - ); - })(); + ) + ); return { + news_count, + events_count, + people_count, + laboratories_count, + publications_count, news, events, people, @@ -93,3 +159,127 @@ export async function load({ fetch, url }) { publications }; } + +export const actions = { + loadMore: async ({ request, fetch, url }) => { + const tab = url.searchParams.get('tab') ?? 'News'; + const data = await request.formData(); + const current = JSON.parse(data.get('data') as string); + const directus = getDirectusInstance(fetch); + let news: News = []; + let events: Events = []; + let people: People = []; + let laboratories: Laboratories = []; + let publications: Publications = []; + let items: (NewsItem | Event | Person | Laboratory | Publication)[] = []; + if (tab == 'News') { + news = parse( + News, + await directus.request( + readItems('news', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...news]; + } + if (tab == 'Events') { + events = parse( + Events, + await directus.request( + readItems('events', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...events]; + } + if (tab == 'People') { + people = parse( + People, + await directus.request( + readItems('people', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...people]; + } + if (tab == 'Laboratories') { + laboratories = parse( + Laboratories, + await directus.request( + readItems('laboratories', { + search: url.searchParams.get('q') ?? undefined, + offset: current.length, + limit + }) + ) + ); + items = [...current, ...laboratories]; + } + if (tab == 'Publications') { + const people = new Map( + (await directus.request(readItems('people'))).map((person) => [person.id, person]) + ); + publications = parse( + Publications, + await directus + .request( + readItems('publications', { + search: url.searchParams.get('q') ?? undefined, + sort: ['-publish_date'], + offset: current.length, + limit, + fields: [ + '*', + { + publication_tags: [ + { + publications_tags_id: ['name'] + } + ] + } + ] + }) + ) + .then( + async (res) => + await Promise.all( + res.map(async (item) => { + return { + ...item, + authors: await Promise.all( + item.authors + ? item.authors.map(async (author) => { + if (typeof author.link === 'undefined') return author; + if (typeof author.link === 'object') { + const person = people.get(author.link.key); + return { + ...author, + link: `/people/${person ? (person.username ?? '') : ''}` + }; + } + }) + : [] + ) + }; + }) + ) + ) + ); + items = [...current, ...publications]; + } + + return { + success: true, + items + }; + } +} satisfies Actions; diff --git a/website-frontend/src/routes/search/+page.svelte b/website-frontend/src/routes/search/+page.svelte index d72e9b40..ca39b664 100644 --- a/website-frontend/src/routes/search/+page.svelte +++ b/website-frontend/src/routes/search/+page.svelte @@ -3,56 +3,94 @@ import * as Tabs from '$lib/@shadcn-svelte/ui/tabs'; import { Search } from 'lucide-svelte'; - import SearchResult from '$lib/components/search/SearchResult.svelte'; - import { ScrollArea } from '$lib/@shadcn-svelte/ui/scroll-area/index.js'; import { page } from '$app/stores'; + import Await from '$lib/components/loading/Await.svelte'; + import { goto } from '$app/navigation'; + import NewsSearchResult from '$lib/components/search/NewsSearchResult.svelte'; + import EventsSearchResult from '$lib/components/search/EventsSearchResult.svelte'; + import PeopleSearchResult from '$lib/components/search/PeopleSearchResult.svelte'; + import LaboratoriesSearchResult from '$lib/components/search/LaboratoriesSearchResult.svelte'; + import PublicationsSearchResult from '$lib/components/search/PublicationsSearchResult.svelte'; export let data; - let tabs; + const layout = 'lg:w-3/4'; - $: ({ news, events, people, laboratories, publications } = data); + $: ({ + news_count, + events_count, + people_count, + laboratories_count, + publications_count, + news, + events, + people, + laboratories, + publications + } = data); + $: query = new URLSearchParams($page.url.searchParams.toString()); + $: value = + query.get('tab') ?? + (news_count > 0 + ? 'News' + : events_count > 0 + ? 'Events' + : people_count > 0 + ? 'People' + : laboratories_count > 0 + ? 'Laboratories' + : publications_count > 0 + ? 'Publications' + : 'News'); $: tabs = [ - { - tab: 'All', - res: news.length + events.length + people.length + laboratories.length + publications.length - }, + // { + // tab: 'All', + // res: news_count + events_count + people_count + laboratories_count + publications_count + // }, { tab: 'News', - res: news.length + res: news_count }, { tab: 'Events', - res: events.length + res: events_count }, { tab: 'People', - res: people.length + res: people_count }, { tab: 'Laboratories', - res: laboratories.length + res: laboratories_count }, { tab: 'Publications', - res: publications.length + res: publications_count } ]; -
- - - - {#each tabs as { tab, res }} - {tab} ({res}) - {/each} - - +
+ { + // if (value == 'All') query.delete('tab'); + // else query.set('tab', value ?? ''); + query.set('tab', value ?? ''); + goto(`?${query.toString()}`, { noScroll: true }); + }} + > + + {#each tabs as { tab, res }} + {tab} ({res}) + {/each} + {#each tabs as { tab, res }} @@ -61,130 +99,87 @@

{res} Search result{#if res !== 1}s{/if} for {$page.url.searchParams.get('q')} - {#if tab !== 'All'} + in {tab} +

- - {#if tab === 'All'} - {#each news as { slug, title, summary, background_image }} - -

{title}

- {#if summary} -

{@html summary}

- {/if} -
- {/each} - {#each events as { slug, event_headline, event_content, hero_image }} - -

{event_headline}

- {#if event_content} -

{@html event_content}

- {/if} -
- {/each} - {#each people as { username, first_name, last_name, profile_image, position }} - -

{first_name} {last_name}

- {#if position} -

{position}

- {/if} -
- {/each} - {#each laboratories as { slug, name, description, logo }} - -

{name}

- {#if description} -

{description}

- {/if} -
- {/each} - {#each publications as publication} - -

{publication.title}

- {#if publication.authors.length > 0} -

- {#each publication.authors as { last_name }, i} - {last_name}{#if i + 1 !== publication.authors.length}{`, `}{/if} - {/each} -

- {/if} - {#if publication.abstract} -

{publication.abstract}

- {/if} -
- {/each} - {:else if tab === 'News'} - {#each news as { slug, title, summary, background_image }} - -

{title}

- {#if summary} -

{@html summary}

- {/if} -
- {/each} - {:else if tab === 'Events'} - {#each events as { slug, event_headline, event_content, hero_image }} - -

{event_headline}

- {#if event_content} -

{@html event_content}

- {/if} -
- {/each} - {:else if tab === 'People'} - {#each people as { username, first_name, last_name, profile_image, position }} - -

{first_name} {last_name}

- {#if position} -

{position}

- {/if} -
- {/each} - {:else if tab === 'Laboratories'} - {#each laboratories as { slug, name, description, logo }} - -

{name}

- {#if description} -

{description}

- {/if} -
- {/each} - {:else if tab === 'Publications'} - {#each publications as publication} - -

{publication.title}

- {#if publication.authors.length > 0} -

- {#each publication.authors as { last_name }, i} - {last_name}{#if i + 1 !== publication.authors.length}{`, `}{/if} - {/each} -

- {/if} - {#if publication.abstract} -

{publication.abstract}

- {/if} -
- {/each} + + {#if tab === 'News'} + + {/if} + {#if tab === 'Events'} + + {/if} + {#if tab === 'People'} + + {/if} + {#if tab === 'Laboratories'} + + {/if} + {#if tab === 'Publications'} + {/if} {:else}
@@ -192,12 +187,13 @@
-

+

{#if $page.url.searchParams.get('q')} No search results for {$page.url.searchParams.get('q')} - {#if tab !== 'All'} + in {tab} + {:else} Please provide a search input {/if} diff --git a/website-frontend/src/routes/search/+page.ts b/website-frontend/src/routes/search/+page.ts index a7ad3ae8..3ce1e056 100644 --- a/website-frontend/src/routes/search/+page.ts +++ b/website-frontend/src/routes/search/+page.ts @@ -1,6 +1,11 @@ /** @type {import('./$types').PageLoad} */ export async function load({ data }) { return { + news_count: data.news_count, + events_count: data.events_count, + people_count: data.people_count, + laboratories_count: data.laboratories_count, + publications_count: data.publications_count, news: data.news, events: data.events, people: data.people, From 26032aa2d014b34b95e86be6a84df27f4213b5bb Mon Sep 17 00:00:00 2001 From: Aaron Jude Tanael <69955418+eyronjuude@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:32:33 +0800 Subject: [PATCH 19/21] fix: remove buggy $reloading spinner feature --- .../src/lib/components/cards/FeaturedEventCard.svelte | 9 +-------- .../src/lib/components/cards/NewsCard.svelte | 9 +-------- .../src/lib/components/cards/PublicationCard.svelte | 9 +-------- .../src/lib/components/list_items/LaboratoryItem.svelte | 9 +-------- .../lib/components/list_items/OrganizationItem.svelte | 9 +-------- .../components/search/PublicationsSearchResult.svelte | 9 +-------- website-frontend/src/lib/stores.ts | 1 - website-frontend/src/routes/+layout.svelte | 5 +---- 8 files changed, 7 insertions(+), 53 deletions(-) diff --git a/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte b/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte index 3285c581..b0ea8c74 100644 --- a/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte +++ b/website-frontend/src/lib/components/cards/FeaturedEventCard.svelte @@ -2,7 +2,6 @@ import { PUBLIC_APIURL } from '$env/static/public'; import { Event } from '$lib/models/events'; import { Calendar, MapPin, Clock, Image } from 'lucide-svelte'; - import { reloading } from '$lib/stores'; export let item: Event; let hero_height: number; @@ -38,13 +37,7 @@ } - { - $reloading = true; - }} -> +

diff --git a/website-frontend/src/lib/components/cards/NewsCard.svelte b/website-frontend/src/lib/components/cards/NewsCard.svelte index bae4ad10..91435794 100644 --- a/website-frontend/src/lib/components/cards/NewsCard.svelte +++ b/website-frontend/src/lib/components/cards/NewsCard.svelte @@ -3,7 +3,6 @@ import { NewsItem } from '$lib/models/news'; import { Image } from 'lucide-svelte'; import { error } from '@sveltejs/kit'; - import { reloading } from '$lib/stores'; export let item: NewsItem; const news_tags = @@ -17,13 +16,7 @@ let banner_height: number; - { - $reloading = true; - }} -> +
diff --git a/website-frontend/src/lib/components/cards/PublicationCard.svelte b/website-frontend/src/lib/components/cards/PublicationCard.svelte index e95d5346..b3500cba 100644 --- a/website-frontend/src/lib/components/cards/PublicationCard.svelte +++ b/website-frontend/src/lib/components/cards/PublicationCard.svelte @@ -5,7 +5,6 @@ import { PUBLIC_APIURL } from '$env/static/public'; import { Publication } from '$lib/models/publications'; import { SquareArrowOutUpRight } from 'lucide-svelte'; - import { reloading } from '$lib/stores'; export let item: Publication; @@ -93,13 +92,7 @@ {/if} {#each item.authors as author, i} {#if author.link && typeof author.link === 'string'} - { - $reloading = true; - }} + {`${author.last_name}, ${author.first_name}`} {:else} diff --git a/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte b/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte index e08b2767..377f1c47 100644 --- a/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte +++ b/website-frontend/src/lib/components/list_items/LaboratoryItem.svelte @@ -1,7 +1,6 @@ - { - $reloading = true; - }} -> +
import { Image } from 'lucide-svelte'; import { PUBLIC_APIURL } from '$env/static/public'; - import { reloading } from '$lib/stores'; export let organization; let logo_height: number; @@ -9,13 +8,7 @@ $: ({ name, logo, description, website, slug } = organization); - { - $reloading = true; - }} -> +
{ - $reloading = true; - }} + {`${author.last_name}, ${author.first_name}`} {:else} diff --git a/website-frontend/src/lib/stores.ts b/website-frontend/src/lib/stores.ts index b44e8b3d..c75d3d65 100644 --- a/website-frontend/src/lib/stores.ts +++ b/website-frontend/src/lib/stores.ts @@ -1,6 +1,5 @@ import { writable } from 'svelte/store'; -export const reloading = writable(false); export const searchOpen = writable(false); export const mobileOpen = writable(false); export const searchInput = writable(''); diff --git a/website-frontend/src/routes/+layout.svelte b/website-frontend/src/routes/+layout.svelte index c5f2254e..56b62137 100644 --- a/website-frontend/src/routes/+layout.svelte +++ b/website-frontend/src/routes/+layout.svelte @@ -6,14 +6,11 @@ import { fade } from 'svelte/transition'; import { cubicIn, cubicOut } from 'svelte/easing'; import { navigating } from '$app/stores'; - import { reloading } from '$lib/stores'; export let data; let marginType = 'default'; - $: $reloading = false; - $: { if ($page.url.pathname === '/') { marginType = 'wide'; @@ -56,7 +53,7 @@ -{#if $navigating || $reloading} +{#if $navigating}
Date: Sun, 20 Jul 2025 16:43:07 +0800 Subject: [PATCH 20/21] chore: add margin for displaying error message body --- website-frontend/src/routes/+error.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website-frontend/src/routes/+error.svelte b/website-frontend/src/routes/+error.svelte index 1fd65936..7f919232 100644 --- a/website-frontend/src/routes/+error.svelte +++ b/website-frontend/src/routes/+error.svelte @@ -2,7 +2,7 @@ import { page } from '$app/stores'; -
+

{$page.status}: {$page.error?.message}

{/if} {#if item.authors} - {#if item.authors.length !== 1} - Authors: - {:else} - Author: - {/if} - {#each item.authors as author, i} - {#if author.link && typeof author.link === 'string'} - {`${author.last_name}, ${author.first_name}`} - +
+ {#if item.authors.length !== 1} + Authors: {:else} - {`${author.last_name}, ${author.first_name}`} - {/if} - {#if i + 1 !== item.authors.length} - {` & `} + Author: {/if} - {/each} + {#each item.authors as author, i} + {#if author.link && typeof author.link === 'string'} + {`${author.last_name}, ${author.first_name}`} + + {:else} + {`${author.last_name}, ${author.first_name}`} + {/if} + {#if i + 1 !== item.authors.length} + {` & `} + {/if} + {/each} +
{/if}
{#if item.abstract} -
+
Abstract: {item.abstract}
{/if}