support card state mutator in test scheduler

Documentation to come
This commit is contained in:
Damien Elmes 2021-05-17 16:59:02 +10:00
parent 1f16ce2096
commit 9edac805ad
7 changed files with 145 additions and 2 deletions

View file

@ -37,12 +37,21 @@ copy_files_into_group(
package = "//ts/editor", package = "//ts/editor",
) )
copy_files_into_group(
name = "reviewer_extras",
srcs = [
"reviewer_extras.js",
],
package = "//ts/reviewer",
)
filegroup( filegroup(
name = "js", name = "js",
srcs = [ srcs = [
"aqt_es5", "aqt_es5",
"editor", "editor",
"mathjax.js", "mathjax.js",
"reviewer_extras",
"//qt/aqt/data/web/js/vendor", "//qt/aqt/data/web/js/vendor",
], ],
visibility = ["//qt:__subpackages__"], visibility = ["//qt:__subpackages__"],

View file

@ -22,6 +22,7 @@ import aqt
from anki import hooks from anki import hooks
from anki.collection import GraphPreferences, OpChanges from anki.collection import GraphPreferences, OpChanges
from anki.decks import UpdateDeckConfigs from anki.decks import UpdateDeckConfigs
from anki.scheduler.v3 import NextStates
from anki.utils import devMode, from_json_bytes from anki.utils import devMode, from_json_bytes
from aqt.deckoptions import DeckOptionsDialog from aqt.deckoptions import DeckOptionsDialog
from aqt.operations.deck import update_deck_configs from aqt.operations.deck import update_deck_configs
@ -307,12 +308,29 @@ def update_deck_configs_request() -> bytes:
return b"" return b""
def next_card_states() -> bytes:
if states := aqt.mw.reviewer.get_next_states():
return states.SerializeToString()
else:
return b""
def set_next_card_states() -> bytes:
key = request.headers.get("key", "")
input = NextStates()
input.ParseFromString(request.data)
aqt.mw.reviewer.set_next_states(key, input)
return b""
post_handlers = { post_handlers = {
"graphData": graph_data, "graphData": graph_data,
"graphPreferences": graph_preferences, "graphPreferences": graph_preferences,
"setGraphPreferences": set_graph_preferences, "setGraphPreferences": set_graph_preferences,
"deckConfigsForUpdate": deck_configs_for_update, "deckConfigsForUpdate": deck_configs_for_update,
"updateDeckConfigs": update_deck_configs_request, "updateDeckConfigs": update_deck_configs_request,
"nextCardStates": next_card_states,
"setNextCardStates": set_next_card_states,
# pylint: disable=unnecessary-lambda # pylint: disable=unnecessary-lambda
"i18nResources": i18n_resources, "i18nResources": i18n_resources,
"congratsInfo": congrats_info, "congratsInfo": congrats_info,

View file

@ -6,6 +6,7 @@ from __future__ import annotations
import difflib import difflib
import html import html
import json import json
import random
import re import re
import unicodedata as ucd import unicodedata as ucd
from dataclasses import dataclass from dataclasses import dataclass
@ -124,6 +125,7 @@ class Reviewer:
self.state: Optional[str] = None self.state: Optional[str] = None
self._refresh_needed: Optional[RefreshNeeded] = None self._refresh_needed: Optional[RefreshNeeded] = None
self._v3: Optional[V3CardInfo] = None self._v3: Optional[V3CardInfo] = None
self._state_mutation_key = str(random.randint(0, 2 ** 64 - 1))
self.bottom = BottomBar(mw, mw.bottomWeb) self.bottom = BottomBar(mw, mw.bottomWeb)
hooks.card_did_leech.append(self.onLeech) hooks.card_did_leech.append(self.onLeech)
@ -131,6 +133,7 @@ class Reviewer:
self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore
self.web.set_bridge_command(self._linkHandler, self) self.web.set_bridge_command(self._linkHandler, self)
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self)) self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
self._state_mutation_js = self.mw.col.get_config("cardStateCustomizer")
self._reps: int = None self._reps: int = None
self._refresh_needed = RefreshNeeded.QUEUES self._refresh_needed = RefreshNeeded.QUEUES
self.refresh_if_needed() self.refresh_if_needed()
@ -231,6 +234,25 @@ class Reviewer:
self.card = Card(self.mw.col, backend_card=self._v3.top_card().card) self.card = Card(self.mw.col, backend_card=self._v3.top_card().card)
self.card.startTimer() self.card.startTimer()
def get_next_states(self) -> Optional[NextStates]:
if v3 := self._v3:
return v3.next_states
else:
return None
def set_next_states(self, key: str, states: NextStates) -> None:
if key != self._state_mutation_key:
return
if v3 := self._v3:
v3.next_states = states
def _run_state_mutation_hook(self) -> None:
if self._v3 and (js := self._state_mutation_js):
self.web.eval(
f"anki.mutateNextCardStates('{self._state_mutation_key}', (states) => {{ {js} }})"
)
# Audio # Audio
########################################################################## ##########################################################################
@ -268,6 +290,8 @@ class Reviewer:
"js/mathjax.js", "js/mathjax.js",
"js/vendor/mathjax/tex-chtml.js", "js/vendor/mathjax/tex-chtml.js",
"js/reviewer.js", "js/reviewer.js",
"js/vendor/protobuf.min.js",
"js/reviewer_extras.js",
], ],
context=self, context=self,
) )
@ -308,6 +332,7 @@ class Reviewer:
# render & update bottom # render & update bottom
q = self._mungeQA(q) q = self._mungeQA(q)
q = gui_hooks.card_will_show(q, c, "reviewQuestion") q = gui_hooks.card_will_show(q, c, "reviewQuestion")
self._run_state_mutation_hook()
bodyclass = theme_manager.body_classes_for_card_ord(c.ord) bodyclass = theme_manager.body_classes_for_card_ord(c.ord)

View file

@ -3,9 +3,9 @@
export async function postRequest( export async function postRequest(
path: string, path: string,
body: string | Uint8Array body: string | Uint8Array,
headers: Record<string, string> = {}
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const headers = {};
if (body instanceof Uint8Array) { if (body instanceof Uint8Array) {
headers["Content-type"] = "application/octet-stream"; headers["Content-type"] = "application/octet-stream";
} }

54
ts/reviewer/BUILD.bazel Normal file
View file

@ -0,0 +1,54 @@
load("@npm//@bazel/typescript:index.bzl", "ts_library")
load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:compile_sass.bzl", "compile_sass")
ts_library(
name = "lib",
srcs = glob(["*.ts"]),
deps = [
"//ts/lib",
"//ts/lib:backend_proto",
],
)
esbuild(
name = "reviewer_extras",
srcs = [
"//ts:protobuf-shim.js",
],
args = [
"--inject:$(location //ts:protobuf-shim.js)",
"--resolve-extensions=.mjs,.js",
"--log-level=warning",
],
entry_point = "index.ts",
external = [
"protobufjs/light",
],
visibility = ["//visibility:public"],
deps = [
":lib",
"//ts/lib",
"//ts/lib:backend_proto",
],
)
# Tests
################
prettier_test(
name = "format_check",
srcs = glob([
"*.ts",
]),
)
eslint_test(
name = "eslint",
srcs = glob([
"*.ts",
]),
)

28
ts/reviewer/answering.ts Normal file
View file

@ -0,0 +1,28 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as pb from "lib/backend_proto";
import { postRequest } from "lib/postrequest";
async function getNextStates(): Promise<pb.BackendProto.NextCardStates> {
return pb.BackendProto.NextCardStates.decode(
await postRequest("/_anki/nextCardStates", "")
);
}
async function setNextStates(
key: string,
states: pb.BackendProto.NextCardStates
): Promise<void> {
const data: Uint8Array = pb.BackendProto.NextCardStates.encode(states).finish();
await postRequest("/_anki/setNextCardStates", data, { key });
}
export async function mutateNextCardStates(
key: string,
mutator: (states: pb.BackendProto.NextCardStates) => void
): Promise<void> {
const states = await getNextStates();
mutator(states);
await setNextStates(key, states);
}

9
ts/reviewer/index.ts Normal file
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
// This is a temporary extra file we load separately from reviewer.ts. Once
// reviewer.ts has been migrated into ts/, the code here can be merged into
// it.
import { mutateNextCardStates } from "./answering";
globalThis.anki = { ...globalThis.anki, mutateNextCardStates };