Skip to content

Commit adf569c

Browse files
madkarmaaoSumAtrIXUshie
authored
feat: API outage banner (#254)
Co-authored-by: oSumAtrIX <[email protected]> Co-authored-by: oSumAtrIX <[email protected]> Co-authored-by: Ushie <[email protected]>
1 parent 87ce20f commit adf569c

File tree

10 files changed

+976
-661
lines changed

10 files changed

+976
-661
lines changed

pnpm-lock.yaml

Lines changed: 637 additions & 593 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ body {
3434
.wrapper {
3535
margin-inline: auto;
3636
width: min(90%, 80rem);
37-
margin-top: 7rem;
37+
margin-top: 2.6rem;
3838
}
3939

4040
:root {
@@ -73,6 +73,9 @@ body {
7373
--surface-nine: hsl(calc(var(--hue, 206) + 24), 9.5%, 17.5%);
7474

7575
--red-one: hsl(333, 84%, 62%);
76+
--red-two: hsl(357, 74%, 60%);
77+
78+
--yellow-one: hsl(59, 100%, 72%);
7679

7780
--bezier-one: cubic-bezier(0.25, 0.46, 0.45, 0.94);
7881
--drop-shadow-one: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),

src/data/api/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ async function about(): Promise<AboutData> {
7474
return { about: json };
7575
}
7676

77+
async function ping(): Promise<boolean> {
78+
try {
79+
const res = await fetch(`${settings.api_base_url()}/v4/ping`, { method: 'HEAD' });
80+
return res.ok;
81+
} catch (error) {
82+
return false;
83+
}
84+
}
85+
7786
export const staleTime = 5 * 60 * 1000;
7887
export const queries = {
7988
manager: {
@@ -100,5 +109,10 @@ export const queries = {
100109
queryKey: ['info'],
101110
queryFn: about,
102111
staleTime
112+
},
113+
ping: {
114+
queryKey: ['ping'],
115+
queryFn: ping,
116+
staleTime
103117
}
104118
};

src/data/api/settings.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,48 @@ import { RV_API_URL } from '$env/static/public';
44
export const default_api_url = RV_API_URL;
55

66
const URL_KEY = 'revanced_api_url';
7+
const STATUS_KEY = 'revanced_status_url';
8+
9+
function set_status_url(apiUrl: string) {
10+
fetch(`${apiUrl}/v4/about`)
11+
.then((response) => (response.ok ? response.json() : null))
12+
.then((data) => {
13+
if (data?.status) {
14+
localStorage.setItem(STATUS_KEY, data.status);
15+
console.log('status is now ' + localStorage.getItem(STATUS_KEY));
16+
}
17+
});
18+
}
719

820
// Get base URL
921
export function api_base_url(): string {
1022
if (browser) {
11-
return localStorage.getItem(URL_KEY) || default_api_url;
23+
const apiUrl = localStorage.getItem(URL_KEY) || default_api_url;
24+
25+
if (!localStorage.getItem(STATUS_KEY)) {
26+
set_status_url(apiUrl);
27+
}
28+
29+
return apiUrl;
1230
}
1331

1432
return default_api_url;
1533
}
1634

35+
export function status_url(): string | null {
36+
if (browser) {
37+
return localStorage.getItem(STATUS_KEY) || null;
38+
}
39+
40+
return null;
41+
}
42+
1743
// (re)set base URL.
1844
export function set_api_base_url(url?: string) {
1945
if (!url) {
2046
localStorage.removeItem(URL_KEY);
2147
} else {
2248
localStorage.setItem(URL_KEY, url);
49+
set_status_url(url);
2350
}
2451
}

src/layout/Navbar/NavHost.svelte

Lines changed: 131 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
import { horizontalSlide } from '$util/horizontalSlide';
44
import { fade } from 'svelte/transition';
55
import { expoOut } from 'svelte/easing';
6+
import { createQuery } from '@tanstack/svelte-query';
67
78
import Navigation from './NavButton.svelte';
89
import Modal from '$lib/components/Dialogue.svelte';
910
import Button from '$lib/components/Button.svelte';
11+
import Banner from '$lib/components/Banner.svelte';
12+
import Query from '$lib/components/Query.svelte';
1013
1114
import Cog from 'svelte-material-icons/Cog.svelte';
1215
import Replay from 'svelte-material-icons/Replay.svelte';
1316
14-
import { api_base_url, set_api_base_url, default_api_url } from '$data/api/settings';
17+
import { status_url, api_base_url, set_api_base_url, default_api_url } from '$data/api/settings';
1518
import RouterEvents from '$data/RouterEvents';
19+
import { queries } from '$data/api';
1620
1721
import { useQueryClient } from '@tanstack/svelte-query';
1822
@@ -31,6 +35,7 @@
3135
}
3236
3337
let url = api_base_url();
38+
const statusUrl = status_url();
3439
3540
function save() {
3641
set_api_base_url(url);
@@ -44,6 +49,7 @@
4449
let menuOpen = false;
4550
let modalOpen = false;
4651
let y: number;
52+
const pingQuery = () => createQuery(['ping'], queries.ping);
4753
4854
onMount(() => {
4955
return RouterEvents.subscribe((event) => {
@@ -56,53 +62,88 @@
5662

5763
<svelte:window bind:scrollY={y} />
5864

59-
<nav class:scrolled={y > 10}>
60-
<a class="menu-btn skiptab-btn" href="#skiptab">Skip navigation</a>
61-
62-
<button
63-
class="menu-btn mobile-only"
64-
on:click={() => (menuOpen = !menuOpen)}
65-
class:open={menuOpen}
66-
aria-label="Menu"
67-
>
68-
<span class="menu-btn__burger" />
69-
</button>
70-
<a href="/" id="logo"><img src="/logo.svg" alt="ReVanced Logo" /></a>
71-
72-
{#key menuOpen}
73-
<div
74-
class="nav-wrapper"
75-
class:desktop-only={!menuOpen}
76-
transition:horizontalSlide={{ direction: 'inline', easing: expoOut, duration: 400 }}
65+
<div id="nav-container">
66+
<Query query={pingQuery()} let:data>
67+
{#if !data}
68+
<span class="banner">
69+
<Banner level="caution" permanent>
70+
The API is currently unresponsive and some services may not work correctly. {#if statusUrl}
71+
Check the <a href={statusUrl} target="_blank" rel="noopener noreferrer">status page</a> for
72+
updates.
73+
{/if}
74+
</Banner>
75+
</span>
76+
{/if}
77+
</Query>
78+
79+
<nav class:scrolled={y > 10}>
80+
<a class="menu-btn skiptab-btn" href="#skiptab">Skip navigation</a>
81+
82+
<button
83+
class="menu-btn mobile-only"
84+
on:click={() => (menuOpen = !menuOpen)}
85+
class:open={menuOpen}
86+
aria-label="Menu"
7787
>
78-
<div id="main-navigation">
79-
<ul class="nav-buttons">
80-
<Navigation href="/" label="Home">Home</Navigation>
81-
<Navigation queryKey="manager" href="/download" label="Download">Download</Navigation>
82-
<Navigation queryKey="patches" href="/patches" label="Patches">Patches</Navigation>
83-
<Navigation queryKey="contributors" href="/contributors" label="Contributors">
84-
Contributors
85-
</Navigation>
86-
<Navigation queryKey={['about', 'team']} href="/donate" label="Donate">Donate</Navigation>
87-
</ul>
88-
</div>
89-
<div id="secondary-navigation">
90-
<button on:click={() => (modalOpen = !modalOpen)} aria-label="Settings">
91-
<Cog size="20px" color="var(--surface-six)" />
92-
</button>
88+
<span class="menu-btn__burger" />
89+
</button>
90+
<a href="/" id="logo"><img src="/logo.svg" alt="ReVanced Logo" /></a>
91+
92+
{#key menuOpen}
93+
<div
94+
id="nav-wrapper-container"
95+
class:desktop-only={!menuOpen}
96+
transition:horizontalSlide={{ direction: 'inline', easing: expoOut, duration: 400 }}
97+
>
98+
<div id="banner-pad">
99+
<Query query={pingQuery()} let:data>
100+
{#if !data}
101+
<span class="banner">
102+
<Banner level="caution" permanent>
103+
The API is currently unresponsive and some services may not work correctly. {#if statusUrl}
104+
Check the
105+
<a href={statusUrl} target="_blank" rel="noopener noreferrer">status page</a> for
106+
updates.
107+
{/if}
108+
</Banner>
109+
</span>
110+
{/if}
111+
</Query>
112+
</div>
113+
114+
<div class="nav-wrapper">
115+
<div id="main-navigation">
116+
<ul class="nav-buttons">
117+
<Navigation href="/" label="Home">Home</Navigation>
118+
<Navigation queryKey="manager" href="/download" label="Download">Download</Navigation>
119+
<Navigation queryKey="patches" href="/patches" label="Patches">Patches</Navigation>
120+
<Navigation queryKey="contributors" href="/contributors" label="Contributors">
121+
Contributors
122+
</Navigation>
123+
<Navigation queryKey={['about', 'team']} href="/donate" label="Donate"
124+
>Donate</Navigation
125+
>
126+
</ul>
127+
</div>
128+
<div id="secondary-navigation">
129+
<button on:click={() => (modalOpen = !modalOpen)} aria-label="Settings">
130+
<Cog size="20px" color="var(--surface-six)" />
131+
</button>
132+
</div>
133+
</div>
93134
</div>
94-
</div>
95-
{/key}
96-
97-
{#if menuOpen}
98-
<div
99-
class="overlay mobile-only"
100-
transition:fade={{ duration: 350 }}
101-
on:click={() => (menuOpen = !menuOpen)}
102-
on:keypress={() => (menuOpen = !menuOpen)}
103-
/>
104-
{/if}
105-
</nav>
135+
{/key}
136+
137+
{#if menuOpen}
138+
<div
139+
class="overlay mobile-only"
140+
transition:fade={{ duration: 350 }}
141+
on:click={() => (menuOpen = !menuOpen)}
142+
on:keypress={() => (menuOpen = !menuOpen)}
143+
/>
144+
{/if}
145+
</nav>
146+
</div>
106147

107148
<!-- settings -->
108149
<Modal bind:modalOpen>
@@ -126,7 +167,7 @@
126167
</svelte:fragment>
127168
</Modal>
128169

129-
<style>
170+
<style lang="scss">
130171
#logo {
131172
padding: 0.5rem;
132173
}
@@ -160,15 +201,26 @@
160201
top: 30px;
161202
}
162203
204+
#nav-container {
205+
position: sticky;
206+
z-index: 666;
207+
width: 100%;
208+
209+
&:has(.nav-buttons > li:first-child.selected) {
210+
margin-bottom: 2.65rem;
211+
212+
&:has(.banner) {
213+
margin-bottom: 1.5rem;
214+
}
215+
}
216+
}
217+
163218
nav {
164-
position: fixed;
165-
top: 0;
166219
display: flex;
167220
gap: 2rem;
168221
justify-content: space-between;
169222
align-items: center;
170223
padding: 1rem 2rem;
171-
z-index: 666;
172224
height: 70px;
173225
background-color: var(--surface-eight);
174226
width: 100%;
@@ -181,10 +233,6 @@
181233
gap: 2rem;
182234
}
183235
184-
a {
185-
display: flex;
186-
}
187-
188236
img {
189237
height: 22px;
190238
}
@@ -220,21 +268,45 @@
220268
}
221269
}
222270
271+
#banner-pad {
272+
display: none;
273+
}
274+
275+
#nav-wrapper-container {
276+
width: 100%;
277+
}
278+
223279
@media (max-width: 767px) {
280+
#banner-pad {
281+
display: block;
282+
width: 100vw;
283+
visibility: hidden;
284+
}
285+
286+
#nav-container:has(.nav-buttons > li:first-child.selected):has(.banner) {
287+
margin-bottom: 0rem;
288+
}
289+
290+
#nav-wrapper-container {
291+
overflow: hidden;
292+
position: fixed;
293+
width: 20rem;
294+
top: 0;
295+
left: 0;
296+
height: 100%;
297+
background-color: var(--surface-eight);
298+
z-index: 100;
299+
}
300+
224301
.nav-wrapper {
225302
flex-direction: column;
226303
gap: 0.5rem;
227304
height: 100%;
228305
margin: 0 auto;
229-
position: fixed;
230306
width: 20rem;
231-
top: 0px;
232307
border-radius: 0px 24px 24px 0px;
233-
left: 0px;
234-
background-color: var(--surface-eight);
235308
padding: 1rem;
236309
padding-top: 6rem;
237-
z-index: 100;
238310
}
239311
240312
.desktop-only {

0 commit comments

Comments
 (0)