From b647f5eef9efd9eecd445bd792af48da415e0bcf Mon Sep 17 00:00:00 2001 From: Jules Date: Sat, 18 Jul 2026 09:12:52 +0000 Subject: [PATCH 1/2] feat: stateful image component with dynamic skeleton loaders for publications --- src/lib/components/ImageLoader.svelte | 51 +++++++ src/lib/helpers/projectsProvider.ts | 134 +++++++++++++++++++ src/lib/types.ts | 16 ++- src/routes/publications/+page.svelte | 7 +- src/routes/publications/[paper]/+page.svelte | 11 +- 5 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 src/lib/components/ImageLoader.svelte diff --git a/src/lib/components/ImageLoader.svelte b/src/lib/components/ImageLoader.svelte new file mode 100644 index 0000000..838cf31 --- /dev/null +++ b/src/lib/components/ImageLoader.svelte @@ -0,0 +1,51 @@ + + +
+ + {#if !loaded && !error} + + {/if} + + + +
diff --git a/src/lib/helpers/projectsProvider.ts b/src/lib/helpers/projectsProvider.ts index 73d9e9f..ea1c8cc 100644 --- a/src/lib/helpers/projectsProvider.ts +++ b/src/lib/helpers/projectsProvider.ts @@ -5,6 +5,134 @@ import Web from 'svelte-material-icons/Web.svelte'; import Youtube from 'svelte-material-icons/Youtube.svelte'; import { FunProject, LinkWithIcon, ResearchProject } from '../types'; +const imageDimensions: Record = { + "images/NAPs.png": { + "width": 1000, + "height": 450 + }, + "images/alphaevolve.png": { + "width": 2043, + "height": 1077 + }, + "images/bert.png": { + "width": 1926, + "height": 830 + }, + "images/breath.png": { + "width": 1200, + "height": 512 + }, + "images/cgvc.png": { + "width": 1600, + "height": 1200 + }, + "images/contagion.png": { + "width": 1024, + "height": 1024 + }, + "images/dotfiles.png": { + "width": 256, + "height": 256 + }, + "images/eot.png": { + "width": 500, + "height": 412 + }, + "images/exploRNN.png": { + "width": 1926, + "height": 992 + }, + "images/fibrils.png": { + "width": 2866, + "height": 1250 + }, + "images/gemini.png": { + "width": 3846, + "height": 1624 + }, + "images/hie.png": { + "width": 1000, + "height": 729 + }, + "images/humboldt.png": { + "width": 1292, + "height": 625 + }, + "images/image_quality.png": { + "width": 923, + "height": 477 + }, + "images/luna.png": { + "width": 1000, + "height": 1000 + }, + "images/midosa.png": { + "width": 1114, + "height": 1114 + }, + "images/mint.png": { + "width": 888, + "height": 538 + }, + "images/mySnow.png": { + "width": 1024, + "height": 1024 + }, + "images/nPMI.png": { + "width": 1596, + "height": 774 + }, + "images/net2vis.png": { + "width": 1916, + "height": 1046 + }, + "images/npmiVIS.png": { + "width": 4000, + "height": 2240 + }, + "images/openhands.png": { + "width": 542, + "height": 362 + }, + "images/other_left.png": { + "width": 678, + "height": 424 + }, + "images/pc-missing-data.png": { + "width": 2500, + "height": 850 + }, + "images/preview.png": { + "width": 1200, + "height": 630 + }, + "images/svelte-vega.png": { + "width": 434, + "height": 336 + }, + "images/symphony.png": { + "width": 291, + "height": 296 + }, + "images/tox.png": { + "width": 357, + "height": 260 + }, + "images/vegaprof.png": { + "width": 2100, + "height": 688 + }, + "images/zeno.png": { + "width": 617, + "height": 615 + }, + "images/vispositions.svg": { + "width": 1000, + "height": 500 + } +}; + + let cachedResearchProjects: ResearchProject[] | null = null; /** @@ -400,6 +528,9 @@ export function getResearchProjects(): ResearchProject[] { ) ]; projects.sort((a, b) => parseInt(b.year) - parseInt(a.year)); + projects.forEach(p => { + if (imageDimensions[p.imageSrc]) p.imageDimensions = imageDimensions[p.imageSrc]; + }); cachedResearchProjects = projects; return projects; } @@ -505,6 +636,9 @@ export function getFunProjects(): FunProject[] { ] ) ]; + projects.forEach(p => { + if (imageDimensions[p.imageSrc]) p.imageDimensions = imageDimensions[p.imageSrc]; + }); cachedFunProjects = projects; return projects; } diff --git a/src/lib/types.ts b/src/lib/types.ts index fbb99d1..2812374 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -24,6 +24,7 @@ export class Project { title: string; abstract: string; imageSrc: string; + imageDimensions?: { width: number; height: number }; links: LinkWithIcon[]; /** * Creates a project @@ -32,11 +33,13 @@ export class Project { * @param {String} abstract the abstract of the project/paper * @param {String} imageSrc the image source for the project * @param {[LinkWithIcon]} links links from the project to more information + * @param {{ width: number; height: number }} imageDimensions dimensions of the image */ - constructor(title: string, abstract: string, imageSrc: string, links: LinkWithIcon[] = []) { + constructor(title: string, abstract: string, imageSrc: string, links: LinkWithIcon[] = [], imageDimensions?: { width: number; height: number }) { this.title = title; this.abstract = abstract; this.imageSrc = imageSrc; + this.imageDimensions = imageDimensions; this.links = links; } } @@ -59,6 +62,7 @@ export class ResearchProject extends Project { * @param {String} venue the venue of the publication * @param {String} imageSRC the image source for the project * @param {[LinkWithIcon]} links links from the project to more information + * @param {{ width: number; height: number }} imageDimensions dimensions of the image */ constructor( title: string, @@ -67,9 +71,10 @@ export class ResearchProject extends Project { year: string, venue: string, imageSRC: string, - links: LinkWithIcon[] = [] + links: LinkWithIcon[] = [], + imageDimensions?: { width: number; height: number } ) { - super(title, abstract, imageSRC, links); + super(title, abstract, imageSRC, links, imageDimensions); this.authors = authors; this.year = year; this.venue = venue; @@ -87,9 +92,10 @@ export class FunProject extends Project { * @param {String} abstract the abstract of the project/paper * @param {String} imageSRC the image source for the project * @param {[LinkWithIcon]} links links from the project to more information + * @param {{ width: number; height: number }} imageDimensions dimensions of the image */ - constructor(title: string, abstract: string, imageSRC: string, links: LinkWithIcon[] = []) { - super(title, abstract, imageSRC, links); + constructor(title: string, abstract: string, imageSRC: string, links: LinkWithIcon[] = [], imageDimensions?: { width: number; height: number }) { + super(title, abstract, imageSRC, links, imageDimensions); } } diff --git a/src/routes/publications/+page.svelte b/src/routes/publications/+page.svelte index d0c1f6b..cab3259 100644 --- a/src/routes/publications/+page.svelte +++ b/src/routes/publications/+page.svelte @@ -1,5 +1,6 @@ -
+
{#if !loaded && !error} - + {/if} @@ -46,6 +38,9 @@ {alt} on:load={handleLoad} on:error={handleError} - class="absolute inset-0 h-full w-full object-contain transition-all duration-700 ease-in-out {loaded && !error ? 'opacity-100' : 'opacity-0'} {imageClassName}" + class="absolute inset-0 h-full w-full object-contain transition-all duration-700 ease-in-out {loaded && + !error + ? 'opacity-100' + : 'opacity-0'} {imageClassName}" />
diff --git a/src/lib/helpers/projectsProvider.ts b/src/lib/helpers/projectsProvider.ts index ea1c8cc..a70ec9e 100644 --- a/src/lib/helpers/projectsProvider.ts +++ b/src/lib/helpers/projectsProvider.ts @@ -6,133 +6,132 @@ import Youtube from 'svelte-material-icons/Youtube.svelte'; import { FunProject, LinkWithIcon, ResearchProject } from '../types'; const imageDimensions: Record = { - "images/NAPs.png": { - "width": 1000, - "height": 450 - }, - "images/alphaevolve.png": { - "width": 2043, - "height": 1077 - }, - "images/bert.png": { - "width": 1926, - "height": 830 - }, - "images/breath.png": { - "width": 1200, - "height": 512 - }, - "images/cgvc.png": { - "width": 1600, - "height": 1200 - }, - "images/contagion.png": { - "width": 1024, - "height": 1024 - }, - "images/dotfiles.png": { - "width": 256, - "height": 256 - }, - "images/eot.png": { - "width": 500, - "height": 412 - }, - "images/exploRNN.png": { - "width": 1926, - "height": 992 - }, - "images/fibrils.png": { - "width": 2866, - "height": 1250 - }, - "images/gemini.png": { - "width": 3846, - "height": 1624 - }, - "images/hie.png": { - "width": 1000, - "height": 729 - }, - "images/humboldt.png": { - "width": 1292, - "height": 625 - }, - "images/image_quality.png": { - "width": 923, - "height": 477 - }, - "images/luna.png": { - "width": 1000, - "height": 1000 - }, - "images/midosa.png": { - "width": 1114, - "height": 1114 - }, - "images/mint.png": { - "width": 888, - "height": 538 - }, - "images/mySnow.png": { - "width": 1024, - "height": 1024 - }, - "images/nPMI.png": { - "width": 1596, - "height": 774 - }, - "images/net2vis.png": { - "width": 1916, - "height": 1046 - }, - "images/npmiVIS.png": { - "width": 4000, - "height": 2240 - }, - "images/openhands.png": { - "width": 542, - "height": 362 - }, - "images/other_left.png": { - "width": 678, - "height": 424 - }, - "images/pc-missing-data.png": { - "width": 2500, - "height": 850 - }, - "images/preview.png": { - "width": 1200, - "height": 630 - }, - "images/svelte-vega.png": { - "width": 434, - "height": 336 - }, - "images/symphony.png": { - "width": 291, - "height": 296 - }, - "images/tox.png": { - "width": 357, - "height": 260 - }, - "images/vegaprof.png": { - "width": 2100, - "height": 688 - }, - "images/zeno.png": { - "width": 617, - "height": 615 - }, - "images/vispositions.svg": { - "width": 1000, - "height": 500 - } + 'images/NAPs.png': { + width: 1000, + height: 450 + }, + 'images/alphaevolve.png': { + width: 2043, + height: 1077 + }, + 'images/bert.png': { + width: 1926, + height: 830 + }, + 'images/breath.png': { + width: 1200, + height: 512 + }, + 'images/cgvc.png': { + width: 1600, + height: 1200 + }, + 'images/contagion.png': { + width: 1024, + height: 1024 + }, + 'images/dotfiles.png': { + width: 256, + height: 256 + }, + 'images/eot.png': { + width: 500, + height: 412 + }, + 'images/exploRNN.png': { + width: 1926, + height: 992 + }, + 'images/fibrils.png': { + width: 2866, + height: 1250 + }, + 'images/gemini.png': { + width: 3846, + height: 1624 + }, + 'images/hie.png': { + width: 1000, + height: 729 + }, + 'images/humboldt.png': { + width: 1292, + height: 625 + }, + 'images/image_quality.png': { + width: 923, + height: 477 + }, + 'images/luna.png': { + width: 1000, + height: 1000 + }, + 'images/midosa.png': { + width: 1114, + height: 1114 + }, + 'images/mint.png': { + width: 888, + height: 538 + }, + 'images/mySnow.png': { + width: 1024, + height: 1024 + }, + 'images/nPMI.png': { + width: 1596, + height: 774 + }, + 'images/net2vis.png': { + width: 1916, + height: 1046 + }, + 'images/npmiVIS.png': { + width: 4000, + height: 2240 + }, + 'images/openhands.png': { + width: 542, + height: 362 + }, + 'images/other_left.png': { + width: 678, + height: 424 + }, + 'images/pc-missing-data.png': { + width: 2500, + height: 850 + }, + 'images/preview.png': { + width: 1200, + height: 630 + }, + 'images/svelte-vega.png': { + width: 434, + height: 336 + }, + 'images/symphony.png': { + width: 291, + height: 296 + }, + 'images/tox.png': { + width: 357, + height: 260 + }, + 'images/vegaprof.png': { + width: 2100, + height: 688 + }, + 'images/zeno.png': { + width: 617, + height: 615 + }, + 'images/vispositions.svg': { + width: 1000, + height: 500 + } }; - let cachedResearchProjects: ResearchProject[] | null = null; /** @@ -528,7 +527,7 @@ export function getResearchProjects(): ResearchProject[] { ) ]; projects.sort((a, b) => parseInt(b.year) - parseInt(a.year)); - projects.forEach(p => { + projects.forEach((p) => { if (imageDimensions[p.imageSrc]) p.imageDimensions = imageDimensions[p.imageSrc]; }); cachedResearchProjects = projects; @@ -636,7 +635,7 @@ export function getFunProjects(): FunProject[] { ] ) ]; - projects.forEach(p => { + projects.forEach((p) => { if (imageDimensions[p.imageSrc]) p.imageDimensions = imageDimensions[p.imageSrc]; }); cachedFunProjects = projects; diff --git a/src/lib/types.ts b/src/lib/types.ts index 2812374..52ee71b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -35,7 +35,13 @@ export class Project { * @param {[LinkWithIcon]} links links from the project to more information * @param {{ width: number; height: number }} imageDimensions dimensions of the image */ - constructor(title: string, abstract: string, imageSrc: string, links: LinkWithIcon[] = [], imageDimensions?: { width: number; height: number }) { + constructor( + title: string, + abstract: string, + imageSrc: string, + links: LinkWithIcon[] = [], + imageDimensions?: { width: number; height: number } + ) { this.title = title; this.abstract = abstract; this.imageSrc = imageSrc; @@ -94,7 +100,13 @@ export class FunProject extends Project { * @param {[LinkWithIcon]} links links from the project to more information * @param {{ width: number; height: number }} imageDimensions dimensions of the image */ - constructor(title: string, abstract: string, imageSRC: string, links: LinkWithIcon[] = [], imageDimensions?: { width: number; height: number }) { + constructor( + title: string, + abstract: string, + imageSRC: string, + links: LinkWithIcon[] = [], + imageDimensions?: { width: number; height: number } + ) { super(title, abstract, imageSRC, links, imageDimensions); } }