mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
Added: NextCardData
This commit is contained in:
parent
b6e07f5780
commit
aa42d87558
4 changed files with 66 additions and 12 deletions
|
|
@ -17,6 +17,7 @@ import "anki/deck_config.proto";
|
||||||
service SchedulerService {
|
service SchedulerService {
|
||||||
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
|
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
|
||||||
rpc AnswerCard(CardAnswer) returns (collection.OpChanges);
|
rpc AnswerCard(CardAnswer) returns (collection.OpChanges);
|
||||||
|
rpc NextCardData(NextCardDataRequest) returns (NextCardDataResponse);
|
||||||
rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse);
|
rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse);
|
||||||
rpc StudiedToday(generic.Empty) returns (generic.String);
|
rpc StudiedToday(generic.Empty) returns (generic.String);
|
||||||
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
|
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
|
||||||
|
|
@ -285,6 +286,21 @@ message CardAnswer {
|
||||||
uint32 milliseconds_taken = 6;
|
uint32 milliseconds_taken = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message NextCardDataRequest {
|
||||||
|
optional CardAnswer answer = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NextCardDataResponse {
|
||||||
|
message NextCardData {
|
||||||
|
string front = 1;
|
||||||
|
string back = 2;
|
||||||
|
|
||||||
|
SchedulingStates states = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional NextCardData next_card = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message CustomStudyRequest {
|
message CustomStudyRequest {
|
||||||
message Cram {
|
message Cram {
|
||||||
enum CramKind {
|
enum CramKind {
|
||||||
|
|
|
||||||
|
|
@ -696,6 +696,7 @@ exposed_backend_list = [
|
||||||
"get_optimal_retention_parameters",
|
"get_optimal_retention_parameters",
|
||||||
"simulate_fsrs_review",
|
"simulate_fsrs_review",
|
||||||
"simulate_fsrs_workload",
|
"simulate_fsrs_workload",
|
||||||
|
"next_card_data",
|
||||||
# DeckConfigService
|
# DeckConfigService
|
||||||
"get_ignored_before_count",
|
"get_ignored_before_count",
|
||||||
"get_retention_workload",
|
"get_retention_workload",
|
||||||
|
|
@ -719,7 +720,6 @@ post_handlers = {
|
||||||
for handler in exposed_backend_list
|
for handler in exposed_backend_list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _extract_collection_post_request(path: str) -> DynamicRequest | NotFound:
|
def _extract_collection_post_request(path: str) -> DynamicRequest | NotFound:
|
||||||
if not aqt.mw.col:
|
if not aqt.mw.col:
|
||||||
return NotFound(message=f"collection not open, ignore request for {path}")
|
return NotFound(message=f"collection not open, ignore request for {path}")
|
||||||
|
|
@ -766,6 +766,8 @@ def _check_dynamic_request_permissions():
|
||||||
"/_anki/setSchedulingStates",
|
"/_anki/setSchedulingStates",
|
||||||
"/_anki/i18nResources",
|
"/_anki/i18nResources",
|
||||||
"/_anki/congratsInfo",
|
"/_anki/congratsInfo",
|
||||||
|
# TODO: Unsure about this
|
||||||
|
"/_anki/nextCardData"
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ mod states;
|
||||||
use anki_proto::cards;
|
use anki_proto::cards;
|
||||||
use anki_proto::generic;
|
use anki_proto::generic;
|
||||||
use anki_proto::scheduler;
|
use anki_proto::scheduler;
|
||||||
|
use anki_proto::scheduler::next_card_data_response::NextCardData;
|
||||||
use anki_proto::scheduler::ComputeFsrsParamsResponse;
|
use anki_proto::scheduler::ComputeFsrsParamsResponse;
|
||||||
use anki_proto::scheduler::ComputeMemoryStateResponse;
|
use anki_proto::scheduler::ComputeMemoryStateResponse;
|
||||||
use anki_proto::scheduler::ComputeOptimalRetentionResponse;
|
use anki_proto::scheduler::ComputeOptimalRetentionResponse;
|
||||||
|
|
@ -14,6 +15,8 @@ use anki_proto::scheduler::FsrsBenchmarkResponse;
|
||||||
use anki_proto::scheduler::FuzzDeltaRequest;
|
use anki_proto::scheduler::FuzzDeltaRequest;
|
||||||
use anki_proto::scheduler::FuzzDeltaResponse;
|
use anki_proto::scheduler::FuzzDeltaResponse;
|
||||||
use anki_proto::scheduler::GetOptimalRetentionParametersResponse;
|
use anki_proto::scheduler::GetOptimalRetentionParametersResponse;
|
||||||
|
use anki_proto::scheduler::NextCardDataRequest;
|
||||||
|
use anki_proto::scheduler::NextCardDataResponse;
|
||||||
use anki_proto::scheduler::SimulateFsrsReviewRequest;
|
use anki_proto::scheduler::SimulateFsrsReviewRequest;
|
||||||
use anki_proto::scheduler::SimulateFsrsReviewResponse;
|
use anki_proto::scheduler::SimulateFsrsReviewResponse;
|
||||||
use anki_proto::scheduler::SimulateFsrsWorkloadResponse;
|
use anki_proto::scheduler::SimulateFsrsWorkloadResponse;
|
||||||
|
|
@ -382,6 +385,30 @@ impl crate::services::SchedulerService for Collection {
|
||||||
delta_days: self.get_fuzz_delta(input.card_id.into(), input.interval)?,
|
delta_days: self.get_fuzz_delta(input.card_id.into(), input.interval)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_card_data(&mut self, req: NextCardDataRequest) -> Result<NextCardDataResponse> {
|
||||||
|
if let Some(answer) = req.answer {
|
||||||
|
self.answer_card(&mut answer.into())?;
|
||||||
|
}
|
||||||
|
let queue = self.get_queued_cards(1, false)?;
|
||||||
|
let next_card = queue.cards.first();
|
||||||
|
if let Some(next_card) = next_card {
|
||||||
|
let cid = next_card.card.id;
|
||||||
|
|
||||||
|
let render = self.render_existing_card(cid, false, false)?;
|
||||||
|
|
||||||
|
Ok(NextCardDataResponse {
|
||||||
|
next_card: Some(NextCardData {
|
||||||
|
front: render.question().to_string(),
|
||||||
|
back: render.answer().to_string(),
|
||||||
|
|
||||||
|
states: Some(next_card.states.clone().into()),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(NextCardDataResponse::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::services::BackendSchedulerService for Backend {
|
impl crate::services::BackendSchedulerService for Backend {
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,38 @@
|
||||||
// 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
|
||||||
|
import { CardAnswer, SchedulingStates } from "@generated/anki/scheduler_pb";
|
||||||
|
import { nextCardData } from "@generated/backend";
|
||||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { preloadAnswerImages } from "../../reviewer/images";
|
|
||||||
|
|
||||||
export function setupReviewer(iframe: HTMLIFrameElement) {
|
export function setupReviewer(iframe: HTMLIFrameElement) {
|
||||||
const cardClass = writable("");
|
const cardClass = writable("");
|
||||||
|
let answer_html = "";
|
||||||
|
let states: SchedulingStates | undefined;
|
||||||
|
|
||||||
function updateHtml(htmlString) {
|
function updateHtml(htmlString) {
|
||||||
iframe.contentWindow?.postMessage({ type: "html", value: htmlString }, "*");
|
iframe.contentWindow?.postMessage({ type: "html", value: htmlString }, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showQuestion(q, a, cc) {
|
async function showQuestion(answer: CardAnswer | null) {
|
||||||
updateHtml(q);
|
let resp = await nextCardData({
|
||||||
// html.set(q);
|
answer: answer || undefined,
|
||||||
cardClass.set(cc);
|
});
|
||||||
preloadAnswerImages(a);
|
// TODO: "Congratulation screen" logic
|
||||||
|
const question = resp.nextCard?.front || "";
|
||||||
|
answer_html = resp.nextCard?.back || "";
|
||||||
|
states = resp.nextCard?.states;
|
||||||
|
console.log({ resp });
|
||||||
|
updateHtml(question);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAnswer() {
|
||||||
|
updateHtml(answer_html);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReady() {
|
function onReady() {
|
||||||
// TODO This should probably be a "ready" command now that it is part of the actual reviewer,
|
|
||||||
// Currently this depends on the reviewer component mounting after the bottom-reviewer which it should but seems hacky.
|
|
||||||
// Maybe use a counter with a counter.subscribe($counter == 2 then call("ready"))
|
|
||||||
bridgeCommand("bottomReady");
|
|
||||||
iframe.contentWindow?.postMessage({ type: "nightMode", value: true }, "*");
|
iframe.contentWindow?.postMessage({ type: "nightMode", value: true }, "*");
|
||||||
|
showQuestion(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe?.addEventListener("load", onReady);
|
iframe?.addEventListener("load", onReady);
|
||||||
|
|
@ -46,8 +55,8 @@ export function setupReviewer(iframe: HTMLIFrameElement) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis._showAnswer = updateHtml;
|
|
||||||
globalThis._showQuestion = showQuestion;
|
globalThis._showQuestion = showQuestion;
|
||||||
|
globalThis._showAnswer = showAnswer;
|
||||||
|
|
||||||
return { cardClass };
|
return { cardClass };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue