Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit 9cfc5c6

Browse files
authored
Merge pull request #38 from sdsc-ordes/fix/search-results
feat: pagination, dark mode, qlever connection
2 parents 96c2cec + 693132e commit 9cfc5c6

23 files changed

Lines changed: 790 additions & 217 deletions

docs/queries/casNumber.rq

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
PREFIX cat: <http://example.org/catplus/ontology>
22
SELECT DISTINCT ?casNumber
3-
WHERE { ?s cat:casNumber ?casNumber .}
3+
WHERE { ?s cat:casNumber ?casNumber .}

docs/queries/devices.rq

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ PREFIX cat: <http://example.org/catplus/ontology/>
33
PREFIX allores: <http://purl.allotrope.org/ontologies/result#>
44
SELECT DISTINCT ?name
55
WHERE {{ ?s1 rdf:type allores:AFR_0002567 ; allores:AFR_0002568 ?name .}
6-
UNION { ?s1 rdf:type cat:AddAction ; allores:AFR_0001723 ?name .}}
6+
UNION { ?s1 rdf:type cat:AddAction ; allores:AFR_0001723 ?name .}}

docs/queries/result.rq

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,42 @@ PREFIX allores: <http://purl.allotrope.org/ontologies/result#>
22
PREFIX cat: <http://example.org/catplus/ontology/>
33
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
44
PREFIX schema: <https://schema.org/>
5-
SELECT DISTINCT ?contentUrl ?campaignName ?reactionType ?reactionName ?casNumber ?smiles ?chemicalName
5+
6+
SELECT ?contentUrl ?campaignName ?reactionType ?reactionName ?chemicalName ?casNumber ?smiles
67
WHERE {
7-
?s rdf:type cat:Campaign ;
8-
cat:hasBatch ?batch ;
8+
# STEP 2: The outer query takes each ?s from the subquery result
9+
# and finds all the associated details for it.
10+
?s cat:hasBatch ?batch ;
911
cat:hasChemical ?chemical ;
1012
schema:name ?campaignName ;
1113
schema:contentUrl ?contentUrl .
1214
?batch cat:reactionType ?reactionType ;
1315
cat:reactionName ?reactionName .
1416
?chemical allores:AFR_0002295 ?smiles ;
15-
allores:AFR_0002292 ?chemicalName ;
16-
cat:casNumber ?casNumber .
17+
allores:AFR_0002292 ?chemicalName ;
18+
cat:casNumber ?casNumber .
19+
20+
# --- Subquery Starts Here ---
21+
# STEP 1: This inner query runs first. Its only job is to find the
22+
# unique campaigns (?s) that match the filter, and then apply pagination.
23+
{
24+
SELECT DISTINCT ?s WHERE {
25+
?s rdf:type cat:Campaign ;
26+
cat:hasBatch ?batch ;
27+
cat:hasChemical ?chemical ;
28+
schema:contentUrl ?contentUrl .
29+
?batch cat:reactionType ?reactionType ;
30+
cat:reactionName ?reactionName .
31+
?chemical allores:AFR_0002295 ?smiles .
32+
# The FILTER must be inside the subquery to apply before the LIMIT.
33+
FILTER (?reactionType = 'N-methylation' || ?reactionName = 'Caffeine synthesis' || ?smiles = 'CO')
34+
}
35+
ORDER BY ASC(?contentUrl)
36+
LIMIT 3
37+
OFFSET 0
38+
}
39+
#--- Subquery Ends Here ---
1740
}
41+
GROUP BY ?contentUrl ?campaignName ?reactionType ?reactionName ?chemicalName ?casNumber ?smiles
42+
# This sorts the final results that are returned to the user.
43+
ORDER BY ASC(?contentUrl)

src/lib/components/Campaign.svelte

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
</script>
3434

3535
{#if isLoading}
36-
<div class="p-6 text-center text-gray-500 animate-pulse">
36+
<div class="p-6 text-center text-surface-500 animate-pulse">
3737
<p>Loading details...</p>
3838
</div>
3939
{:else if error}
@@ -42,8 +42,8 @@
4242
<p>{error}</p>
4343
</div>
4444
{:else if campaignFiles && activeCampaign}
45-
<div class="hover:bg-tertiary-100">
46-
<h1 class="mb-6 flex items-center gap-x-2 text-2xl text-gray-800">
45+
<div class="hover:bg-tertiary-100-900">
46+
<h1 class="mb-6 flex items-center gap-x-2 text-2xl text-surface-800-200">
4747
<Archive />
4848
<span>{title}</span>
4949
<a
@@ -70,7 +70,7 @@
7070
{/each}
7171
</tr>
7272
</thead>
73-
<tbody class="[&>tr]:hover:bg-tertiary-100">
73+
<tbody class="[&>tr]:hover:bg-tertiary-100-900">
7474
{#each campaignFiles as file}
7575
<tr>
7676
<td>{file.name}</td>
@@ -89,11 +89,11 @@
8989
</table>
9090
</div>
9191
{:else if activeCampaign}
92-
<div class="p-6 text-center text-gray-500">
92+
<div class="p-6 text-center text-surface-500">
9393
<p>Preparing details for {activeCampaign}...</p>
9494
</div>
9595
{:else}
96-
<div class="flex items-center justify-center h-full text-gray-500">
96+
<div class="flex items-center justify-center h-full text-surface-500">
9797
<p class="text-lg">Please select an item from the sidebar to view its details.</p>
9898
</div>
9999
{/if}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<script lang="ts" generics="ResultItemType extends ResultItemBase">
2+
import Campaign from '$lib/components/Campaign.svelte';
3+
import { publicConfig } from '$lib/config';
4+
import type { S3FileInfo } from '$lib/server/s3';
5+
import { Pagination } from '@skeletonlabs/skeleton-svelte';
6+
import { base } from '$app/paths';
7+
8+
interface ResultItemBase {
9+
prefix: string;
10+
}
11+
12+
// get props from data loader
13+
let {
14+
results,
15+
resultsTotal,
16+
tableHeaders,
17+
handlePageChange,
18+
} = $props();
19+
20+
const headers = ["Campaign"].concat(Object.values(tableHeaders));
21+
22+
// Pagination of Campaigns
23+
let currentPage = $state(1);
24+
let pageSize = publicConfig.PUBLIC_RESULTS_PER_PAGE;
25+
26+
// State for the fetched detailed data for the main content
27+
let detailedContent = $state<S3FileInfo[] | null>(null);
28+
let isLoadingDetails = $state(false);
29+
let detailError = $state<string | null>(null);
30+
let activeResultItem = $state<ResultItemType | null>(null);
31+
32+
async function fetchDetails(campaignPath: string) {
33+
isLoadingDetails = true;
34+
detailError = null;
35+
detailedContent = null;
36+
try {
37+
// Adjust the URL to your actual API endpoint structure
38+
const response = await fetch(`${base}/api/${campaignPath}`);
39+
if (!response.ok) {
40+
const errorData = await response.json().catch(() => ({ message: `HTTP error! status: ${response.status}` }));
41+
throw new Error(errorData.message || `Failed to fetch details. Status: ${response.status}`);
42+
}
43+
const fetchedDetails: S3FileInfo[] = await response.json();
44+
detailedContent = fetchedDetails;
45+
} catch (err: any) {
46+
console.error('Error fetching details:', err);
47+
detailError = err.message || 'An unknown error occurred.';
48+
} finally {
49+
isLoadingDetails = false;
50+
}
51+
}
52+
53+
function handleRowClick(result: ResultItemType) {
54+
activeResultItem = result;
55+
if (result && result.prefix) {
56+
fetchDetails(result.prefix);
57+
} else {
58+
// Reset main content if row is invalid or deselected (if implementing deselection)
59+
detailedContent = null;
60+
isLoadingDetails = false;
61+
detailError = null;
62+
}
63+
}
64+
65+
$effect(() => {
66+
// This effect runs whenever the `results` array changes
67+
if (results && results.length > 0) {
68+
// Automatically select the first item of the list
69+
const firstItem = results[0];
70+
handleRowClick(firstItem);
71+
} else {
72+
// If the new page has no results, clear the details view
73+
activeResultItem = null;
74+
detailedContent = null;
75+
}
76+
});
77+
</script>
78+
79+
<div class="bg-tertiary-50-800 space-y-4 rounded p-4">
80+
<div class="table-wrap bg-tertiary-50-800 overflow-x-auto rounded-lg shadow">
81+
<table class="table caption-bottom">
82+
<thead>
83+
<tr>
84+
{#each headers as header}
85+
<th>{header}</th>
86+
{/each}
87+
</tr>
88+
</thead>
89+
<tbody class="[&>tr]:hover:bg-tertiary-100-900">
90+
{#each results as result, i}
91+
<tr
92+
onclick={() => handleRowClick(result)}
93+
class="cursor-pointer"
94+
class:bg-tertiary-200-800={activeResultItem?.prefix === result.prefix}
95+
>
96+
{#each Object.values(result) as value, key}
97+
<td>
98+
{#if Array.isArray(value)}
99+
<ul class="list-disc pl-5">
100+
{#each value as item}
101+
<li>{item}</li>
102+
{/each}
103+
</ul>
104+
{:else}
105+
{value}
106+
{/if}
107+
</td>
108+
{/each}
109+
</tr>
110+
{/each}
111+
</tbody>
112+
</table>
113+
</div>
114+
<footer class="">
115+
<Pagination
116+
data={results}
117+
{currentPage}
118+
onPageChange={(e) => handlePageChange(e)}
119+
pageSize={pageSize}
120+
siblingCount={4}
121+
count={resultsTotal}
122+
alternative
123+
/>
124+
</footer>
125+
</div>
126+
<Campaign
127+
isLoading={isLoadingDetails}
128+
error={detailError}
129+
campaignFiles={detailedContent?.files}
130+
activeCampaign={activeResultItem?.prefix}
131+
title={activeResultItem?.prefix}
132+
/>

src/lib/components/DisplayResults.svelte renamed to src/lib/components/DisplayS3Results.svelte

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,8 @@
7878
});
7979
</script>
8080

81-
<div class="bg-tertiary-50 space-y-4 rounded p-4">
82-
<h1 class="bg-tertiary-50 mb-4 p-4 text-2xl font-bold text-gray-800">
83-
Results ({results.length})
84-
</h1>
85-
<div class="table-wrap bg-tertiary-50 overflow-x-auto rounded-lg shadow">
81+
<div class="bg-tertiary-50-800 space-y-4 rounded p-4">
82+
<div class="table-wrap bg-tertiary-50-800 overflow-x-auto rounded-lg shadow">
8683
<table class="table caption-bottom">
8784
<thead>
8885
<tr>
@@ -91,12 +88,12 @@
9188
{/each}
9289
</tr>
9390
</thead>
94-
<tbody class="[&>tr]:hover:bg-tertiary-100">
91+
<tbody class="[&>tr]:hover:bg-tertiary-100-900">
9592
{#each slicedResults(results) as result, i}
9693
<tr
9794
onclick={() => handleRowClick(result)}
9895
class="cursor-pointer"
99-
class:bg-tertiary-200={activeResultItem?.prefix === result.prefix}
96+
class:bg-tertiary-200-800={activeResultItem?.prefix === result.prefix}
10097
>
10198
{#each Object.values(result) as value, key}
10299
<td>
@@ -115,13 +112,8 @@
115112
onPageChange={handlePageChange}
116113
pageSize={size}
117114
siblingCount={4}
118-
>
119-
{#snippet labelEllipsis()}<IconEllipsis class="size-4" />{/snippet}
120-
{#snippet labelNext()}<IconArrowRight class="size-4" />{/snippet}
121-
{#snippet labelPrevious()}<IconArrowLeft class="size-4" />{/snippet}
122-
{#snippet labelFirst()}<IconFirst class="size-4" />{/snippet}
123-
{#snippet labelLast()}<IconLast class="size-4" />{/snippet}
124-
</Pagination>
115+
alternative
116+
/>
125117
</footer>
126118
</div>
127119
<Campaign

src/lib/components/Header.svelte

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
<script lang="ts">
2-
import { AppBar } from '@skeletonlabs/skeleton-svelte';
3-
import { base } from '$app/paths';
4-
import Paperclip from '@lucide/svelte/icons/paperclip';
5-
import Calendar from '@lucide/svelte/icons/calendar';
6-
import Home from '@lucide/svelte/icons/home';
7-
import Database from '@lucide/svelte/icons/database';
8-
import CircleUser from '@lucide/svelte/icons/circle-user';
9-
import Search from '@lucide/svelte/icons/search';
2+
import { AppBar } from '@skeletonlabs/skeleton-svelte';
3+
import { base } from '$app/paths';
4+
import { page } from '$app/stores'; // <-- Import the page store
5+
6+
// Import all your icons
7+
import Paperclip from '@lucide/svelte/icons/paperclip';
8+
import Calendar from '@lucide/svelte/icons/calendar';
9+
import Home from '@lucide/svelte/icons/home';
10+
import Database from '@lucide/svelte/icons/database';
11+
import CircleUser from '@lucide/svelte/icons/circle-user';
12+
import Search from '@lucide/svelte/icons/search';
13+
14+
// --- Define your navigation items in an array ---
15+
const navItems = [
16+
{ href: `${base}/`, label: 'Home', icon: Home },
17+
{ href: `${base}/search`, label: 'Search', icon: Search },
18+
{ href: `${base}/data`, label: 'Data', icon: Database }
19+
];
1020
</script>
1121

1222
<AppBar classes="flex items-left">
13-
{#snippet lead()}
14-
<a href="{base}/" class="flex items-center mx-4 gap-x-2"><Home size={20} />Home</a>
15-
<a href="{base}/search" class="flex items-center mx-4 gap-x-2"><Search size={20} />Search</a>
16-
<a href="{base}/data" class="flex items-center mx-4 gap-x-2"><Database size={20} />Data</a>
17-
{/snippet}
18-
{#snippet trail()}
19-
<Paperclip size={20} />
20-
<Calendar size={20} />
21-
<CircleUser size={20} />
22-
{/snippet}
23-
<span>CAT+ Chemboard</span>
24-
</AppBar>
23+
{#snippet lead()}
24+
{#each navItems as item}
25+
<a
26+
href={item.href}
27+
class="flex items-center mx-4 gap-x-2 transition-colors hover:text-primary-500"
28+
class:text-primary-500={$page.url.pathname === item.href}
29+
>
30+
<svelte:component this={item.icon} size={20} />
31+
<span>{item.label}</span>
32+
</a>
33+
{/each}
34+
{/snippet}
35+
{#snippet trail()}
36+
<Paperclip size={20} />
37+
<Calendar size={20} />
38+
<CircleUser size={20} />
39+
{/snippet}
40+
<span>CAT+ Chemboard</span>
41+
</AppBar>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script lang="ts">
2+
let {
3+
resultsTotal,
4+
} = $props();
5+
</script>
6+
7+
<h1 class="mb-4 p-4 text-xl font-bold text-surface-800-200">
8+
Results ({resultsTotal})
9+
</h1>

0 commit comments

Comments
 (0)