mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Merge 1e67a773c6
into 4506ad0c97
This commit is contained in:
commit
3307fdaf11
15 changed files with 372 additions and 5 deletions
|
@ -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
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -334,6 +334,7 @@ def is_sveltekit_page(path: str) -> bool:
|
||||||
"import-csv",
|
"import-csv",
|
||||||
"import-page",
|
"import-page",
|
||||||
"image-occlusion",
|
"image-occlusion",
|
||||||
|
"reviewer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
17
ts/routes/reviewer/+page.svelte
Normal file
17
ts/routes/reviewer/+page.svelte
Normal 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>
|
0
ts/routes/reviewer/index.ts
Normal file
0
ts/routes/reviewer/index.ts
Normal file
14
ts/routes/reviewer/reviewer-bottom/AnswerButton.svelte
Normal file
14
ts/routes/reviewer/reviewer-bottom/AnswerButton.svelte
Normal 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>
|
16
ts/routes/reviewer/reviewer-bottom/RemainingNumber.svelte
Normal file
16
ts/routes/reviewer/reviewer-bottom/RemainingNumber.svelte
Normal 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>
|
79
ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte
Normal file
79
ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte
Normal 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()}↧
|
||||||
|
</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>
|
|
@ -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}
|
|
@ -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"] {
|
90
ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts
Normal file
90
ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
9
ts/routes/reviewer/reviewer-bottom/types.ts
Normal file
9
ts/routes/reviewer/reviewer-bottom/types.ts
Normal 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;
|
||||||
|
}
|
16
ts/routes/reviewer/reviewer.svelte
Normal file
16
ts/routes/reviewer/reviewer.svelte
Normal 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>
|
18
ts/routes/reviewer/reviewer.ts
Normal file
18
ts/routes/reviewer/reviewer.ts
Normal 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 };
|
||||||
|
}
|
21
ts/routes/reviewer/reviewerOuter.svelte
Normal file
21
ts/routes/reviewer/reviewerOuter.svelte
Normal 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}
|
Loading…
Reference in a new issue