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",
)
copy_files_into_group(
name = "reviewer_extras",
srcs = [
"reviewer_extras.js",
],
package = "//ts/reviewer",
)
filegroup(
name = "js",
srcs = [
"aqt_es5",
"editor",
"mathjax.js",
"reviewer_extras",
"//qt/aqt/data/web/js/vendor",
],
visibility = ["//qt:__subpackages__"],

View File

@ -22,6 +22,7 @@ import aqt
from anki import hooks
from anki.collection import GraphPreferences, OpChanges
from anki.decks import UpdateDeckConfigs
from anki.scheduler.v3 import NextStates
from anki.utils import devMode, from_json_bytes
from aqt.deckoptions import DeckOptionsDialog
from aqt.operations.deck import update_deck_configs
@ -307,12 +308,29 @@ def update_deck_configs_request() -> bytes:
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 = {
"graphData": graph_data,
"graphPreferences": graph_preferences,
"setGraphPreferences": set_graph_preferences,
"deckConfigsForUpdate": deck_configs_for_update,
"updateDeckConfigs": update_deck_configs_request,
"nextCardStates": next_card_states,
"setNextCardStates": set_next_card_states,
# pylint: disable=unnecessary-lambda
"i18nResources": i18n_resources,
"congratsInfo": congrats_info,

View File

@ -6,6 +6,7 @@ from __future__ import annotations
import difflib
import html
import json
import random
import re
import unicodedata as ucd
from dataclasses import dataclass
@ -124,6 +125,7 @@ class Reviewer:
self.state: Optional[str] = None
self._refresh_needed: Optional[RefreshNeeded] = None
self._v3: Optional[V3CardInfo] = None
self._state_mutation_key = str(random.randint(0, 2 ** 64 - 1))
self.bottom = BottomBar(mw, mw.bottomWeb)
hooks.card_did_leech.append(self.onLeech)
@ -131,6 +133,7 @@ class Reviewer:
self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore
self.web.set_bridge_command(self._linkHandler, 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._refresh_needed = RefreshNeeded.QUEUES
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.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
##########################################################################
@ -268,6 +290,8 @@ class Reviewer:
"js/mathjax.js",
"js/vendor/mathjax/tex-chtml.js",
"js/reviewer.js",
"js/vendor/protobuf.min.js",
"js/reviewer_extras.js",
],
context=self,
)
@ -308,6 +332,7 @@ class Reviewer:
# render & update bottom
q = self._mungeQA(q)
q = gui_hooks.card_will_show(q, c, "reviewQuestion")
self._run_state_mutation_hook()
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)

View File

@ -3,9 +3,9 @@
export async function postRequest(
path: string,
body: string | Uint8Array
body: string | Uint8Array,
headers: Record<string, string> = {}
): Promise<Uint8Array> {
const headers = {};
if (body instanceof Uint8Array) {
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 };