From 6d0ec5dd8561da80141b459265f87ff577dc4be5 Mon Sep 17 00:00:00 2001 From: wukko <me@wukko.me> Date: Mon, 16 Dec 2024 18:03:55 +0600 Subject: [PATCH] web: basic ui for the download queue manager --- .../downloads/DownloadManager.svelte | 109 +++++++++++++++ .../downloads/DownloadStatus.svelte | 132 ++++++++++++++++++ web/src/components/misc/Meowbalt.svelte | 1 - web/src/routes/+layout.svelte | 8 +- 4 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 web/src/components/downloads/DownloadManager.svelte create mode 100644 web/src/components/downloads/DownloadStatus.svelte diff --git a/web/src/components/downloads/DownloadManager.svelte b/web/src/components/downloads/DownloadManager.svelte new file mode 100644 index 00000000..6008adb5 --- /dev/null +++ b/web/src/components/downloads/DownloadManager.svelte @@ -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> diff --git a/web/src/components/downloads/DownloadStatus.svelte b/web/src/components/downloads/DownloadStatus.svelte new file mode 100644 index 00000000..743805c7 --- /dev/null +++ b/web/src/components/downloads/DownloadStatus.svelte @@ -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> diff --git a/web/src/components/misc/Meowbalt.svelte b/web/src/components/misc/Meowbalt.svelte index 26ad14b3..929c63aa 100644 --- a/web/src/components/misc/Meowbalt.svelte +++ b/web/src/components/misc/Meowbalt.svelte @@ -1,6 +1,5 @@ <script lang="ts"> import { t } from "$lib/i18n/translations"; - import type { MeowbaltEmotions } from "$lib/types/meowbalt"; export let emotion: MeowbaltEmotions; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index f930e227..6eb4abf8 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -25,6 +25,7 @@ import NotchSticker from "$components/misc/NotchSticker.svelte"; import DialogHolder from "$components/dialog/DialogHolder.svelte"; import UpdateNotification from "$components/misc/UpdateNotification.svelte"; + import DownloadManager from "$components/downloads/DownloadManager.svelte"; $: reduceMotion = $settings.appearance.reduceMotion || device.prefers.reducedMotion; @@ -81,14 +82,15 @@ data-reduce-motion={reduceMotion} data-reduce-transparency={reduceTransparency} > - {#if $updated} - <UpdateNotification /> - {/if} {#if device.is.iPhone && app.is.installed} <NotchSticker /> {/if} + <DownloadManager /> <DialogHolder /> <Sidebar /> + {#if $updated} + <UpdateNotification /> + {/if} <div id="content"> {#if ($turnstileEnabled && $page.url.pathname === "/") || $turnstileCreated} <Turnstile />