mirror of
https://github.com/wukko/cobalt.git
synced 2025-02-02 08:26:22 +01:00
web: basic ui for the download queue manager
This commit is contained in:
parent
5d75ee493d
commit
6d0ec5dd85
4 changed files with 246 additions and 4 deletions
109
web/src/components/downloads/DownloadManager.svelte
Normal file
109
web/src/components/downloads/DownloadManager.svelte
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onNavigate } from "$app/navigation";
|
||||||
|
import type { SvelteComponent } from "svelte";
|
||||||
|
|
||||||
|
import Meowbalt from "$components/misc/Meowbalt.svelte";
|
||||||
|
import DownloadStatus from "$components/downloads/DownloadStatus.svelte";
|
||||||
|
import PopoverContainer from "$components/misc/PopoverContainer.svelte";
|
||||||
|
|
||||||
|
let popover: SvelteComponent;
|
||||||
|
$: expanded = false;
|
||||||
|
|
||||||
|
$: progress = 0;
|
||||||
|
$: indeterminate = false;
|
||||||
|
|
||||||
|
const popoverAction = async () => {
|
||||||
|
expanded = !expanded;
|
||||||
|
if (expanded) {
|
||||||
|
popover.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onNavigate(() => {
|
||||||
|
expanded = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="downloads-manager">
|
||||||
|
<DownloadStatus
|
||||||
|
{indeterminate}
|
||||||
|
{progress}
|
||||||
|
expandAction={popover?.showPopover}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PopoverContainer
|
||||||
|
bind:this={popover}
|
||||||
|
id="downloads-popover"
|
||||||
|
{expanded}
|
||||||
|
{popoverAction}
|
||||||
|
expandStart="right"
|
||||||
|
>
|
||||||
|
<div id="downloads-header">
|
||||||
|
<div class="downloads-header-title">downloads</div>
|
||||||
|
</div>
|
||||||
|
<div id="downloads-list">
|
||||||
|
<div class="list-stub">
|
||||||
|
<Meowbalt emotion="think" />
|
||||||
|
<span>your downloads will appear here!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#downloads-manager {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: end;
|
||||||
|
z-index: 9;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downloads-manager :global(#downloads-popover) {
|
||||||
|
padding: 16px;
|
||||||
|
max-width: 425px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downloads-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloads-header-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downloads-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-stub {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gray);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 25px;
|
||||||
|
text-align: center;
|
||||||
|
gap: var(--padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-stub :global(.meowbalt) {
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 535px) {
|
||||||
|
#downloads-manager {
|
||||||
|
top: calc(env(safe-area-inset-top) - var(--padding) + 4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
132
web/src/components/downloads/DownloadStatus.svelte
Normal file
132
web/src/components/downloads/DownloadStatus.svelte
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import IconArrowDown from "@tabler/icons-svelte/IconArrowDown.svelte";
|
||||||
|
|
||||||
|
export let indeterminate = false;
|
||||||
|
export let progress: number = 0;
|
||||||
|
export let expandAction: () => void;
|
||||||
|
|
||||||
|
$: progressStroke = `${progress}, 100`;
|
||||||
|
const indeterminateStroke = "15, 5";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="downloads-status"
|
||||||
|
on:click={expandAction}
|
||||||
|
class:completed={progress >= 100}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
id="progress-ring"
|
||||||
|
class:indeterminate
|
||||||
|
class:progressive={progress > 0 && !indeterminate}
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="19"
|
||||||
|
cy="19"
|
||||||
|
r="16"
|
||||||
|
fill="none"
|
||||||
|
stroke-dasharray={indeterminate
|
||||||
|
? indeterminateStroke
|
||||||
|
: progressStroke}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="icon-holder">
|
||||||
|
<IconArrowDown />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#downloads-status {
|
||||||
|
--download-status-glow: 0 0 8px 0px var(--button-elevated-hover);
|
||||||
|
|
||||||
|
pointer-events: all;
|
||||||
|
padding: 7px;
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow:
|
||||||
|
var(--button-box-shadow),
|
||||||
|
var(--download-status-glow);
|
||||||
|
|
||||||
|
transition: box-shadow 0.2s, background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downloads-status.completed {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--blue) inset,
|
||||||
|
var(--download-status-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global([data-theme="light"]) #downloads-status.completed {
|
||||||
|
background-color: #e0eeff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global([data-theme="dark"]) #downloads-status.completed {
|
||||||
|
background-color: #1f3249;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-holder {
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--button-elevated-hover);
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-holder :global(svg) {
|
||||||
|
height: 21px;
|
||||||
|
width: 21px;
|
||||||
|
stroke: var(--secondary);
|
||||||
|
transition: stroke 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed .icon-holder {
|
||||||
|
background-color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed .icon-holder :global(svg) {
|
||||||
|
stroke: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-ring {
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-ring circle {
|
||||||
|
stroke: var(--blue);
|
||||||
|
stroke-width: 4;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-ring.progressive circle {
|
||||||
|
transition: stroke-dasharray 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-ring.progressive,
|
||||||
|
#progress-ring.indeterminate {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-ring.indeterminate {
|
||||||
|
animation: spin 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-ring.indeterminate circle {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed #progress-ring {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { t } from "$lib/i18n/translations";
|
import { t } from "$lib/i18n/translations";
|
||||||
|
|
||||||
import type { MeowbaltEmotions } from "$lib/types/meowbalt";
|
import type { MeowbaltEmotions } from "$lib/types/meowbalt";
|
||||||
|
|
||||||
export let emotion: MeowbaltEmotions;
|
export let emotion: MeowbaltEmotions;
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
import NotchSticker from "$components/misc/NotchSticker.svelte";
|
import NotchSticker from "$components/misc/NotchSticker.svelte";
|
||||||
import DialogHolder from "$components/dialog/DialogHolder.svelte";
|
import DialogHolder from "$components/dialog/DialogHolder.svelte";
|
||||||
import UpdateNotification from "$components/misc/UpdateNotification.svelte";
|
import UpdateNotification from "$components/misc/UpdateNotification.svelte";
|
||||||
|
import DownloadManager from "$components/downloads/DownloadManager.svelte";
|
||||||
|
|
||||||
$: reduceMotion =
|
$: reduceMotion =
|
||||||
$settings.appearance.reduceMotion || device.prefers.reducedMotion;
|
$settings.appearance.reduceMotion || device.prefers.reducedMotion;
|
||||||
|
@ -81,14 +82,15 @@
|
||||||
data-reduce-motion={reduceMotion}
|
data-reduce-motion={reduceMotion}
|
||||||
data-reduce-transparency={reduceTransparency}
|
data-reduce-transparency={reduceTransparency}
|
||||||
>
|
>
|
||||||
{#if $updated}
|
|
||||||
<UpdateNotification />
|
|
||||||
{/if}
|
|
||||||
{#if device.is.iPhone && app.is.installed}
|
{#if device.is.iPhone && app.is.installed}
|
||||||
<NotchSticker />
|
<NotchSticker />
|
||||||
{/if}
|
{/if}
|
||||||
|
<DownloadManager />
|
||||||
<DialogHolder />
|
<DialogHolder />
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
{#if $updated}
|
||||||
|
<UpdateNotification />
|
||||||
|
{/if}
|
||||||
<div id="content">
|
<div id="content">
|
||||||
{#if ($turnstileEnabled && $page.url.pathname === "/") || $turnstileCreated}
|
{#if ($turnstileEnabled && $page.url.pathname === "/") || $turnstileCreated}
|
||||||
<Turnstile />
|
<Turnstile />
|
||||||
|
|
Loading…
Reference in a new issue