Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.

Commit 73376d8

Browse files
committed
Improve mobile layout and loading states of explore page
We still need to handle cancelation
1 parent 6df9063 commit 73376d8

2 files changed

Lines changed: 55 additions & 23 deletions

File tree

frontend/src/lib/components/Callout.svelte

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,21 @@
2929
>
3030
<div>
3131
<slot name="title">
32-
<div class="flex items-center space-x-1.5 pb-1 font-bold capitalize tracking-wide">
32+
<div class="flex items-center space-x-1.5 font-bold capitalize tracking-wide">
3333
<svelte:component this={icon} />
3434
<span>
3535
{title}
3636
</span>
37-
<div class="m-0 flex-1" />
38-
<slot name="top-right" />
37+
{#if $$slots['top-right']}
38+
<div class="m-0 flex-1" />
39+
<slot name="top-right" />
40+
{/if}
3941
</div>
4042
</slot>
4143
</div>
42-
<div class="pt-1">
43-
<slot />
44-
</div>
44+
{#if $$slots.default}
45+
<div class="mt-1 pt-1">
46+
<slot />
47+
</div>
48+
{/if}
4549
</div>

frontend/src/routes/explore/+page.svelte

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,46 @@
1919
let chosenSites: string[] = ['facebook.com'];
2020
let similarSites: ScoredSite[] = [];
2121
22-
let errorMessage = false;
22+
type ValidationState = 'idle' | 'validating url' | 'loading similar' | 'error';
23+
let validation: ValidationState = 'idle';
2324
2425
$: {
25-
api
26-
.webgraphHostSimilar({ sites: chosenSites, topN: limit })
27-
.data.then((res) => (similarSites = res));
26+
validation = 'loading similar';
27+
api.webgraphHostSimilar({ sites: chosenSites, topN: limit }).data.then(async (res) => {
28+
await wait(1000);
29+
similarSites = res;
30+
validation = 'idle';
31+
});
2832
}
2933
34+
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
35+
3036
const removeWebsite = async (site: string) => {
3137
if (chosenSites.includes(site)) {
3238
chosenSites = chosenSites.filter((s) => s != site);
3339
}
3440
};
35-
const addWebsite = async (site: string, clear = false) => {
36-
errorMessage = false;
41+
const addWebsite = async (
42+
site: string,
43+
{ clear = false, validate = true }: { clear?: boolean; validate?: boolean } = {},
44+
) => {
45+
validation = 'idle';
3746
site = site.trim();
3847
if (!site) return;
48+
if (!validate) {
49+
chosenSites = [...chosenSites, site];
50+
return;
51+
}
52+
validation = 'validating url';
3953
54+
await wait(1000);
4055
const result = await api.webgraphHostKnows({ site }).data;
4156
match(result)
4257
.with({ type: 'unknown' }, () => {
43-
errorMessage = true;
58+
validation = 'error';
4459
})
4560
.with({ type: 'known' }, async ({ site }) => {
61+
validation = 'idle';
4662
if (clear) inputWebsite = '';
4763
if (!chosenSites.includes(site)) chosenSites = [...chosenSites, site];
4864
})
@@ -74,12 +90,13 @@
7490
'mb-2 flex w-full max-w-lg rounded-full border border-base-400 bg-base-100 p-[2px] pl-3 transition focus-within:shadow',
7591
)}
7692
id="site-input-container"
77-
on:submit|preventDefault={() => addWebsite(inputWebsite, true)}
93+
novalidate
94+
on:submit|preventDefault={() => addWebsite(inputWebsite, { clear: true })}
7895
>
7996
<!-- svelte-ignore a11y-autofocus -->
8097
<input
8198
class="grow border-none bg-transparent outline-none placeholder:opacity-50 focus:ring-0"
82-
type="text"
99+
type="url"
83100
id="site-input"
84101
name="site"
85102
autofocus
@@ -88,16 +105,24 @@
88105
/>
89106
<Button>Add</Button>
90107
</form>
91-
{#if errorMessage}
108+
{#if validation == 'error'}
92109
<div class="my-2" transition:slide>
93110
<Callout kind="warning" title="Unable to add page">
94-
<button slot="top-right" on:click={() => (errorMessage = false)}>
111+
<button slot="top-right" on:click={() => (validation = 'idle')}>
95112
<XMark />
96113
</button>
97114

98115
Unfortunately, we don't know about that site yet.
99116
</Callout>
100117
</div>
118+
{:else if validation == 'validating url'}
119+
<div class="my-2" transition:slide>
120+
<Callout kind="neutral" title="Validating URL..." />
121+
</div>
122+
{:else if validation == 'loading similar'}
123+
<div class="my-2" transition:slide>
124+
<Callout kind="neutral" title="Fetching similar sites..." />
125+
</div>
101126
{/if}
102127
<div class="flex flex-wrap justify-center gap-x-5 gap-y-3" id="sites-list">
103128
{#each chosenSites as site (site)}
@@ -115,18 +140,21 @@
115140

116141
{#if chosenSites.length > 0 && similarSites.length > 0}
117142
<div class="flex flex-col space-y-4">
118-
<div class="grid grid-cols-[auto_auto_1fr_auto] items-center gap-5">
119-
<h2 class="text-2xl font-bold">Similar sites</h2>
120-
<div class="flex space-x-1">
143+
<div
144+
class="grid grid-cols-[auto_auto_1fr] items-center sm:grid-cols-[auto_auto_1fr] sm:gap-x-5 md:grid-rows-none"
145+
>
146+
<h2 class="text-lg font-bold sm:text-2xl">Similar sites</h2>
147+
<div class="flex space-x-1 justify-self-center">
121148
<Select
122149
id="limit"
123150
class="cursor-pointer rounded border-none dark:bg-transparent"
124151
bind:value={limit}
125152
options={LIMIT_OPTIONS.map((value) => ({ value, label: value.toString() }))}
126153
/>
127154
</div>
128-
<div />
129-
<Button on:click={exportAsOptic}>Export as optic</Button>
155+
<div class="justify-self-end md:col-auto md:row-auto md:place-self-end">
156+
<Button on:click={exportAsOptic}>Export as optic</Button>
157+
</div>
130158
</div>
131159
<div class="grid items-center gap-y-2">
132160
{#each similarSites as site (site.site)}
@@ -141,7 +169,7 @@
141169
on:click={() =>
142170
chosenSites.includes(site.site)
143171
? removeWebsite(site.site)
144-
: addWebsite(site.site)}
172+
: addWebsite(site.site, { validate: false })}
145173
>
146174
<PlusCircleOutline
147175
class={twJoin(

0 commit comments

Comments
 (0)