mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 20:57:13 -05:00
add components
This commit is contained in:
parent
e398671e57
commit
3bb77f964d
5 changed files with 374 additions and 0 deletions
90
qt/launcher-gui/src/routes/Action.svelte
Normal file
90
qt/launcher-gui/src/routes/Action.svelte
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { tr } from "./stores";
|
||||||
|
import { ExistingVersions, Versions } from "@generated/anki/launcher_pb";
|
||||||
|
import Row from "$lib/components/Row.svelte";
|
||||||
|
import EnumSelector from "$lib/components/EnumSelector.svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
releases,
|
||||||
|
existing,
|
||||||
|
allowBetas,
|
||||||
|
choose = (_, __) => {},
|
||||||
|
}: {
|
||||||
|
releases: Versions;
|
||||||
|
existing: ExistingVersions;
|
||||||
|
allowBetas: boolean;
|
||||||
|
choose: (version: string, existing: boolean, current?: string) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const availableVersions = $derived(
|
||||||
|
releases.all
|
||||||
|
.filter((v) => allowBetas || !v.isPrerelease)
|
||||||
|
.map((v) => ({ label: v.version, value: v.version })),
|
||||||
|
);
|
||||||
|
|
||||||
|
let latest = $derived(availableVersions[0]?.value ?? null);
|
||||||
|
let selected = $derived(availableVersions[0]?.value ?? null);
|
||||||
|
let current = $derived(existing.current?.version);
|
||||||
|
|
||||||
|
let pyprojectModified = existing.pyprojectModifiedByUser;
|
||||||
|
|
||||||
|
function _choose(version: string, keepExisting: boolean = false) {
|
||||||
|
choose(version, keepExisting, current);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
{#if latest != null && latest != current}
|
||||||
|
<Row class="centre m-3">
|
||||||
|
<button class="btn btn-primary" onclick={() => _choose(latest)}>
|
||||||
|
{#if latest == null}
|
||||||
|
{$tr.launcherLatestAnki()}
|
||||||
|
{:else}
|
||||||
|
{$tr.launcherLatestAnkiVersion({
|
||||||
|
version: latest!,
|
||||||
|
})}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
|
{#if current != null}
|
||||||
|
<Row class="centre m-3">
|
||||||
|
<button class="btn btn-primary" onclick={() => _choose(current, true)}>
|
||||||
|
{#if pyprojectModified}
|
||||||
|
{$tr.launcherSyncProjectChanges()}
|
||||||
|
{:else}
|
||||||
|
{$tr.launcherKeepExistingVersion({ current })}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
|
<Row class="centre m-3">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
onclick={() => _choose(selected!)}
|
||||||
|
disabled={selected == null}
|
||||||
|
>
|
||||||
|
{$tr.launcherChooseAVersion()}
|
||||||
|
</button>
|
||||||
|
<div class="m-2">
|
||||||
|
{"->"}
|
||||||
|
</div>
|
||||||
|
<div style="width: 100px">
|
||||||
|
<EnumSelector bind:value={selected} choices={availableVersions} />
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
:global(.centre) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
65
qt/launcher-gui/src/routes/AnkiWillStart.svelte
Normal file
65
qt/launcher-gui/src/routes/AnkiWillStart.svelte
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts" module>
|
||||||
|
let count = $state(3);
|
||||||
|
let timeout: any = $state(undefined);
|
||||||
|
let firstRun = $state(true);
|
||||||
|
let launch = $state(Promise.resolve({}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { tr } from "./stores";
|
||||||
|
import Icon from "$lib/components/Icon.svelte";
|
||||||
|
import { checkDecagramOutline } from "$lib/components/icons";
|
||||||
|
import Warning from "./Warning.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { exit, launchAnki } from "@generated/backend-launcher";
|
||||||
|
import type { ChooseVersionResponse } from "@generated/anki/launcher_pb";
|
||||||
|
import IconConstrain from "$lib/components/IconConstrain.svelte";
|
||||||
|
import Spinner from "./Spinner.svelte";
|
||||||
|
|
||||||
|
const { res }: { res: ChooseVersionResponse } = $props();
|
||||||
|
const { warmingUp } = res;
|
||||||
|
|
||||||
|
if (firstRun) {
|
||||||
|
firstRun = false;
|
||||||
|
launch = launchAnki({});
|
||||||
|
|
||||||
|
const countdown = () => {
|
||||||
|
count -= 1;
|
||||||
|
if (count <= 0) {
|
||||||
|
exit({});
|
||||||
|
} else {
|
||||||
|
timeout = setTimeout(countdown, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!warmingUp) {
|
||||||
|
timeout = setTimeout(countdown, 1000);
|
||||||
|
onMount(() => {
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// wait for warm-up to end
|
||||||
|
launch.then(countdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await launch}
|
||||||
|
<Spinner>
|
||||||
|
<div>{$tr.launcherAnkiIsWarmingUp()}</div>
|
||||||
|
{#if warmingUp}
|
||||||
|
<div class="m-1">{$tr.launcherThisMayTake()}</div>
|
||||||
|
{/if}
|
||||||
|
</Spinner>
|
||||||
|
{:then}
|
||||||
|
<!-- TODO: replace with Spinner showing checkmark/cross -->
|
||||||
|
<Warning warning={$tr.launcherWillCloseIn({ count })} className="alert-success">
|
||||||
|
<IconConstrain iconSize={100} slot="icon">
|
||||||
|
<Icon icon={checkDecagramOutline} />
|
||||||
|
</IconConstrain>
|
||||||
|
</Warning>
|
||||||
|
{/await}
|
||||||
64
qt/launcher-gui/src/routes/Options.svelte
Normal file
64
qt/launcher-gui/src/routes/Options.svelte
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { tr } from "./stores";
|
||||||
|
import EnumSelectorRow from "$lib/components/EnumSelectorRow.svelte";
|
||||||
|
import SettingTitle from "$lib/components/SettingTitle.svelte";
|
||||||
|
import SwitchRow from "$lib/components/SwitchRow.svelte";
|
||||||
|
import TitledContainer from "$lib/components/TitledContainer.svelte";
|
||||||
|
import { Mirror } from "@generated/anki/launcher_pb";
|
||||||
|
|
||||||
|
let {
|
||||||
|
allowBetas = $bindable(),
|
||||||
|
downloadCaching = $bindable(),
|
||||||
|
mirrors,
|
||||||
|
selectedMirror = $bindable(),
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const availableMirrors = $derived(
|
||||||
|
mirrors.map(({ mirror, name }) => ({
|
||||||
|
label: name,
|
||||||
|
value: mirror,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
// only the labels are expected to change
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
|
selectedMirror = availableMirrors[0].value ?? Mirror.DISABLED;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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>
|
||||||
70
qt/launcher-gui/src/routes/Spinner.svelte
Normal file
70
qt/launcher-gui/src/routes/Spinner.svelte
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { pageTheme } from "$lib/sveltelib/theme";
|
||||||
|
import { type Snippet } from "svelte";
|
||||||
|
|
||||||
|
let { label = "", children }: { label?: string; children?: Snippet } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- spinner taken from https://loading.io/css/; CC0 -->
|
||||||
|
<div class="progress">
|
||||||
|
<div class="spinner" class:nightMode={$pageTheme.isDark}>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div id="label">
|
||||||
|
{label}
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.spinner {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 32px;
|
||||||
|
border: 2px solid #000;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
|
border-color: #000 transparent transparent transparent;
|
||||||
|
}
|
||||||
|
&.nightMode div {
|
||||||
|
border-top-color: #fff;
|
||||||
|
}
|
||||||
|
div:nth-child(1) {
|
||||||
|
animation-delay: -0.45s;
|
||||||
|
}
|
||||||
|
div:nth-child(2) {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
div:nth-child(3) {
|
||||||
|
animation-delay: -0.15s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#label {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
85
qt/launcher-gui/src/routes/Term.svelte
Normal file
85
qt/launcher-gui/src/routes/Term.svelte
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { on } from "@tslib/events";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { protoBase64 } from "@bufbuild/protobuf";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { Terminal } from "@xterm/xterm";
|
||||||
|
import { WebglAddon } from "@xterm/addon-webgl";
|
||||||
|
import "@xterm/xterm/css/xterm.css";
|
||||||
|
|
||||||
|
let {
|
||||||
|
term = $bindable(),
|
||||||
|
open = $bindable(false),
|
||||||
|
}: { term: Terminal | undefined; open: boolean } = $props();
|
||||||
|
|
||||||
|
let termRef: HTMLDivElement;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
term = new Terminal({
|
||||||
|
fontFamily: '"Cascadia Code", Menlo, monospace',
|
||||||
|
disableStdin: true,
|
||||||
|
rows: 10,
|
||||||
|
cols: 50,
|
||||||
|
cursorStyle: "underline",
|
||||||
|
cursorInactiveStyle: "none",
|
||||||
|
altClickMovesCursor: false,
|
||||||
|
// TODO: saw this in the docs, but do we need it?
|
||||||
|
// windowsMode: navigator.platform.indexOf("Win") != -1,
|
||||||
|
});
|
||||||
|
// term.options.
|
||||||
|
|
||||||
|
term.open(termRef);
|
||||||
|
|
||||||
|
// dom renderer has viewport issues, try webgl
|
||||||
|
try {
|
||||||
|
const webgl = new WebglAddon();
|
||||||
|
term.loadAddon(webgl);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("WebGL addon threw an exception during load", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlisten = listen<string>("pty-data", (e) => {
|
||||||
|
const data = protoBase64.dec(e.payload);
|
||||||
|
open = true;
|
||||||
|
term!.write(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// prevent wheel events from scrolling page if terminal has scrollback
|
||||||
|
const unsub = on(
|
||||||
|
document.querySelector(".xterm")! as HTMLElement,
|
||||||
|
"wheel",
|
||||||
|
(e) => {
|
||||||
|
if (term && term.buffer.active.baseY > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((cb) => cb());
|
||||||
|
unsub();
|
||||||
|
term!.dispose();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="term-container centre" style:display={open ? "block" : "none"}>
|
||||||
|
<div class="term" bind:this={termRef}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.term-container {
|
||||||
|
display: flex;
|
||||||
|
margin: 20px 0 50px;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.term {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue