This commit is contained in:
Luc Mcgrady 2025-09-15 10:28:26 +02:00 committed by GitHub
commit 3307fdaf11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 372 additions and 5 deletions

View file

@ -1075,9 +1075,9 @@ title="{}" {}>{}</button>""".format(
self.overview = Overview(self) self.overview = Overview(self)
def setupReviewer(self) -> None: def setupReviewer(self) -> None:
from aqt.reviewer import Reviewer from aqt.reviewer import Reviewer, SvelteReviewer
self.reviewer = Reviewer(self) self.reviewer = SvelteReviewer(self) if True else Reviewer(self)
# Syncing # Syncing
########################################################################## ##########################################################################

View file

@ -334,6 +334,7 @@ def is_sveltekit_page(path: str) -> bool:
"import-csv", "import-csv",
"import-page", "import-page",
"image-occlusion", "image-occlusion",
"reviewer",
] ]

View file

@ -1228,6 +1228,69 @@ timerStopped = false;
setFlag = set_flag_on_current_card setFlag = set_flag_on_current_card
class SvelteReviewer(Reviewer):
def _answerButtons(self) -> str:
default = self._defaultEase()
assert isinstance(self.mw.col.sched, V3Scheduler)
labels = self.mw.col.sched.describe_next_states(self._v3.states)
def but(i: int, label: str):
if i == default:
id = "defease"
else:
id = ""
due = self._buttonTime(i, v3_labels=labels)
key = (
tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i))
if aqt.mw.pm.get_answer_key(i)
else ""
)
return {
"id": id,
"key": key,
"i": i,
"label": label,
"due": due,
}
return [but(ease, label) for ease, label in self._answerButtonList()] # type: ignore
def _remaining(self) -> str:
if not self.mw.col.conf["dueCounts"]:
return ""
idx, counts = self._v3.counts()
self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})")
return ""
def _showAnswerButton(self) -> None:
if self.card.should_show_timer():
maxTime = self.card.time_limit() / 1000
else:
maxTime = 0
self._remaining()
self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime))
def _linkHandler(self, url: str) -> None:
if url == "bottomReady":
self._showQuestion()
self._remaining()
return
super()._linkHandler(url)
def _initWeb(self) -> None:
self._reps = 0
# main window
self.web.load_sveltekit_page("reviewer")
# block default drag & drop behavior while allowing drop events to be received by JS handlers
self.web.allow_drops = True
self.web.eval("_blockDefaultDragDropBehavior();")
# ensure bottom web functions trigger
self.bottom.web = self.web
self.mw.bottomWeb.hide()
# if the last element is a comment, then the RUN_STATE_MUTATION code # if the last element is a comment, then the RUN_STATE_MUTATION code
# breaks due to the comment wrongly commenting out python code. # breaks due to the comment wrongly commenting out python code.
# To prevent this we put the js code on a separate line # To prevent this we put the js code on a separate line

View file

@ -0,0 +1,17 @@
<script>
import ReviewerBottomOuter from "./reviewer-bottom/ReviewerBottomOuter.svelte";
import ReviewerOuter from "./reviewerOuter.svelte";
</script>
<div>
<ReviewerOuter></ReviewerOuter>
<ReviewerBottomOuter></ReviewerBottomOuter>
</div>
<style>
div {
height: calc(100vh - 40px);
display: flex;
flex-direction: column;
}
</style>

View file

View file

@ -0,0 +1,14 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { bridgeCommand } from "@tslib/bridgecommand";
import type { AnswerButtonInfo } from "./types";
export let info: AnswerButtonInfo;
</script>
<button on:click={() => bridgeCommand(`ease${info.i}`)}>
{info.label}
</button>

View file

@ -0,0 +1,16 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
export let underlined: boolean;
export let cls: string;
</script>
<span class={cls}>
{#if underlined}
<u><slot /></u>
{:else}
<slot />
{/if}
</span>

View file

@ -0,0 +1,79 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type { Writable } from "svelte/store";
import AnswerButton from "./AnswerButton.svelte";
import { bridgeCommand } from "@tslib/bridgecommand";
import * as tr from "@generated/ftl";
import RemainingNumber from "./RemainingNumber.svelte";
import type { AnswerButtonInfo } from "./types";
export let answerButtons: Writable<AnswerButtonInfo[]>;
export let remaining: Writable<number[]>;
export let remainingIndex: Writable<number>;
$: console.log($remaining);
</script>
<div id="outer" class="fancy">
<div id="tableinner">
<div>
<button
title={tr.actionsShortcutKey({ val: "E" })}
on:click={() => bridgeCommand("edit")}
>
{tr.studyingEdit()}
</button>
</div>
<div class="review-buttons">
<span>
<RemainingNumber cls="new-count" underlined={$remainingIndex === 0}>
{$remaining[0]}
</RemainingNumber> +
<RemainingNumber cls="learn-count" underlined={$remainingIndex === 1}>
{$remaining[1]}
</RemainingNumber> +
<RemainingNumber cls="review-count" underlined={$remainingIndex === 2}>
{$remaining[2]}
</RemainingNumber>
</span>
<div>
{#if $answerButtons.length}
{#each $answerButtons as answerButton}
<AnswerButton info={answerButton}></AnswerButton>
{/each}
{:else}
<button on:click={() => bridgeCommand("ans")}>
{tr.studyingShowAnswer()}
</button>
{/if}
</div>
</div>
<div>
<button
on:click={() => bridgeCommand("more")}
title={tr.actionsShortcutKey({ val: "M" })}
>
{tr.studyingMore()}&#8615
</button>
</div>
</div>
</div>
<style lang="scss">
#tableinner {
width: 100%;
display: grid;
grid-template-columns: auto 1fr auto;
justify-items: center;
align-items: center;
}
.review-buttons {
display: flex;
flex-direction: column;
align-items: center;
}
</style>

View file

@ -0,0 +1,20 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { onMount } from "svelte";
import ReviewerBottom from "./ReviewerBottom.svelte";
import "./index.scss";
import { setupBottomBar } from "./reviewer-bottom";
let reviewerInfo: null | ReturnType<typeof setupBottomBar> = null;
onMount(() => {
reviewerInfo = setupBottomBar();
});
</script>
{#if reviewerInfo}
<ReviewerBottom {...reviewerInfo}></ReviewerBottom>
{/if}

View file

@ -1,9 +1,10 @@
/* Copyright: Ankitects Pty Ltd and contributors /* 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 */
@use "../../../../../ts/lib/sass/root-vars"; @use "../../../lib/sass/root-vars";
@use "../../../../../ts/lib/sass/vars" as *; @use "../../../lib/sass/vars" as *;
@use "../../../../../ts/lib/sass/card-counts"; @use "../../../lib/sass/card-counts";
@use "../../../lib/sass/buttons";
:root { :root {
--focus-color: #{palette-of(border-focus)}; --focus-color: #{palette-of(border-focus)};
@ -16,6 +17,8 @@
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-size: 12px;
height: 72px;
} }
#middle td[align="center"] { #middle td[align="center"] {

View file

@ -0,0 +1,90 @@
import { bridgeCommand } from "@tslib/bridgecommand";
import { writable } from "svelte/store";
import type { AnswerButtonInfo } from "./types";
export function setupBottomBar() {
/*
let timerStopped = false;
let maxTime = 0;
function updateTime(): void {
const timeNode = document.getElementById("time");
if (maxTime === 0) {
timeNode.textContent = "";
return;
}
globalThis.time = Math.min(maxTime, globalThis.time);
const m = Math.floor(globalThis.time / 60);
const s = globalThis.time % 60;
const sStr = String(s).padStart(2, "0");
const timeString = `${m}:${sStr}`;
if (maxTime === time) {
timeNode.innerHTML = `<font color=red>${timeString}</font>`;
} else {
timeNode.textContent = timeString;
}
}*/
const answerButtons = writable<AnswerButtonInfo[]>([]);
const remaining = writable<[number, number, number]>([0, 0, 0]);
const remainingIndex = writable<number>(-1);
let intervalId: number | undefined;
function _showQuestion(_txt: string, _maxTime_: number): void {
_showAnswer([]);
globalThis.time = 0;
// maxTime = maxTime_;
// updateTime();
if (intervalId !== undefined) {
clearInterval(intervalId);
}
/*
intervalId = setInterval(function() {
if (!timerStopped) {
globalThis.time += 1;
//updateTime();
}
}, 1000);*/
}
function _showAnswer(info: AnswerButtonInfo[], _stopTimer = false): void {
console.log(info);
answerButtons.set(info);
// timerStopped = stopTimer;
}
function _updateRemaining(counts: [number, number, number], idx: number) {
remaining.set(counts);
remainingIndex.set(idx);
}
globalThis.showQuestion = _showQuestion;
globalThis.showAnswer = _showAnswer;
globalThis._updateRemaining = _updateRemaining;
/*
function selectedAnswerButton(): string | undefined {
const node = document.activeElement as HTMLElement;
if (!node) {
return;
}
return node.dataset.ease;
}
*/
// TODO This should probably be a "ready" command now that it is part of the actual reviewer,
// Currently this depends on this component mounting after the reviewer which it should but seems hacky.
// Maybe use a counter with a counter.subscribe($counter == 2 then call("ready"))
bridgeCommand("bottomReady");
return {
answerButtons,
remaining,
remainingIndex,
};
}

View file

@ -0,0 +1,9 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export interface AnswerButtonInfo {
"extra": string;
"key": string;
"i": number;
"label": string;
"due": string;
}

View file

@ -0,0 +1,16 @@
<script lang="ts">
import type { Writable } from "svelte/store";
export let html: Writable<string>;
export let cardClass: Writable<string>;
</script>
<div id="qa" class={$cardClass}>
{@html $html}
</div>
<style lang="scss">
#qa {
flex: 1;
}
</style>

View file

@ -0,0 +1,18 @@
import { writable } from "svelte/store";
import { preloadAnswerImages } from "../../reviewer/images";
export function setupReviewer() {
const html = writable("");
const cardClass = writable("");
function showQuestion(q, a, cc) {
html.set(q);
cardClass.set(cc);
preloadAnswerImages(a);
}
globalThis._showAnswer = html.set;
globalThis._showQuestion = showQuestion;
return { html, cardClass };
}

View file

@ -0,0 +1,21 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { onMount } from "svelte";
import { setupReviewer } from "./reviewer";
import Reviewer from "./reviewer.svelte";
import "../../reviewer/reviewer.scss";
let reviewerInfo: null | ReturnType<typeof setupReviewer> = null;
onMount(() => {
reviewerInfo = setupReviewer();
});
</script>
{#if reviewerInfo}
<Reviewer {...reviewerInfo}></Reviewer>
{/if}