Spaces:
Paused
Paused
| <script lang="ts"> | |
| import "@fontsource-variable/noto-sans-mono"; | |
| import API from "$lib/api/api"; | |
| import { t } from "$lib/i18n/translations"; | |
| import { createDialog } from "$lib/state/dialogs"; | |
| import { downloadFile } from "$lib/download"; | |
| import type { DialogInfo } from "$lib/types/dialog"; | |
| export let url: string; | |
| export let disabled = false; | |
| export let loading = false; | |
| $: buttonText = ">>"; | |
| $: buttonAltText = $t("a11y.save.download"); | |
| let defaultErrorPopup: DialogInfo = { | |
| id: "save-error", | |
| type: "small", | |
| meowbalt: "error", | |
| buttons: [ | |
| { | |
| text: $t("button.gotit"), | |
| main: true, | |
| action: () => {}, | |
| }, | |
| ], | |
| }; | |
| type DownloadButtonState = "idle" | "think" | "check" | "done" | "error"; | |
| const changeDownloadButton = (state: DownloadButtonState) => { | |
| disabled = state !== "idle"; | |
| loading = state === "think" || state === "check"; | |
| buttonText = { | |
| idle: ">>", | |
| think: "...", | |
| check: "..?", | |
| done: ">>>", | |
| error: "!!", | |
| }[state]; | |
| buttonAltText = $t( | |
| { | |
| idle: "a11y.save.download", | |
| think: "a11y.save.download.think", | |
| check: "a11y.save.download.check", | |
| done: "a11y.save.download.done", | |
| error: "a11y.save.download.error", | |
| }[state] | |
| ); | |
| // states that don't wait for anything, and thus can | |
| // transition back to idle after some period of time. | |
| const final: DownloadButtonState[] = ["done", "error"]; | |
| if (final.includes(state)) { | |
| setTimeout(() => changeDownloadButton("idle"), 1500); | |
| } | |
| }; | |
| export const download = async (link: string) => { | |
| changeDownloadButton("think"); | |
| const response = await API.request(link); | |
| if (!response) { | |
| changeDownloadButton("error"); | |
| return createDialog({ | |
| ...defaultErrorPopup, | |
| bodyText: $t("error.api.unreachable"), | |
| }); | |
| } | |
| if (response.status === "error") { | |
| changeDownloadButton("error"); | |
| return createDialog({ | |
| ...defaultErrorPopup, | |
| bodyText: $t(response.error.code, response?.error?.context), | |
| }); | |
| } | |
| if (response.status === "redirect") { | |
| changeDownloadButton("done"); | |
| return downloadFile({ | |
| url: response.url, | |
| urlType: "redirect", | |
| }); | |
| } | |
| if (response.status === "tunnel") { | |
| changeDownloadButton("check"); | |
| const probeResult = await API.probeCobaltTunnel(response.url); | |
| if (probeResult === 200) { | |
| changeDownloadButton("done"); | |
| return downloadFile({ | |
| url: response.url, | |
| }); | |
| } else { | |
| changeDownloadButton("error"); | |
| return createDialog({ | |
| ...defaultErrorPopup, | |
| bodyText: $t("error.tunnel.probe"), | |
| }); | |
| } | |
| } | |
| if (response.status === "picker") { | |
| changeDownloadButton("done"); | |
| const buttons = [ | |
| { | |
| text: $t("button.done"), | |
| main: true, | |
| action: () => {}, | |
| }, | |
| ]; | |
| if (response.audio) { | |
| const pickerAudio = response.audio; | |
| buttons.unshift({ | |
| text: $t("button.download.audio"), | |
| main: false, | |
| action: () => { | |
| downloadFile({ | |
| url: pickerAudio, | |
| }); | |
| }, | |
| }); | |
| } | |
| return createDialog({ | |
| id: "download-picker", | |
| type: "picker", | |
| items: response.picker, | |
| buttons, | |
| }); | |
| } | |
| changeDownloadButton("error"); | |
| return createDialog({ | |
| ...defaultErrorPopup, | |
| bodyText: $t("error.api.unknown_response"), | |
| }); | |
| }; | |
| </script> | |
| <button | |
| id="download-button" | |
| {disabled} | |
| on:click={() => download(url)} | |
| aria-label={buttonAltText} | |
| > | |
| <span id="download-state">{buttonText}</span> | |
| </button> | |
| <style> | |
| #download-button { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100%; | |
| min-width: 48px; | |
| border-radius: 0; | |
| padding: 0 12px; | |
| background: none; | |
| box-shadow: none; | |
| transform: none; | |
| border-left: 1.5px var(--input-border) solid; | |
| border-top-right-radius: var(--border-radius); | |
| border-bottom-right-radius: var(--border-radius); | |
| } | |
| #download-button:dir(rtl) { | |
| border-left: 0; | |
| border-top-right-radius: 0; | |
| border-bottom-right-radius: 0; | |
| border-right: 1.5px var(--input-border) solid; | |
| border-top-left-radius: var(--border-radius); | |
| border-bottom-left-radius: var(--border-radius); | |
| } | |
| #download-button:focus-visible { | |
| box-shadow: 0 0 0 2px var(--blue) inset; | |
| } | |
| #download-state { | |
| font-size: 24px; | |
| font-family: "Noto Sans Mono Variable", "Noto Sans Mono", | |
| "IBM Plex Mono", monospace; | |
| font-weight: 400; | |
| text-align: center; | |
| text-indent: -5px; | |
| letter-spacing: -5.3px; | |
| margin-bottom: 2px; | |
| } | |
| #download-button:disabled { | |
| cursor: unset; | |
| opacity: 0.7; | |
| } | |
| :global(#input-container.focused) #download-button { | |
| border-left: 2px var(--secondary) solid; | |
| } | |
| :global(#input-container.focused) #download-button:dir(rtl) { | |
| border-left: 0; | |
| border-right: 2px var(--secondary) solid; | |
| } | |
| @media (hover: hover) { | |
| #download-button:hover { | |
| background: var(--button-hover-transparent); | |
| } | |
| #download-button:disabled:hover { | |
| background: none; | |
| } | |
| } | |
| </style> | |