split out remaining tags, stats, media and rendering

This commit is contained in:
Damien Elmes 2021-07-10 21:58:34 +10:00
parent 35b059ecdb
commit 185e9acd22
44 changed files with 413 additions and 382 deletions

View file

@ -3,7 +3,7 @@ load("@bazel_skylib//lib:versions.bzl", "versions")
load("@rules_rust//rust:repositories.bzl", "rust_repositories") load("@rules_rust//rust:repositories.bzl", "rust_repositories")
load("@anki//cargo:crates.bzl", "raze_fetch_remote_crates") load("@anki//cargo:crates.bzl", "raze_fetch_remote_crates")
load(":python.bzl", "setup_local_python") load(":python.bzl", "setup_local_python")
load(":protobuf.bzl", "setup_protobuf_binary") load("//proto:protobuf.bzl", "setup_protobuf_binary")
load("//proto:format.bzl", "setup_clang_format") load("//proto:format.bzl", "setup_clang_format")
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install") load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install")
load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories") load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories")

View file

@ -5,15 +5,6 @@ syntax = "proto3";
package anki.backend; package anki.backend;
import "anki/generic.proto";
import "anki/cards.proto";
import "anki/collection.proto";
import "anki/notes.proto";
import "anki/notetypes.proto";
// Backend methods
///////////////////////////////////////////////////////////
/// while the protobuf descriptors expose the order services are defined in, /// while the protobuf descriptors expose the order services are defined in,
/// that information is not available in prost, so we define an enum to make /// that information is not available in prost, so we define an enum to make
/// sure all clients agree on the service index /// sure all clients agree on the service index
@ -35,54 +26,6 @@ enum ServiceIndex {
SERVICE_INDEX_CARDS = 14; SERVICE_INDEX_CARDS = 14;
} }
service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
rpc GetEmptyCards(generic.Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCard(RenderUncommittedCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest)
returns (RenderCardResponse);
rpc StripAVTags(generic.String) returns (generic.String);
rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String);
}
service TagsService {
rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount);
rpc AllTags(generic.Empty) returns (generic.StringList);
rpc RemoveTags(generic.String) returns (collection.OpChangesWithCount);
rpc SetTagCollapsed(SetTagCollapsedRequest) returns (collection.OpChanges);
rpc TagTree(generic.Empty) returns (TagTreeNode);
rpc ReparentTags(ReparentTagsRequest) returns (collection.OpChangesWithCount);
rpc RenameTags(RenameTagsRequest) returns (collection.OpChangesWithCount);
rpc AddNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc RemoveNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc FindAndReplaceTag(FindAndReplaceTagRequest)
returns (collection.OpChangesWithCount);
}
service StatsService {
rpc CardStats(cards.CardId) returns (generic.String);
rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
}
service MediaService {
rpc CheckMedia(generic.Empty) returns (CheckMediaResponse);
rpc TrashMediaFiles(TrashMediaFilesRequest) returns (generic.Empty);
rpc AddMediaFile(AddMediaFileRequest) returns (generic.String);
rpc EmptyTrash(generic.Empty) returns (generic.Empty);
rpc RestoreTrash(generic.Empty) returns (generic.Empty);
}
// Backend
///////////////////////////////////////////////////////////
message BackendInit { message BackendInit {
repeated string preferred_langs = 1; repeated string preferred_langs = 1;
string locale_folder_path = 2; string locale_folder_path = 2;
@ -94,9 +37,6 @@ message I18nBackendInit {
string locale_folder_path = 5; string locale_folder_path = 5;
} }
// Errors
///////////////////////////////////////////////////////////
message BackendError { message BackendError {
enum Kind { enum Kind {
INVALID_INPUT = 0; INVALID_INPUT = 0;
@ -121,208 +61,3 @@ message BackendError {
// the error subtype // the error subtype
Kind kind = 2; Kind kind = 2;
} }
// Messages
///////////////////////////////////////////////////////////
message RenderExistingCardRequest {
int64 card_id = 1;
bool browser = 2;
}
message RenderUncommittedCardRequest {
notes.Note note = 1;
uint32 card_ord = 2;
notetypes.Notetype.Template template = 3;
bool fill_empty = 4;
}
message RenderUncommittedCardLegacyRequest {
notes.Note note = 1;
uint32 card_ord = 2;
bytes template = 3;
bool fill_empty = 4;
}
message RenderCardResponse {
repeated RenderedTemplateNode question_nodes = 1;
repeated RenderedTemplateNode answer_nodes = 2;
string css = 3;
bool latex_svg = 4;
}
message RenderedTemplateNode {
oneof value {
string text = 1;
RenderedTemplateReplacement replacement = 2;
}
}
message RenderedTemplateReplacement {
string field_name = 1;
string current_text = 2;
repeated string filters = 3;
}
message ExtractAVTagsRequest {
string text = 1;
bool question_side = 2;
}
message ExtractAVTagsResponse {
string text = 1;
repeated AVTag av_tags = 2;
}
message AVTag {
oneof value {
string sound_or_video = 1;
TTSTag tts = 2;
}
}
message TTSTag {
string field_text = 1;
string lang = 2;
repeated string voices = 3;
float speed = 4;
repeated string other_args = 5;
}
message ExtractLatexRequest {
string text = 1;
bool svg = 2;
bool expand_clozes = 3;
}
message ExtractLatexResponse {
string text = 1;
repeated ExtractedLatex latex = 2;
}
message ExtractedLatex {
string filename = 1;
string latex_body = 2;
}
message AddMediaFileRequest {
string desired_name = 1;
bytes data = 2;
}
message CheckMediaResponse {
repeated string unused = 1;
repeated string missing = 2;
string report = 3;
bool have_trash = 4;
}
message TrashMediaFilesRequest {
repeated string fnames = 1;
}
message CongratsLearnMessageRequest {
float next_due = 1;
uint32 remaining = 2;
}
message SetTagCollapsedRequest {
string name = 1;
bool collapsed = 2;
}
message GetChangedTagsResponse {
repeated string tags = 1;
}
message TagTreeNode {
string name = 1;
repeated TagTreeNode children = 2;
uint32 level = 3;
bool collapsed = 4;
}
message ReparentTagsRequest {
repeated string tags = 1;
string new_parent = 2;
}
message RenameTagsRequest {
string current_prefix = 1;
string new_prefix = 2;
}
message EmptyCardsReport {
message NoteWithEmptyCards {
int64 note_id = 1;
repeated int64 card_ids = 2;
bool will_delete_note = 3;
}
string report = 1;
repeated NoteWithEmptyCards notes = 2;
}
message NoteIdsAndTagsRequest {
repeated int64 note_ids = 1;
string tags = 2;
}
message FindAndReplaceTagRequest {
repeated int64 note_ids = 1;
string search = 2;
string replacement = 3;
bool regex = 4;
bool match_case = 5;
}
message GraphsRequest {
string search = 1;
uint32 days = 2;
}
message GraphsResponse {
repeated cards.Card cards = 1;
repeated RevlogEntry revlog = 2;
uint32 days_elapsed = 3;
// Based on rollover hour
uint32 next_day_at_secs = 4;
uint32 scheduler_version = 5;
/// Seconds to add to UTC timestamps to get local time.
int32 local_offset_secs = 7;
}
message GraphPreferences {
enum Weekday {
SUNDAY = 0;
MONDAY = 1;
FRIDAY = 5;
SATURDAY = 6;
}
Weekday calendar_first_day_of_week = 1;
bool card_counts_separate_inactive = 2;
bool browser_links_supported = 3;
bool future_due_show_backlog = 4;
}
message RevlogEntry {
enum ReviewKind {
LEARNING = 0;
REVIEW = 1;
RELEARNING = 2;
EARLY_REVIEW = 3;
MANUAL = 4;
}
int64 id = 1;
int64 cid = 2;
int32 usn = 3;
uint32 button_chosen = 4;
int32 interval = 5;
int32 last_interval = 6;
uint32 ease_factor = 7;
uint32 taken_millis = 8;
ReviewKind review_kind = 9;
}
message RenderMarkdownRequest {
string markdown = 1;
bool sanitize = 2;
}

View file

@ -0,0 +1,119 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.card_rendering;
import "anki/generic.proto";
import "anki/notes.proto";
import "anki/notetypes.proto";
service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
rpc GetEmptyCards(generic.Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCard(RenderUncommittedCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest)
returns (RenderCardResponse);
rpc StripAVTags(generic.String) returns (generic.String);
rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String);
}
message ExtractAVTagsRequest {
string text = 1;
bool question_side = 2;
}
message ExtractAVTagsResponse {
string text = 1;
repeated AVTag av_tags = 2;
}
message AVTag {
oneof value {
string sound_or_video = 1;
TTSTag tts = 2;
}
}
message TTSTag {
string field_text = 1;
string lang = 2;
repeated string voices = 3;
float speed = 4;
repeated string other_args = 5;
}
message ExtractLatexRequest {
string text = 1;
bool svg = 2;
bool expand_clozes = 3;
}
message ExtractLatexResponse {
string text = 1;
repeated ExtractedLatex latex = 2;
}
message ExtractedLatex {
string filename = 1;
string latex_body = 2;
}
message EmptyCardsReport {
message NoteWithEmptyCards {
int64 note_id = 1;
repeated int64 card_ids = 2;
bool will_delete_note = 3;
}
string report = 1;
repeated NoteWithEmptyCards notes = 2;
}
message RenderExistingCardRequest {
int64 card_id = 1;
bool browser = 2;
}
message RenderUncommittedCardRequest {
notes.Note note = 1;
uint32 card_ord = 2;
notetypes.Notetype.Template template = 3;
bool fill_empty = 4;
}
message RenderUncommittedCardLegacyRequest {
notes.Note note = 1;
uint32 card_ord = 2;
bytes template = 3;
bool fill_empty = 4;
}
message RenderCardResponse {
repeated RenderedTemplateNode question_nodes = 1;
repeated RenderedTemplateNode answer_nodes = 2;
string css = 3;
bool latex_svg = 4;
}
message RenderedTemplateNode {
oneof value {
string text = 1;
RenderedTemplateReplacement replacement = 2;
}
}
message RenderedTemplateReplacement {
string field_name = 1;
string current_text = 2;
repeated string filters = 3;
}
message RenderMarkdownRequest {
string markdown = 1;
bool sanitize = 2;
}

32
proto/anki/media.proto Normal file
View file

@ -0,0 +1,32 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.media;
import "anki/generic.proto";
service MediaService {
rpc CheckMedia(generic.Empty) returns (CheckMediaResponse);
rpc TrashMediaFiles(TrashMediaFilesRequest) returns (generic.Empty);
rpc AddMediaFile(AddMediaFileRequest) returns (generic.String);
rpc EmptyTrash(generic.Empty) returns (generic.Empty);
rpc RestoreTrash(generic.Empty) returns (generic.Empty);
}
message CheckMediaResponse {
repeated string unused = 1;
repeated string missing = 2;
string report = 3;
bool have_trash = 4;
}
message TrashMediaFilesRequest {
repeated string fnames = 1;
}
message AddMediaFileRequest {
string desired_name = 1;
bytes data = 2;
}

64
proto/anki/stats.proto Normal file
View file

@ -0,0 +1,64 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.stats;
import "anki/generic.proto";
import "anki/cards.proto";
service StatsService {
rpc CardStats(cards.CardId) returns (generic.String);
rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
}
message GraphsRequest {
string search = 1;
uint32 days = 2;
}
message GraphsResponse {
repeated cards.Card cards = 1;
repeated RevlogEntry revlog = 2;
uint32 days_elapsed = 3;
// Based on rollover hour
uint32 next_day_at_secs = 4;
uint32 scheduler_version = 5;
/// Seconds to add to UTC timestamps to get local time.
int32 local_offset_secs = 7;
}
message GraphPreferences {
enum Weekday {
SUNDAY = 0;
MONDAY = 1;
FRIDAY = 5;
SATURDAY = 6;
}
Weekday calendar_first_day_of_week = 1;
bool card_counts_separate_inactive = 2;
bool browser_links_supported = 3;
bool future_due_show_backlog = 4;
}
message RevlogEntry {
enum ReviewKind {
LEARNING = 0;
REVIEW = 1;
RELEARNING = 2;
EARLY_REVIEW = 3;
MANUAL = 4;
}
int64 id = 1;
int64 cid = 2;
int32 usn = 3;
uint32 button_chosen = 4;
int32 interval = 5;
int32 last_interval = 6;
uint32 ease_factor = 7;
uint32 taken_millis = 8;
ReviewKind review_kind = 9;
}

60
proto/anki/tags.proto Normal file
View file

@ -0,0 +1,60 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.tags;
import "anki/generic.proto";
import "anki/collection.proto";
service TagsService {
rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount);
rpc AllTags(generic.Empty) returns (generic.StringList);
rpc RemoveTags(generic.String) returns (collection.OpChangesWithCount);
rpc SetTagCollapsed(SetTagCollapsedRequest) returns (collection.OpChanges);
rpc TagTree(generic.Empty) returns (TagTreeNode);
rpc ReparentTags(ReparentTagsRequest) returns (collection.OpChangesWithCount);
rpc RenameTags(RenameTagsRequest) returns (collection.OpChangesWithCount);
rpc AddNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc RemoveNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc FindAndReplaceTag(FindAndReplaceTagRequest)
returns (collection.OpChangesWithCount);
}
message SetTagCollapsedRequest {
string name = 1;
bool collapsed = 2;
}
message TagTreeNode {
string name = 1;
repeated TagTreeNode children = 2;
uint32 level = 3;
bool collapsed = 4;
}
message ReparentTagsRequest {
repeated string tags = 1;
string new_parent = 2;
}
message RenameTagsRequest {
string current_prefix = 1;
string new_prefix = 2;
}
message NoteIdsAndTagsRequest {
repeated int64 note_ids = 1;
string tags = 2;
}
message FindAndReplaceTagRequest {
repeated int64 note_ids = 1;
string search = 2;
string replacement = 3;
bool regex = 4;
bool match_case = 5;
}

View file

@ -32,7 +32,7 @@ py_library(
"py.typed", "py.typed",
":buildinfo", ":buildinfo",
":hooks_gen", ":hooks_gen",
":proto", ":proto_py",
"//pylib/anki/_backend", "//pylib/anki/_backend",
], ],
imports = [ imports = [
@ -110,26 +110,17 @@ filegroup(
load("//pylib:protobuf.bzl", "py_proto") load("//pylib:protobuf.bzl", "py_proto")
py_proto( py_proto(
name = "proto_files", name = "proto_py",
srcs = ["//proto"], srcs = ["//proto"],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",
], ],
) )
filegroup(
name = "proto",
srcs = [
# "__init__.py",
":proto_files",
],
visibility = ["//pylib:__subpackages__"],
)
# only used for genbackend.py # only used for genbackend.py
py_library( py_library(
name = "proto_lib", name = "proto_lib",
srcs = [":proto"], srcs = [":proto_py", "__init__.py"],
imports = [".."], imports = [".."],
visibility = ["//pylib:__subpackages__"], visibility = ["//visibility:public"],
) )

View file

@ -1,8 +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
import os
import sys import sys
if not os.getenv("PROTOS_ONLY"):
from anki.buildinfo import version from anki.buildinfo import version
from anki.collection import Collection from anki.collection import Collection

View file

@ -8,6 +8,8 @@ import sys
import google.protobuf.descriptor import google.protobuf.descriptor
os.environ["PROTOS_ONLY"] = "1"
import anki.backend_pb2 import anki.backend_pb2
import anki.i18n_pb2 import anki.i18n_pb2
import anki.cards_pb2 import anki.cards_pb2
@ -20,6 +22,10 @@ import anki.scheduler_pb2
import anki.sync_pb2 import anki.sync_pb2
import anki.configs_pb2 import anki.configs_pb2
import anki.search_pb2 import anki.search_pb2
import anki.stats_pb2
import anki.card_rendering_pb2
import anki.tags_pb2
import anki.media_pb2
import stringcase import stringcase
@ -187,12 +193,16 @@ service_modules = dict(
SYNC=anki.sync_pb2, SYNC=anki.sync_pb2,
CONFIGS=anki.configs_pb2, CONFIGS=anki.configs_pb2,
SEARCH=anki.search_pb2, SEARCH=anki.search_pb2,
STATS=anki.stats_pb2,
CARD_RENDERING=anki.card_rendering_pb2,
TAGS=anki.tags_pb2,
MEDIA=anki.media_pb2,
) )
for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values: for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values:
# SERVICE_INDEX_TEST -> _TESTSERVICE # SERVICE_INDEX_TEST -> _TESTSERVICE
base = service.name.replace("SERVICE_INDEX_", "") base = service.name.replace("SERVICE_INDEX_", "")
service_pkg = service_modules.get(base) or anki.backend_pb2 service_pkg = service_modules.get(base)
service_var = "_" + base.replace("_", "") + "SERVICE" service_var = "_" + base.replace("_", "") + "SERVICE"
service_obj = getattr(service_pkg, service_var) service_obj = getattr(service_pkg, service_var)
service_index = service.number service_index = service.number

View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/card_rendering_pb2.pyi

View file

@ -7,15 +7,21 @@ from __future__ import annotations
from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast
import anki.backend_pb2 as _pb from anki import (
from anki import collection_pb2, configs_pb2, generic_pb2, search_pb2 card_rendering_pb2,
collection_pb2,
configs_pb2,
generic_pb2,
search_pb2,
stats_pb2,
)
from anki._legacy import DeprecatedNamesMixin, deprecated from anki._legacy import DeprecatedNamesMixin, deprecated
# protobuf we publicly export - listed first to avoid circular imports # protobuf we publicly export - listed first to avoid circular imports
SearchNode = search_pb2.SearchNode SearchNode = search_pb2.SearchNode
Progress = collection_pb2.Progress Progress = collection_pb2.Progress
EmptyCardsReport = _pb.EmptyCardsReport EmptyCardsReport = card_rendering_pb2.EmptyCardsReport
GraphPreferences = _pb.GraphPreferences GraphPreferences = stats_pb2.GraphPreferences
Preferences = configs_pb2.Preferences Preferences = configs_pb2.Preferences
UndoStatus = collection_pb2.UndoStatus UndoStatus = collection_pb2.UndoStatus
OpChanges = collection_pb2.OpChanges OpChanges = collection_pb2.OpChanges

View file

@ -10,8 +10,7 @@ from dataclasses import dataclass
from typing import Any, List, Optional, Tuple from typing import Any, List, Optional, Tuple
import anki import anki
import anki.backend_pb2 as _pb from anki import card_rendering_pb2, hooks
from anki import hooks
from anki.models import NotetypeDict from anki.models import NotetypeDict
from anki.template import TemplateRenderContext, TemplateRenderOutput from anki.template import TemplateRenderContext, TemplateRenderOutput
from anki.utils import call, isMac, namedtmp, tmpdir from anki.utils import call, isMac, namedtmp, tmpdir
@ -45,7 +44,9 @@ class ExtractedLatexOutput:
latex: List[ExtractedLatex] latex: List[ExtractedLatex]
@staticmethod @staticmethod
def from_proto(proto: _pb.ExtractLatexResponse) -> ExtractedLatexOutput: def from_proto(
proto: card_rendering_pb2.ExtractLatexResponse,
) -> ExtractedLatexOutput:
return ExtractedLatexOutput( return ExtractedLatexOutput(
html=proto.text, html=proto.text,
latex=[ latex=[

View file

@ -10,8 +10,7 @@ import sys
import time import time
from typing import Any, Callable, List, Optional, Tuple from typing import Any, Callable, List, Optional, Tuple
import anki from anki import media_pb2
import anki.backend_pb2 as _pb
from anki._legacy import deprecated from anki._legacy import deprecated
from anki.consts import * from anki.consts import *
from anki.latex import render_latex, render_latex_returning_errors from anki.latex import render_latex, render_latex_returning_errors
@ -27,7 +26,7 @@ def media_paths_from_col_path(col_path: str) -> Tuple[str, str]:
return (media_folder, media_db) return (media_folder, media_db)
CheckMediaResponse = _pb.CheckMediaResponse CheckMediaResponse = media_pb2.CheckMediaResponse
# fixme: look into whether we can drop chdir() below # fixme: look into whether we can drop chdir() below

1
pylib/anki/media_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/media_pb2.pyi

1
pylib/anki/stats_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/stats_pb2.pyi

View file

@ -16,15 +16,15 @@ import re
from typing import Collection, List, Match, Optional, Sequence from typing import Collection, List, Match, Optional, Sequence
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki.backend_pb2 as _pb
import anki.collection import anki.collection
from anki import tags_pb2
from anki.collection import OpChanges, OpChangesWithCount from anki.collection import OpChanges, OpChangesWithCount
from anki.decks import DeckId from anki.decks import DeckId
from anki.notes import NoteId from anki.notes import NoteId
from anki.utils import ids2str from anki.utils import ids2str
# public exports # public exports
TagTreeNode = _pb.TagTreeNode TagTreeNode = tags_pb2.TagTreeNode
MARKED_TAG = "marked" MARKED_TAG = "marked"

1
pylib/anki/tags_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/tags_pb2.pyi

View file

@ -32,8 +32,7 @@ from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
import anki import anki
import anki.backend_pb2 as _pb from anki import card_rendering_pb2, hooks
from anki import hooks
from anki.cards import Card from anki.cards import Card
from anki.decks import DeckManager from anki.decks import DeckManager
from anki.errors import TemplateError from anki.errors import TemplateError
@ -65,7 +64,9 @@ class PartiallyRenderedCard:
latex_svg: bool latex_svg: bool
@classmethod @classmethod
def from_proto(cls, out: _pb.RenderCardResponse) -> PartiallyRenderedCard: def from_proto(
cls, out: card_rendering_pb2.RenderCardResponse
) -> PartiallyRenderedCard:
qnodes = cls.nodes_from_proto(out.question_nodes) qnodes = cls.nodes_from_proto(out.question_nodes)
anodes = cls.nodes_from_proto(out.answer_nodes) anodes = cls.nodes_from_proto(out.answer_nodes)
@ -73,7 +74,7 @@ class PartiallyRenderedCard:
@staticmethod @staticmethod
def nodes_from_proto( def nodes_from_proto(
nodes: Sequence[_pb.RenderedTemplateNode], nodes: Sequence[card_rendering_pb2.RenderedTemplateNode],
) -> TemplateReplacementList: ) -> TemplateReplacementList:
results: TemplateReplacementList = [] results: TemplateReplacementList = []
for node in nodes: for node in nodes:
@ -90,7 +91,7 @@ class PartiallyRenderedCard:
return results return results
def av_tag_to_native(tag: _pb.AVTag) -> AVTag: def av_tag_to_native(tag: card_rendering_pb2.AVTag) -> AVTag:
val = tag.WhichOneof("value") val = tag.WhichOneof("value")
if val == "sound_or_video": if val == "sound_or_video":
return SoundOrVideoTag(filename=tag.sound_or_video) return SoundOrVideoTag(filename=tag.sound_or_video)
@ -104,7 +105,7 @@ def av_tag_to_native(tag: _pb.AVTag) -> AVTag:
) )
def av_tags_to_native(tags: Sequence[_pb.AVTag]) -> List[AVTag]: def av_tags_to_native(tags: Sequence[card_rendering_pb2.AVTag]) -> List[AVTag]:
return list(map(av_tag_to_native, tags)) return list(map(av_tag_to_native, tags))

View file

@ -12,6 +12,9 @@ import sys
(protoc, mypy_protobuf, outdir, *protos) = sys.argv[1:] (protoc, mypy_protobuf, outdir, *protos) = sys.argv[1:]
if protos[0].startswith("external"):
prefix = "external/anki/proto/"
else:
prefix = "proto/" prefix = "proto/"
# invoke protoc # invoke protoc

View file

@ -14,6 +14,7 @@ macro_rules! protobuf {
} }
protobuf!(backend); protobuf!(backend);
protobuf!(card_rendering);
protobuf!(cards); protobuf!(cards);
protobuf!(collection); protobuf!(collection);
protobuf!(configs); protobuf!(configs);
@ -21,8 +22,11 @@ protobuf!(deckconfig);
protobuf!(decks); protobuf!(decks);
protobuf!(generic); protobuf!(generic);
protobuf!(i18n); protobuf!(i18n);
protobuf!(media);
protobuf!(notes); protobuf!(notes);
protobuf!(notetypes); protobuf!(notetypes);
protobuf!(scheduler); protobuf!(scheduler);
protobuf!(search); protobuf!(search);
protobuf!(stats);
protobuf!(sync); protobuf!(sync);
protobuf!(tags);

View file

@ -3,7 +3,7 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@ -19,9 +19,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, buildHistogram } from "./added"; import { gatherData, buildHistogram } from "./added";
import type { GraphData } from "./added"; import type { GraphData } from "./added";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>; export let preferences: PreferenceStore<Stats.GraphPreferences>;
let histogramData = null as HistogramData | null; let histogramData = null as HistogramData | null;
let tableData: TableDatum[] = []; let tableData: TableDatum[] = [];

View file

@ -3,7 +3,7 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -14,7 +14,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { renderButtons } from "./buttons"; import { renderButtons } from "./buttons";
import { defaultGraphBounds, GraphRange, RevlogRange } from "./graph-helpers"; import { defaultGraphBounds, GraphRange, RevlogRange } from "./graph-helpers";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;

View file

@ -3,7 +3,7 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@ -18,8 +18,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, renderCalendar } from "./calendar"; import { gatherData, renderCalendar } from "./calendar";
import type { GraphData } from "./calendar"; import type { GraphData } from "./calendar";
export let sourceData: Backend.GraphsResponse; export let sourceData: Stats.GraphsResponse;
export let preferences: PreferenceStore<Backend.GraphPreferences>; export let preferences: PreferenceStore<Stats.GraphPreferences>;
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let nightMode: boolean; export let nightMode: boolean;

View file

@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="typescript"> <script lang="typescript">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
@ -15,9 +15,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, renderCards } from "./card-counts"; import { gatherData, renderCards } from "./card-counts";
import type { GraphData, TableDatum } from "./card-counts"; import type { GraphData, TableDatum } from "./card-counts";
export let sourceData: Backend.GraphsResponse; export let sourceData: Stats.GraphsResponse;
import * as tr2 from "lib/i18n"; import * as tr2 from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>; export let preferences: PreferenceStore<Stats.GraphPreferences>;
let { cardCountsSeparateInactive, browserLinksSupported } = preferences; let { cardCountsSeparateInactive, browserLinksSupported } = preferences;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -3,7 +3,7 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
@ -17,8 +17,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, prepareData } from "./ease"; import { gatherData, prepareData } from "./ease";
import type { TableDatum, SearchEventMap } from "./graph-helpers"; import type { TableDatum, SearchEventMap } from "./graph-helpers";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
export let preferences: PreferenceStore<Backend.GraphPreferences>; export let preferences: PreferenceStore<Stats.GraphPreferences>;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -5,7 +5,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -20,9 +20,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, buildHistogram } from "./future-due"; import { gatherData, buildHistogram } from "./future-due";
import type { GraphData } from "./future-due"; import type { GraphData } from "./future-due";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>; export let preferences: PreferenceStore<Stats.GraphPreferences>;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -3,7 +3,7 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -15,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { defaultGraphBounds, RevlogRange, GraphRange } from "./graph-helpers"; import { defaultGraphBounds, RevlogRange, GraphRange } from "./graph-helpers";
import { renderHours } from "./hours"; import { renderHours } from "./hours";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;
let graphRange: GraphRange = GraphRange.Year; let graphRange: GraphRange = GraphRange.Year;

View file

@ -5,7 +5,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import { timeSpan, MONTH } from "lib/time"; import { timeSpan, MONTH } from "lib/time";
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@ -23,9 +23,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { IntervalGraphData } from "./intervals"; import type { IntervalGraphData } from "./intervals";
import type { TableDatum, SearchEventMap } from "./graph-helpers"; import type { TableDatum, SearchEventMap } from "./graph-helpers";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>; export let preferences: PreferenceStore<Stats.GraphPreferences>;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -3,7 +3,7 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -19,7 +19,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, renderReviews } from "./reviews"; import { gatherData, renderReviews } from "./reviews";
import type { GraphData } from "./reviews"; import type { GraphData } from "./reviews";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";

View file

@ -3,14 +3,14 @@ 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="typescript"> <script lang="typescript">
import type { Backend } from "lib/proto"; import type { Stats } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import type { TodayData } from "./today"; import type { TodayData } from "./today";
import { gatherData } from "./today"; import { gatherData } from "./today";
export let sourceData: Backend.GraphsResponse | null = null; export let sourceData: Stats.GraphsResponse | null = null;
let todayData: TodayData | null = null; let todayData: TodayData | null = null;
$: if (sourceData) { $: if (sourceData) {

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import type { PreferenceRaw, PreferencePayload } from "sveltelib/preferences"; import type { PreferenceRaw, PreferencePayload } from "sveltelib/preferences";
import { Backend } from "lib/proto"; import { Stats } from "lib/proto";
import { postRequest } from "lib/postrequest"; import { postRequest } from "lib/postrequest";
import useAsync from "sveltelib/async"; import useAsync from "sveltelib/async";
@ -21,24 +21,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
async function getGraphData( async function getGraphData(
search: string, search: string,
days: number days: number
): Promise<Backend.GraphsResponse> { ): Promise<Stats.GraphsResponse> {
return Backend.GraphsResponse.decode( return Stats.GraphsResponse.decode(
await postRequest("/_anki/graphData", JSON.stringify({ search, days })) await postRequest("/_anki/graphData", JSON.stringify({ search, days }))
); );
} }
async function getGraphPreferences(): Promise<Backend.GraphPreferences> { async function getGraphPreferences(): Promise<Stats.GraphPreferences> {
return Backend.GraphPreferences.decode( return Stats.GraphPreferences.decode(
await postRequest("/_anki/graphPreferences", JSON.stringify({})) await postRequest("/_anki/graphPreferences", JSON.stringify({}))
); );
} }
async function setGraphPreferences( async function setGraphPreferences(
prefs: PreferencePayload<Backend.GraphPreferences> prefs: PreferencePayload<Stats.GraphPreferences>
): Promise<void> { ): Promise<void> {
await postRequest( await postRequest(
"/_anki/setGraphPreferences", "/_anki/setGraphPreferences",
Backend.GraphPreferences.encode(prefs).finish() Stats.GraphPreferences.encode(prefs).finish()
); );
} }
@ -56,10 +56,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
getPreferences( getPreferences(
getGraphPreferences, getGraphPreferences,
setGraphPreferences, setGraphPreferences,
Backend.GraphPreferences.toObject.bind(Backend.GraphPreferences) as ( Stats.GraphPreferences.toObject.bind(Stats.GraphPreferences) as (
preferences: Backend.GraphPreferences, preferences: Stats.GraphPreferences,
options: { defaults: boolean } options: { defaults: boolean }
) => PreferenceRaw<Backend.GraphPreferences> ) => PreferenceRaw<Stats.GraphPreferences>
) )
); );

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type { Backend, Cards } from "lib/proto"; import type { Stats, Cards } from "lib/proto";
import { import {
extent, extent,
@ -28,7 +28,7 @@ export interface GraphData {
daysAdded: number[]; daysAdded: number[];
} }
export function gatherData(data: Backend.GraphsResponse): GraphData { export function gatherData(data: Stats.GraphsResponse): GraphData {
const daysAdded = (data.cards as Cards.Card[]).map((card) => { const daysAdded = (data.cards as Cards.Card[]).map((card) => {
const elapsedSecs = (card.id as number) / 1000 - data.nextDayAtSecs; const elapsedSecs = (card.id as number) / 1000 - data.nextDayAtSecs;
return Math.ceil(elapsedSecs / 86400); return Math.ceil(elapsedSecs / 86400);

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import { Backend } from "lib/proto"; import { Stats } from "lib/proto";
import { import {
interpolateRdYlGn, interpolateRdYlGn,
@ -36,15 +36,15 @@ export interface GraphData {
mature: ButtonCounts; mature: ButtonCounts;
} }
const ReviewKind = Backend.RevlogEntry.ReviewKind; const ReviewKind = Stats.RevlogEntry.ReviewKind;
export function gatherData(data: Backend.GraphsResponse, range: GraphRange): GraphData { export function gatherData(data: Stats.GraphsResponse, range: GraphRange): GraphData {
const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs); const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs);
const learning: ButtonCounts = [0, 0, 0, 0]; const learning: ButtonCounts = [0, 0, 0, 0];
const young: ButtonCounts = [0, 0, 0, 0]; const young: ButtonCounts = [0, 0, 0, 0];
const mature: ButtonCounts = [0, 0, 0, 0]; const mature: ButtonCounts = [0, 0, 0, 0];
for (const review of data.revlog as Backend.RevlogEntry[]) { for (const review of data.revlog as Stats.RevlogEntry[]) {
if (cutoff && (review.id as number) < cutoff) { if (cutoff && (review.id as number) < cutoff) {
continue; continue;
} }
@ -96,7 +96,7 @@ interface TotalCorrect {
export function renderButtons( export function renderButtons(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
origData: Backend.GraphsResponse, origData: Stats.GraphsResponse,
range: GraphRange range: GraphRange
): void { ): void {
const sourceData = gatherData(origData, range); const sourceData = gatherData(origData, range);

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import { Backend } from "lib/proto"; import { Stats } from "lib/proto";
import { import {
interpolateBlues, interpolateBlues,
select, select,
@ -49,16 +49,16 @@ interface DayDatum {
date: Date; date: Date;
} }
type WeekdayType = Backend.GraphPreferences.Weekday; type WeekdayType = Stats.GraphPreferences.Weekday;
const Weekday = Backend.GraphPreferences.Weekday; /* enum */ const Weekday = Stats.GraphPreferences.Weekday; /* enum */
export function gatherData( export function gatherData(
data: Backend.GraphsResponse, data: Stats.GraphsResponse,
firstDayOfWeek: WeekdayType firstDayOfWeek: WeekdayType
): GraphData { ): GraphData {
const reviewCount = new Map<number, number>(); const reviewCount = new Map<number, number>();
for (const review of data.revlog as Backend.RevlogEntry[]) { for (const review of data.revlog as Stats.RevlogEntry[]) {
if (review.buttonChosen == 0) { if (review.buttonChosen == 0) {
continue; continue;
} }

View file

@ -7,7 +7,7 @@
*/ */
import { CardQueue, CardType } from "lib/cards"; import { CardQueue, CardType } from "lib/cards";
import type { Backend, Cards } from "lib/proto"; import type { Stats, Cards } from "lib/proto";
import { import {
schemeGreens, schemeGreens,
schemeBlues, schemeBlues,
@ -124,7 +124,7 @@ function countCards(cards: Cards.ICard[], separateInactive: boolean): Count[] {
} }
export function gatherData( export function gatherData(
data: Backend.GraphsResponse, data: Stats.GraphsResponse,
separateInactive: boolean separateInactive: boolean
): GraphData { ): GraphData {
const totalCards = data.cards.length; const totalCards = data.cards.length;

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type { Backend, Cards } from "lib/proto"; import type { Stats, Cards } from "lib/proto";
import { import {
extent, extent,
histogram, histogram,
@ -26,7 +26,7 @@ export interface GraphData {
eases: number[]; eases: number[];
} }
export function gatherData(data: Backend.GraphsResponse): GraphData { export function gatherData(data: Stats.GraphsResponse): GraphData {
const eases = (data.cards as Cards.Card[]) const eases = (data.cards as Cards.Card[])
.filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype)) .filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype))
.map((c) => c.easeFactor / 10); .map((c) => c.easeFactor / 10);

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type { Backend, Cards } from "lib/proto"; import type { Stats, Cards } from "lib/proto";
import { import {
extent, extent,
histogram, histogram,
@ -30,7 +30,7 @@ export interface GraphData {
haveBacklog: boolean; haveBacklog: boolean;
} }
export function gatherData(data: Backend.GraphsResponse): GraphData { export function gatherData(data: Stats.GraphsResponse): GraphData {
const isLearning = (card: Cards.Card): boolean => const isLearning = (card: Cards.Card): boolean =>
[CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue); [CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue);

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
@typescript-eslint/ban-ts-comment: "off" */ @typescript-eslint/ban-ts-comment: "off" */
import type { Backend, Cards } from "lib/proto"; import type { Stats, Cards } from "lib/proto";
import type { Selection } from "d3"; import type { Selection } from "d3";
// amount of data to fetch from backend // amount of data to fetch from backend
@ -29,7 +29,7 @@ export enum GraphRange {
export interface GraphsContext { export interface GraphsContext {
cards: Cards.Card[]; cards: Cards.Card[];
revlog: Backend.RevlogEntry[]; revlog: Stats.RevlogEntry[];
revlogRange: RevlogRange; revlogRange: RevlogRange;
nightMode: boolean; nightMode: boolean;
} }

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import { Backend } from "lib/proto"; import { Stats } from "lib/proto";
import { import {
interpolateBlues, interpolateBlues,
select, select,
@ -37,15 +37,15 @@ interface Hour {
correctCount: number; correctCount: number;
} }
const ReviewKind = Backend.RevlogEntry.ReviewKind; const ReviewKind = Stats.RevlogEntry.ReviewKind;
function gatherData(data: Backend.GraphsResponse, range: GraphRange): Hour[] { function gatherData(data: Stats.GraphsResponse, range: GraphRange): Hour[] {
const hours = [...Array(24)].map((_n, idx: number) => { const hours = [...Array(24)].map((_n, idx: number) => {
return { hour: idx, totalCount: 0, correctCount: 0 } as Hour; return { hour: idx, totalCount: 0, correctCount: 0 } as Hour;
}); });
const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs); const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs);
for (const review of data.revlog as Backend.RevlogEntry[]) { for (const review of data.revlog as Stats.RevlogEntry[]) {
switch (review.reviewKind) { switch (review.reviewKind) {
case ReviewKind.LEARNING: case ReviewKind.LEARNING:
case ReviewKind.REVIEW: case ReviewKind.REVIEW:
@ -74,7 +74,7 @@ function gatherData(data: Backend.GraphsResponse, range: GraphRange): Hour[] {
export function renderHours( export function renderHours(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
origData: Backend.GraphsResponse, origData: Stats.GraphsResponse,
range: GraphRange range: GraphRange
): void { ): void {
const data = gatherData(origData, range); const data = gatherData(origData, range);

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type { Backend, Cards } from "lib/proto"; import type { Stats, Cards } from "lib/proto";
import { import {
extent, extent,
histogram, histogram,
@ -36,7 +36,7 @@ export enum IntervalRange {
All = 3, All = 3,
} }
export function gatherIntervalData(data: Backend.GraphsResponse): IntervalGraphData { export function gatherIntervalData(data: Stats.GraphsResponse): IntervalGraphData {
const intervals = (data.cards as Cards.Card[]) const intervals = (data.cards as Cards.Card[])
.filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype)) .filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype))
.map((c) => c.interval); .map((c) => c.interval);

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import { Backend } from "lib/proto"; import { Stats } from "lib/proto";
import { timeSpan, dayLabel } from "lib/time"; import { timeSpan, dayLabel } from "lib/time";
import { import {
@ -50,15 +50,15 @@ export interface GraphData {
reviewTime: Map<number, Reviews>; reviewTime: Map<number, Reviews>;
} }
const ReviewKind = Backend.RevlogEntry.ReviewKind; const ReviewKind = Stats.RevlogEntry.ReviewKind;
type BinType = Bin<Map<number, Reviews[]>, number>; type BinType = Bin<Map<number, Reviews[]>, number>;
export function gatherData(data: Backend.GraphsResponse): GraphData { export function gatherData(data: Stats.GraphsResponse): GraphData {
const reviewCount = new Map<number, Reviews>(); const reviewCount = new Map<number, Reviews>();
const reviewTime = new Map<number, Reviews>(); const reviewTime = new Map<number, Reviews>();
const empty = { mature: 0, young: 0, learn: 0, relearn: 0, early: 0 }; const empty = { mature: 0, young: 0, learn: 0, relearn: 0, early: 0 };
for (const review of data.revlog as Backend.RevlogEntry[]) { for (const review of data.revlog as Stats.RevlogEntry[]) {
if (review.reviewKind == ReviewKind.MANUAL) { if (review.reviewKind == ReviewKind.MANUAL) {
// don't count days with only manual scheduling // don't count days with only manual scheduling
continue; continue;

View file

@ -1,7 +1,7 @@
// 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 { Backend } from "lib/proto"; import { Stats } from "lib/proto";
import { studiedToday } from "lib/time"; import { studiedToday } from "lib/time";
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
@ -11,9 +11,9 @@ export interface TodayData {
lines: string[]; lines: string[];
} }
const ReviewKind = Backend.RevlogEntry.ReviewKind; const ReviewKind = Stats.RevlogEntry.ReviewKind;
export function gatherData(data: Backend.GraphsResponse): TodayData { export function gatherData(data: Stats.GraphsResponse): TodayData {
let answerCount = 0; let answerCount = 0;
let answerMillis = 0; let answerMillis = 0;
let correctCount = 0; let correctCount = 0;
@ -26,7 +26,7 @@ export function gatherData(data: Backend.GraphsResponse): TodayData {
const startOfTodayMillis = (data.nextDayAtSecs - 86400) * 1000; const startOfTodayMillis = (data.nextDayAtSecs - 86400) * 1000;
for (const review of data.revlog as Backend.RevlogEntry[]) { for (const review of data.revlog as Stats.RevlogEntry[]) {
if (review.id < startOfTodayMillis) { if (review.id < startOfTodayMillis) {
continue; continue;
} }

View file

@ -2,9 +2,9 @@
// 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 { anki } from "./backend_proto"; import { anki } from "./backend_proto";
import Backend = anki.backend;
import Cards = anki.cards; import Cards = anki.cards;
import DeckConfig = anki.deckconfig; import DeckConfig = anki.deckconfig;
import Notetypes = anki.notetypes; import Notetypes = anki.notetypes;
import Scheduler = anki.scheduler; import Scheduler = anki.scheduler;
export { Backend, Cards, DeckConfig, Notetypes, Scheduler }; import Stats = anki.stats;
export { Stats, Cards, DeckConfig, Notetypes, Scheduler };