Anki/qt/launcher-gui/src/routes/Normal.svelte
2025-10-24 20:04:14 +08:00

143 lines
3.9 KiB
Svelte

<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import {
Mirror,
type Options as OptionsProto,
type ChooseVersionResponse,
type GetMirrorsResponse_Pair,
} from "@generated/anki/launcher_pb";
import {
chooseVersion,
getAvailableVersions,
getExistingVersions,
} from "@generated/backend-launcher";
import Row from "$lib/components/Row.svelte";
import { tr } from "./stores";
import Warning from "./Warning.svelte";
import Action from "./Action.svelte";
import Spinner from "./Spinner.svelte";
import Options from "./Options.svelte";
import Term from "./Term.svelte";
import AnkiWillStart from "./AnkiWillStart.svelte";
import type { Terminal } from "@xterm/xterm";
import { tick } from "svelte";
let {
options,
mirrors,
uninstall,
footer = $bindable(),
}: {
options: OptionsProto;
mirrors: GetMirrorsResponse_Pair[];
uninstall: (() => void) | null;
footer: any; // https://github.com/sveltejs/svelte/issues/15182
} = $props();
footer = _footer;
let releasesPromise = $state(getAvailableVersions({}, { alertOnError: false }));
let existingPromise = $state(getExistingVersions({}, { alertOnError: false }));
let loadPromise = $derived(Promise.all([releasesPromise, existingPromise]));
let allowBetas = $state(options.allowBetas);
let downloadCaching = $state(options.downloadCaching);
let selectedMirror = $state(Mirror.DISABLED);
let choosePromise: Promise<ChooseVersionResponse | null> = $state(
Promise.resolve(null),
);
let error: Error | null = $state(null);
const setError = (e: Error) => {
tick().then(() => (error = e));
};
let term: Terminal | undefined = $state(undefined);
let termOpen = $state(false);
let chosen = $state(false);
const choose = (version: string, keepExisting: boolean, current?: string) => {
chosen = true;
term?.reset();
choosePromise = chooseVersion({
version,
keepExisting,
options: { allowBetas, downloadCaching, mirror: selectedMirror },
...(current ? { current } : {}),
});
};
</script>
{#await choosePromise}
<Row class="centre m-3">
<Spinner label={$tr.launcherSyncing()} />
</Row>
{:then res}
{#if res === null}
{#await loadPromise}
<Row class="centre m-3">
<Spinner label={$tr.launcherLoadingVersions()} />
</Row>
{:then [releases, existing]}
<Action {releases} {existing} {allowBetas} {choose} {uninstall} />
{:catch e}
{setError(e)}
<Warning
warning={$tr.lauuncherFailedToLoadVersions()}
className="alert-danger"
/>
{/await}
{:else}
<Row class="centre m-3">
<AnkiWillStart {res} />
</Row>
{/if}
{:catch e}
{setError(e)}
<Warning warning={$tr.launcherFailedToSync()} className="alert-danger" />
{/await}
{#if error != null}
<Row>
<pre>{error.message}</pre>
</Row>
{/if}
{#snippet _footer()}
<Term bind:term bind:open={termOpen} />
{#if !chosen}
<Row class="row-columns">
<Options
{mirrors}
bind:allowBetas
bind:downloadCaching
bind:selectedMirror
/>
</Row>
{/if}
{/snippet}
<style lang="scss">
:root {
font-size: 16px;
font-weight: 400;
text-rendering: optimizeLegibility;
font-synthesis: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
:global(.centre) {
justify-content: center;
}
pre {
white-space: pre-wrap;
}
</style>