mirror of
https://github.com/ankitects/anki.git
synced 2025-11-07 05:07:10 -05:00
Added: SchedulingState class
This commit is contained in:
parent
44cc5f82e9
commit
cadf59f8d6
6 changed files with 48 additions and 168 deletions
|
|
@ -3,13 +3,16 @@ 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>
|
<script>
|
||||||
import ReviewerBottomOuter from "./reviewer-bottom/ReviewerBottomOuter.svelte";
|
import { ReviewerState } from "./reviewer";
|
||||||
|
import ReviewerBottom from "./reviewer-bottom/ReviewerBottom.svelte";
|
||||||
import Reviewer from "./Reviewer.svelte";
|
import Reviewer from "./Reviewer.svelte";
|
||||||
|
|
||||||
|
let state = new ReviewerState
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Reviewer></Reviewer>
|
<Reviewer {state}></Reviewer>
|
||||||
<ReviewerBottomOuter></ReviewerBottomOuter>
|
<ReviewerBottom {state}></ReviewerBottom>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,17 @@ 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 { setupReviewer } from "./reviewer";
|
import type { ReviewerState } from "./reviewer";
|
||||||
|
|
||||||
|
|
||||||
let iframe: HTMLIFrameElement;
|
let iframe: HTMLIFrameElement;
|
||||||
|
export let state: ReviewerState
|
||||||
|
|
||||||
|
$: if (iframe) state.registerIFrame(iframe)
|
||||||
|
|
||||||
$: ({ cardClass } = setupReviewer(iframe));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="qa" class={$cardClass}>
|
<div id="qa">
|
||||||
<iframe
|
<iframe
|
||||||
src="/_anki/pages/reviewer-inner.html"
|
src="/_anki/pages/reviewer-inner.html"
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,20 @@ 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 type { Writable } from "svelte/store";
|
|
||||||
import AnswerButton from "./AnswerButton.svelte";
|
import AnswerButton from "./AnswerButton.svelte";
|
||||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import RemainingNumber from "./RemainingNumber.svelte";
|
import RemainingNumber from "./RemainingNumber.svelte";
|
||||||
import type { AnswerButtonInfo } from "./types";
|
import type { ReviewerState } from "../reviewer";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
export let answerButtons: Writable<AnswerButtonInfo[]>;
|
export let state: ReviewerState
|
||||||
export let remaining: Writable<number[]>;
|
|
||||||
export let remainingIndex: Writable<number>;
|
// Placeholders
|
||||||
|
let answerButtons = writable([]);
|
||||||
|
let remaining = writable([0, 0, 0]);
|
||||||
|
let remainingIndex = writable(0);
|
||||||
|
|
||||||
$: console.log($remaining);
|
|
||||||
$: answerShown = $answerButtons.length;
|
$: answerShown = $answerButtons.length;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<!--
|
|
||||||
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,86 +0,0 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return {
|
|
||||||
answerButtons,
|
|
||||||
remaining,
|
|
||||||
remainingIndex,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -5,42 +5,45 @@ import {
|
||||||
type NextCardDataResponse_NextCardData,
|
type NextCardDataResponse_NextCardData,
|
||||||
} from "@generated/anki/scheduler_pb";
|
} from "@generated/anki/scheduler_pb";
|
||||||
import { nextCardData } from "@generated/backend";
|
import { nextCardData } from "@generated/backend";
|
||||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
export function setupReviewer(iframe: HTMLIFrameElement) {
|
export class ReviewerState {
|
||||||
const cardClass = writable("");
|
answerHtml: string = ""
|
||||||
let answer_html = "";
|
cardData: NextCardDataResponse_NextCardData | undefined = undefined;
|
||||||
let cardData: NextCardDataResponse_NextCardData | undefined = undefined;
|
beginAnsweringMs = Date.now();
|
||||||
let startAnswering = Date.now();
|
readonly cardClass = writable("");
|
||||||
|
|
||||||
function updateHtml(htmlString) {
|
iframe: HTMLIFrameElement | undefined = undefined;
|
||||||
iframe.contentWindow?.postMessage({ type: "html", value: htmlString }, "*");
|
|
||||||
|
onReady() {
|
||||||
|
this.iframe?.contentWindow?.postMessage({ type: "nightMode", value: true }, "*");
|
||||||
|
this.showQuestion(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showQuestion(answer: CardAnswer | null) {
|
public registerIFrame(iframe: HTMLIFrameElement) {
|
||||||
|
this.iframe = iframe;
|
||||||
|
iframe.addEventListener("load", this.onReady.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHtml(htmlString: string) {
|
||||||
|
this.iframe?.contentWindow?.postMessage({ type: "html", value: htmlString }, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
async showQuestion(answer: CardAnswer | null) {
|
||||||
const resp = await nextCardData({
|
const resp = await nextCardData({
|
||||||
answer: answer || undefined,
|
answer: answer || undefined,
|
||||||
});
|
});
|
||||||
// TODO: "Congratulation screen" logic
|
// TODO: "Congratulation screen" logic
|
||||||
const question = resp.nextCard?.front || "";
|
const question = resp.nextCard?.front || "";
|
||||||
answer_html = resp.nextCard?.back || "";
|
this.updateHtml(question);
|
||||||
cardData = resp.nextCard;
|
|
||||||
console.log({ resp });
|
|
||||||
updateHtml(question);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAnswer() {
|
public showAnswer() {
|
||||||
updateHtml(answer_html);
|
this.updateHtml(this.cardData?.back || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReady() {
|
public easeButtonPressed(rating: number) {
|
||||||
iframe.contentWindow?.postMessage({ type: "nightMode", value: true }, "*");
|
const states = this.cardData!.states!;
|
||||||
showQuestion(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function easeButtonPressed(rating: number) {
|
|
||||||
const states = cardData!.states!;
|
|
||||||
|
|
||||||
let newState = ({
|
let newState = ({
|
||||||
[1]: states.again!,
|
[1]: states.again!,
|
||||||
|
|
@ -49,40 +52,15 @@ export function setupReviewer(iframe: HTMLIFrameElement) {
|
||||||
[4]: states.easy!,
|
[4]: states.easy!,
|
||||||
})[rating]!;
|
})[rating]!;
|
||||||
|
|
||||||
showQuestion(
|
this.showQuestion(
|
||||||
new CardAnswer({
|
new CardAnswer({
|
||||||
rating: rating,
|
rating: rating,
|
||||||
currentState: states!.current!,
|
currentState: states!.current!,
|
||||||
newState,
|
newState,
|
||||||
cardId: cardData!.cardId,
|
cardId: this.cardData!.cardId,
|
||||||
answeredAtMillis: BigInt(Date.now()),
|
answeredAtMillis: BigInt(Date.now()),
|
||||||
millisecondsTaken: Date.now() - startAnswering,
|
millisecondsTaken: Date.now() - this.beginAnsweringMs,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe?.addEventListener("load", onReady);
|
|
||||||
|
|
||||||
addEventListener("message", (e) => {
|
|
||||||
switch (e.data.type) {
|
|
||||||
case "pycmd": {
|
|
||||||
const cmd = e.data.value as string;
|
|
||||||
if (cmd.startsWith("play:")) {
|
|
||||||
bridgeCommand(e.data.value);
|
|
||||||
} else {
|
|
||||||
console.error("pycmd command is either invalid or forbidden:", cmd);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
console.warn(`Unknown message type: ${e.data.type}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
globalThis._showQuestion = showQuestion;
|
|
||||||
globalThis._showAnswer = showAnswer;
|
|
||||||
|
|
||||||
return { cardClass, easeButtonPressed };
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue