mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
refactor Start.svelte
This commit is contained in:
parent
3bb77f964d
commit
49e850c7da
1 changed files with 101 additions and 223 deletions
|
|
@ -3,32 +3,32 @@ Copyright: Ankitects Pty Ltd and contributors
|
|||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
-->
|
||||
<script lang="ts">
|
||||
import * as tr from "@generated/ftl-launcher";
|
||||
import {
|
||||
Mirror,
|
||||
type Options,
|
||||
type Options as OptionsProto,
|
||||
type ChooseVersionResponse,
|
||||
type GetLangsResponse_Pair,
|
||||
type GetMirrorsResponse_Pair,
|
||||
} from "@generated/anki/launcher_pb";
|
||||
import { chooseVersion } from "@generated/backend-launcher";
|
||||
import {
|
||||
chooseVersion,
|
||||
getAvailableVersions,
|
||||
getExistingVersions,
|
||||
} from "@generated/backend-launcher";
|
||||
import Row from "$lib/components/Row.svelte";
|
||||
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
||||
import SettingTitle from "$lib/components/SettingTitle.svelte";
|
||||
import TitledContainer from "$lib/components/TitledContainer.svelte";
|
||||
import Container from "$lib/components/Container.svelte";
|
||||
import SwitchRow from "$lib/components/SwitchRow.svelte";
|
||||
import EnumSelector from "$lib/components/EnumSelector.svelte";
|
||||
import { tr } from "./stores";
|
||||
import Warning from "./Warning.svelte";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { protoBase64 } from "@bufbuild/protobuf";
|
||||
import { currentLang, versionsStore } from "./stores";
|
||||
import { onMount } from "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 { Terminal } from "@xterm/xterm";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
// TODO: why
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
langs,
|
||||
selectedLang = $bindable(),
|
||||
|
|
@ -37,241 +37,119 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}: {
|
||||
langs: GetLangsResponse_Pair[];
|
||||
selectedLang: string;
|
||||
options: Options;
|
||||
options: OptionsProto;
|
||||
mirrors: GetMirrorsResponse_Pair[];
|
||||
} = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let releasesPromise = $state(getAvailableVersions({}, { alertOnError: false }));
|
||||
let existingPromise = $state(getExistingVersions({}, { alertOnError: false }));
|
||||
let loadPromise = $derived(Promise.all([releasesPromise, existingPromise]));
|
||||
|
||||
const availableLangs = $derived(
|
||||
langs.map((p) => ({ label: p.name, value: p.locale })),
|
||||
);
|
||||
|
||||
const availableMirrors = $derived(
|
||||
mirrors.map(({ mirror, name }) => ({
|
||||
label: name,
|
||||
value: mirror,
|
||||
})),
|
||||
);
|
||||
// only the labels are expected to change
|
||||
// svelte-ignore state_referenced_locally
|
||||
let selectedMirror = $state(availableMirrors[0].value ?? Mirror.DISABLED);
|
||||
|
||||
let allowBetas: boolean = $state(options.allowBetas);
|
||||
let downloadCaching: boolean = $state(options.downloadCaching);
|
||||
|
||||
const availableVersions = $derived(
|
||||
$versionsStore?.all.map((v) => ({ label: v, value: v })) ?? [],
|
||||
);
|
||||
// const availableLatestVersions = $derived($versionsStore?.latest ?? []);
|
||||
let selectedVersion = $derived(availableVersions[0]?.value);
|
||||
/* eslint-disable prefer-const */
|
||||
let currentVersion = $derived($versionsStore?.current);
|
||||
let latestVersion = $derived($versionsStore?.latest[0]);
|
||||
/* eslint-enable prefer-const */
|
||||
let allowBetas = $state(options.allowBetas);
|
||||
let downloadCaching = $state(options.downloadCaching);
|
||||
let selectedMirror = $state(Mirror.DISABLED);
|
||||
|
||||
let choosePromise: Promise<ChooseVersionResponse | null> = $state(
|
||||
Promise.resolve(null),
|
||||
);
|
||||
|
||||
const choose = (version: string, keepExisting: boolean) => {
|
||||
let error: Error | null = $state(null);
|
||||
const setError = (e: Error) => {
|
||||
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 } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
let termRef: HTMLDivElement;
|
||||
let termTabRef: HTMLDetailsElement;
|
||||
|
||||
onMount(() => {
|
||||
const term = new Terminal({
|
||||
disableStdin: true,
|
||||
rows: 12,
|
||||
cols: 60,
|
||||
cursorStyle: "underline",
|
||||
cursorInactiveStyle: "none",
|
||||
// TODO: saw this in the docs, but do we need it?
|
||||
windowsMode: navigator.platform.indexOf("Win") != -1,
|
||||
});
|
||||
|
||||
term.open(termRef);
|
||||
|
||||
termRef.oncontextmenu = (e) => {
|
||||
e.preventDefault();
|
||||
term.selectAll();
|
||||
const lines = term.getSelection().trim();
|
||||
term.clearSelection();
|
||||
navigator.clipboard.writeText(lines);
|
||||
};
|
||||
|
||||
const unlisten = listen<string>("pty-data", (e) => {
|
||||
const data = protoBase64.dec(e.payload);
|
||||
if (!termTabRef.open) {
|
||||
termTabRef.open = true;
|
||||
}
|
||||
term.write(data);
|
||||
});
|
||||
|
||||
return () => {
|
||||
term.dispose();
|
||||
unlisten.then((cb) => cb());
|
||||
};
|
||||
});
|
||||
|
||||
// const zoomIn = () => ($zoomFactor = Math.min($zoomFactor + 0.1, 3));
|
||||
// const zoomOut = () => ($zoomFactor = Math.max($zoomFactor - 0.1, 0.5));
|
||||
</script>
|
||||
|
||||
<!-- TODO: this breaks scrolling on wsl, fine on win -->
|
||||
<!-- <svelte:window -->
|
||||
<!-- onwheel={(e) => { -->
|
||||
<!-- if (!e.ctrlKey) { -->
|
||||
<!-- return true; -->
|
||||
<!-- } -->
|
||||
<!-- e.preventDefault(); -->
|
||||
<!-- e.deltaY < 0 ? zoomIn() : zoomOut(); -->
|
||||
<!-- }} -->
|
||||
<!-- /> -->
|
||||
|
||||
<Container
|
||||
breakpoint="sm"
|
||||
--gutter-inline="0.25rem"
|
||||
--gutter-block="0.75rem"
|
||||
class="container-columns"
|
||||
>
|
||||
{#key $currentLang}
|
||||
<Row class="row-columns">
|
||||
<TitledContainer title={""}>
|
||||
<Row --cols={2} slot="title" class="title">
|
||||
<img src="/anki.png" alt="logo" class="logo" />
|
||||
<h1 class="title">{tr.launcherTitle()}</h1>
|
||||
</Row>
|
||||
<EnumSelectorRow
|
||||
breakpoint="sm"
|
||||
bind:value={selectedLang}
|
||||
choices={availableLangs}
|
||||
defaultValue={selectedLang}
|
||||
hideRevert
|
||||
>
|
||||
<SettingTitle>
|
||||
{tr.launcherLanguage()}
|
||||
</SettingTitle>
|
||||
</EnumSelectorRow>
|
||||
<div class="group">
|
||||
{#if latestVersion != null && latestVersion != currentVersion}
|
||||
<Row class="centre m-3">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={() => choose(latestVersion, false)}
|
||||
>
|
||||
{#if latestVersion == null}
|
||||
{tr.launcherLatestAnki()}
|
||||
{:else}
|
||||
{tr.launcherLatestAnkiVersion({
|
||||
version: latestVersion!,
|
||||
})}
|
||||
{/if}
|
||||
</button>
|
||||
</Row>
|
||||
{/if}
|
||||
{#if currentVersion != null}
|
||||
<Row class="centre m-3">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={() => choose(currentVersion, true)}
|
||||
>
|
||||
{tr.launcherKeepExistingVersion({
|
||||
current: currentVersion ?? "N/A",
|
||||
})}
|
||||
</button>
|
||||
</Row>
|
||||
{/if}
|
||||
<Row class="centre m-3">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={() => choose(selectedVersion!, false)}
|
||||
disabled={selectedVersion == null}
|
||||
>
|
||||
{tr.launcherChooseAVersion()}
|
||||
</button>
|
||||
<div class="m-2">
|
||||
{"->"}
|
||||
</div>
|
||||
<div style="width: 100px">
|
||||
{#if availableVersions.length !== 0}
|
||||
<EnumSelector
|
||||
bind:value={selectedVersion}
|
||||
choices={availableVersions}
|
||||
/>
|
||||
{:else}
|
||||
{"loading"}
|
||||
{/if}
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</TitledContainer>
|
||||
</Row>
|
||||
{#await choosePromise}
|
||||
<Warning warning={tr.launcherSyncing()} className="alert-info" />
|
||||
{:then res}
|
||||
{#if res != null}
|
||||
<Warning
|
||||
warning={tr.launcherAnkiWillStartShortly()}
|
||||
className="alert-success"
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
{/key}
|
||||
<Row class="row-columns">
|
||||
<details bind:this={termTabRef}>
|
||||
{#key $currentLang}
|
||||
<summary>{tr.launcherOutput()}</summary>
|
||||
{/key}
|
||||
<div id="terminal" bind:this={termRef}></div>
|
||||
</details>
|
||||
<TitledContainer>
|
||||
<Row --cols={2} slot="title" class="title">
|
||||
<img src="/anki.png" alt="logo" class="logo" />
|
||||
<h1 class="title">{$tr.launcherTitle()}</h1>
|
||||
</Row>
|
||||
<EnumSelectorRow
|
||||
breakpoint="sm"
|
||||
bind:value={selectedLang}
|
||||
choices={availableLangs}
|
||||
defaultValue={selectedLang}
|
||||
hideRevert
|
||||
>
|
||||
<SettingTitle>
|
||||
{$tr.launcherLanguage()}
|
||||
</SettingTitle>
|
||||
</EnumSelectorRow>
|
||||
{#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} />
|
||||
{: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}
|
||||
</TitledContainer>
|
||||
</Row>
|
||||
{#key $currentLang}
|
||||
<Term bind:term bind:open={termOpen} />
|
||||
{#if !chosen}
|
||||
<Row class="row-columns">
|
||||
<TitledContainer title={tr.launcherAdvanced()}>
|
||||
<div class="m-2">
|
||||
<SwitchRow
|
||||
bind:value={allowBetas}
|
||||
defaultValue={allowBetas}
|
||||
hideRevert
|
||||
>
|
||||
<SettingTitle>
|
||||
{tr.launcherAllowBetasToggle()}
|
||||
</SettingTitle>
|
||||
</SwitchRow>
|
||||
</div>
|
||||
<div class="m-2">
|
||||
<SwitchRow
|
||||
bind:value={downloadCaching}
|
||||
defaultValue={downloadCaching}
|
||||
hideRevert
|
||||
>
|
||||
<SettingTitle>
|
||||
{tr.launcherDownloadCaching()}
|
||||
</SettingTitle>
|
||||
</SwitchRow>
|
||||
</div>
|
||||
<div class="m-2">
|
||||
<EnumSelectorRow
|
||||
breakpoint="sm"
|
||||
bind:value={selectedMirror}
|
||||
choices={availableMirrors}
|
||||
defaultValue={selectedMirror}
|
||||
hideRevert
|
||||
>
|
||||
<SettingTitle>
|
||||
{tr.launcherUseMirror()}
|
||||
</SettingTitle>
|
||||
</EnumSelectorRow>
|
||||
</div>
|
||||
</TitledContainer>
|
||||
<Options
|
||||
{mirrors}
|
||||
bind:allowBetas
|
||||
bind:downloadCaching
|
||||
bind:selectedMirror
|
||||
/>
|
||||
</Row>
|
||||
{/key}
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -305,11 +183,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.group {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#terminal {
|
||||
width: 100%;
|
||||
pre {
|
||||
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue