mirror of
https://github.com/ankitects/anki.git
synced 2025-11-07 13:17:12 -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
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as tr from "@generated/ftl-launcher";
|
|
||||||
import {
|
import {
|
||||||
Mirror,
|
Mirror,
|
||||||
type Options,
|
type Options as OptionsProto,
|
||||||
type ChooseVersionResponse,
|
type ChooseVersionResponse,
|
||||||
type GetLangsResponse_Pair,
|
type GetLangsResponse_Pair,
|
||||||
type GetMirrorsResponse_Pair,
|
type GetMirrorsResponse_Pair,
|
||||||
} from "@generated/anki/launcher_pb";
|
} 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 Row from "$lib/components/Row.svelte";
|
||||||
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
||||||
import SettingTitle from "$lib/components/SettingTitle.svelte";
|
import SettingTitle from "$lib/components/SettingTitle.svelte";
|
||||||
import TitledContainer from "$lib/components/TitledContainer.svelte";
|
import TitledContainer from "$lib/components/TitledContainer.svelte";
|
||||||
import Container from "$lib/components/Container.svelte";
|
import Container from "$lib/components/Container.svelte";
|
||||||
import SwitchRow from "$lib/components/SwitchRow.svelte";
|
import { tr } from "./stores";
|
||||||
import EnumSelector from "$lib/components/EnumSelector.svelte";
|
|
||||||
import Warning from "./Warning.svelte";
|
import Warning from "./Warning.svelte";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import Action from "./Action.svelte";
|
||||||
import { protoBase64 } from "@bufbuild/protobuf";
|
import Spinner from "./Spinner.svelte";
|
||||||
import { currentLang, versionsStore } from "./stores";
|
import Options from "./Options.svelte";
|
||||||
import { onMount } from "svelte";
|
import Term from "./Term.svelte";
|
||||||
|
import AnkiWillStart from "./AnkiWillStart.svelte";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
|
||||||
|
|
||||||
// TODO: why
|
|
||||||
/* eslint-disable prefer-const */
|
|
||||||
let {
|
let {
|
||||||
langs,
|
langs,
|
||||||
selectedLang = $bindable(),
|
selectedLang = $bindable(),
|
||||||
|
|
@ -37,115 +37,58 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}: {
|
}: {
|
||||||
langs: GetLangsResponse_Pair[];
|
langs: GetLangsResponse_Pair[];
|
||||||
selectedLang: string;
|
selectedLang: string;
|
||||||
options: Options;
|
options: OptionsProto;
|
||||||
mirrors: GetMirrorsResponse_Pair[];
|
mirrors: GetMirrorsResponse_Pair[];
|
||||||
} = $props();
|
} = $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(
|
const availableLangs = $derived(
|
||||||
langs.map((p) => ({ label: p.name, value: p.locale })),
|
langs.map((p) => ({ label: p.name, value: p.locale })),
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableMirrors = $derived(
|
let allowBetas = $state(options.allowBetas);
|
||||||
mirrors.map(({ mirror, name }) => ({
|
let downloadCaching = $state(options.downloadCaching);
|
||||||
label: name,
|
let selectedMirror = $state(Mirror.DISABLED);
|
||||||
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 choosePromise: Promise<ChooseVersionResponse | null> = $state(
|
let choosePromise: Promise<ChooseVersionResponse | null> = $state(
|
||||||
Promise.resolve(null),
|
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({
|
choosePromise = chooseVersion({
|
||||||
version,
|
version,
|
||||||
keepExisting,
|
keepExisting,
|
||||||
options: { allowBetas, downloadCaching, mirror: selectedMirror },
|
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>
|
</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
|
<Container
|
||||||
breakpoint="sm"
|
breakpoint="sm"
|
||||||
--gutter-inline="0.25rem"
|
--gutter-inline="0.25rem"
|
||||||
--gutter-block="0.75rem"
|
--gutter-block="0.75rem"
|
||||||
class="container-columns"
|
class="container-columns"
|
||||||
>
|
>
|
||||||
{#key $currentLang}
|
|
||||||
<Row class="row-columns">
|
<Row class="row-columns">
|
||||||
<TitledContainer title={""}>
|
<TitledContainer>
|
||||||
<Row --cols={2} slot="title" class="title">
|
<Row --cols={2} slot="title" class="title">
|
||||||
<img src="/anki.png" alt="logo" class="logo" />
|
<img src="/anki.png" alt="logo" class="logo" />
|
||||||
<h1 class="title">{tr.launcherTitle()}</h1>
|
<h1 class="title">{$tr.launcherTitle()}</h1>
|
||||||
</Row>
|
</Row>
|
||||||
<EnumSelectorRow
|
<EnumSelectorRow
|
||||||
breakpoint="sm"
|
breakpoint="sm"
|
||||||
|
|
@ -155,123 +98,58 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
hideRevert
|
hideRevert
|
||||||
>
|
>
|
||||||
<SettingTitle>
|
<SettingTitle>
|
||||||
{tr.launcherLanguage()}
|
{$tr.launcherLanguage()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</EnumSelectorRow>
|
</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}
|
{#await choosePromise}
|
||||||
<Warning warning={tr.launcherSyncing()} className="alert-info" />
|
<Row class="centre m-3">
|
||||||
{:then res}
|
<Spinner label={$tr.launcherSyncing()} />
|
||||||
{#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>
|
|
||||||
</Row>
|
</Row>
|
||||||
{#key $currentLang}
|
{:then res}
|
||||||
<Row class="row-columns">
|
{#if res === null}
|
||||||
<TitledContainer title={tr.launcherAdvanced()}>
|
{#await loadPromise}
|
||||||
<div class="m-2">
|
<Row class="centre m-3">
|
||||||
<SwitchRow
|
<Spinner label={$tr.launcherLoadingVersions()} />
|
||||||
bind:value={allowBetas}
|
</Row>
|
||||||
defaultValue={allowBetas}
|
{:then [releases, existing]}
|
||||||
hideRevert
|
<Action {releases} {existing} {allowBetas} {choose} />
|
||||||
>
|
{:catch e}
|
||||||
<SettingTitle>
|
{setError(e)}
|
||||||
{tr.launcherAllowBetasToggle()}
|
<Warning
|
||||||
</SettingTitle>
|
warning={$tr.lauuncherFailedToLoadVersions()}
|
||||||
</SwitchRow>
|
className="alert-danger"
|
||||||
</div>
|
/>
|
||||||
<div class="m-2">
|
{/await}
|
||||||
<SwitchRow
|
{:else}
|
||||||
bind:value={downloadCaching}
|
<Row class="centre m-3">
|
||||||
defaultValue={downloadCaching}
|
<AnkiWillStart {res} />
|
||||||
hideRevert
|
</Row>
|
||||||
>
|
{/if}
|
||||||
<SettingTitle>
|
{:catch e}
|
||||||
{tr.launcherDownloadCaching()}
|
{setError(e)}
|
||||||
</SettingTitle>
|
<Warning
|
||||||
</SwitchRow>
|
warning={$tr.launcherFailedToSync()}
|
||||||
</div>
|
className="alert-danger"
|
||||||
<div class="m-2">
|
/>
|
||||||
<EnumSelectorRow
|
{/await}
|
||||||
breakpoint="sm"
|
{#if error != null}
|
||||||
bind:value={selectedMirror}
|
<Row>
|
||||||
choices={availableMirrors}
|
<pre>{error.message}</pre>
|
||||||
defaultValue={selectedMirror}
|
</Row>
|
||||||
hideRevert
|
{/if}
|
||||||
>
|
|
||||||
<SettingTitle>
|
|
||||||
{tr.launcherUseMirror()}
|
|
||||||
</SettingTitle>
|
|
||||||
</EnumSelectorRow>
|
|
||||||
</div>
|
|
||||||
</TitledContainer>
|
</TitledContainer>
|
||||||
</Row>
|
</Row>
|
||||||
{/key}
|
<Term bind:term bind:open={termOpen} />
|
||||||
|
{#if !chosen}
|
||||||
|
<Row class="row-columns">
|
||||||
|
<Options
|
||||||
|
{mirrors}
|
||||||
|
bind:allowBetas
|
||||||
|
bind:downloadCaching
|
||||||
|
bind:selectedMirror
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -305,11 +183,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group {
|
pre {
|
||||||
margin-top: 1em;
|
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||||
}
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||||
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
#terminal {
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
width: 100%;
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue