web/save: add supported services popover

This commit is contained in:
wukko 2024-08-26 23:43:39 +06:00
parent 7524d202f7
commit 42410f7b20
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
5 changed files with 222 additions and 1 deletions

View file

@ -6,5 +6,7 @@
"mute": "mute",
"input.placeholder": "paste the link here",
"terms.note.agreement": "by continuing, you agree to",
"terms.note.link": "terms and ethics of use"
"terms.note.link": "terms and ethics of use",
"services.title": "supported services",
"services.disclaimer": "cobalt is not affiliated with any of the supported services listed above."
}

View file

@ -0,0 +1,155 @@
<script lang="ts">
import { t } from "$lib/i18n/translations";
import { getServerInfo, cachedInfo } from "$lib/api/server-info";
import Skeleton from "$components/misc/Skeleton.svelte";
import IconPlus from "@tabler/icons-svelte/IconPlus.svelte";
let services: string[] = [];
$: expanded = false;
$: loaded = false;
const loadInfo = async () => {
await getServerInfo();
if ($cachedInfo) {
loaded = true;
services = $cachedInfo.cobalt.services;
}
};
</script>
<div id="supported-services">
<button
id="services-button"
class:expanded
on:click={async () => {
expanded = !expanded;
if (expanded && services.length === 0) {
await loadInfo();
}
}}
>
<div class="expand-icon">
<IconPlus />
</div>
<span class="title">{$t("save.services.title")}</span>
</button>
<div id="services-popover" class:expanded>
<div id="services-container">
{#if loaded}
{#each services as service}
<div class="service-item">{service}</div>
{/each}
{:else}
{#each { length: 17 } as _}
<Skeleton
class="elevated"
width={Math.random() * 44 + 50 + "px"}
height="24.5px"
/>
{/each}
{/if}
</div>
<div id="services-disclaimer" class="subtext">
{$t("save.services.disclaimer")}
</div>
</div>
</div>
<style>
#supported-services {
display: flex;
position: relative;
max-width: 400px;
flex-direction: column;
align-items: center;
height: 35px;
}
#services-popover {
display: flex;
flex-direction: column;
transition: transform 0.2s cubic-bezier(0.53, 0.05, 0.23, 0.99);
border-radius: 18px;
background: var(--button);
box-shadow:
var(--button-box-shadow),
0 0 10px 10px var(--button-stroke);
transform: scale(0);
transform-origin: top center;
position: relative;
padding: 12px;
gap: 6px;
top: 6px;
}
#services-popover.expanded {
transform: scale(1);
}
#services-button {
gap: 9px;
padding: 7px 13px 7px 10px;
justify-content: flex-start;
border-radius: 18px;
display: flex;
flex-direction: row;
font-size: 13px;
font-weight: 500;
}
.expand-icon {
height: 21px;
width: 21px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 18px;
background: var(--button-elevated);
padding: 0;
box-shadow: none;
transition: transform 0.2s;
}
#services-button:hover .expand-icon {
background: var(--button-elevated-hover);
}
.expand-icon :global(svg) {
height: 18px;
width: 18px;
stroke-width: 2px;
color: var(--secondary);
will-change: transform;
}
.expanded .expand-icon {
transform: rotate(45deg);
transition: transform 0.2s;
}
#services-container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 3px;
}
.service-item {
display: flex;
padding: 4px 8px;
border-radius: calc(var(--border-radius) / 2);
background: var(--button-elevated);
font-size: 12.5px;
font-weight: 500;
}
#services-disclaimer {
padding: 0;
}
</style>

View file

@ -0,0 +1,46 @@
import { get, writable } from "svelte/store";
import { currentApiURL } from "$lib/api/api-url";
import type { CobaltServerInfoResponse, CobaltErrorResponse, CobaltServerInfo } from "$lib/types/api";
export const cachedInfo = writable<CobaltServerInfo | undefined>();
const request = async () => {
const apiEndpoint = `${currentApiURL()}/`;
const response: CobaltServerInfoResponse = await fetch(apiEndpoint, {
redirect: "manual",
signal: AbortSignal.timeout(10000),
})
.then(r => r.json())
.catch((e) => {
if (e?.message?.includes("timed out")) {
return {
status: "error",
error: {
code: "error.api.timed_out"
}
} as CobaltErrorResponse
}
});
return response;
}
export const getServerInfo = async () => {
const cache = get(cachedInfo);
if (cache) return true;
const freshInfo = await request();
if (!freshInfo || !("cobalt" in freshInfo)) {
return false;
}
if (!("status" in freshInfo)) {
cachedInfo.set(freshInfo);
return true;
}
return false;
}

View file

@ -43,7 +43,23 @@ export type CobaltSession = {
exp: number,
}
export type CobaltServerInfo = {
cobalt: {
version: string,
url: string,
startTime: string,
durationLimit: number,
services: string[]
},
git: {
branch: string,
commit: string,
remote: string,
}
}
export type CobaltSessionResponse = CobaltSession | CobaltErrorResponse;
export type CobaltServerInfoResponse = CobaltServerInfo | CobaltErrorResponse;
export type CobaltAPIResponse = CobaltErrorResponse
| CobaltPickerResponse

View file

@ -3,6 +3,7 @@
import Omnibox from "$components/save/Omnibox.svelte";
import Meowbalt from "$components/misc/Meowbalt.svelte";
import SupportedServices from "$components/save/SupportedServices.svelte";
</script>
<svelte:head>
@ -10,6 +11,7 @@
</svelte:head>
<div id="cobalt-save-container" class="center-column-container">
<SupportedServices />
<main
id="cobalt-save"
tabindex="-1"