Switch CardInfoDialog to ts page (#1414)

* Only collect card stats on the backend ...

... instead of rendering an HTML string using askama.

* Add ts page Card Info

* Update test for new `col.card_stats()`

* Remove obsolete CardStats code

* Use new ts page in `CardInfoDialog`

* Align start and end instead of left and right

Curiously, `text-align: start` does not work for `th` tags if assigned
via classes.

* Adopt ts refactorings after rebase

#1405 and #1409

* Clean up `ts/card-info/BUILD.bazel`

* Port card info logic from Rust to TS

* Move repeated field to the top

https://github.com/ankitects/anki/pull/1414#discussion_r725402730

* Convert pseudo classes to interfaces

* CardInfoPage -> CardInfo

* Make revlog in card info optional

* Add legacy support for old card stats

* Check for undefined instead of falsy

* Make Revlog separate component

* drop askama dependency (dae)

* Fix nightmode for legacy card stats
This commit is contained in:
RumovZ 2021-10-14 11:22:47 +02:00 committed by GitHub
parent 7128de895f
commit 3672b0fe73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 648 additions and 1705 deletions

129
Cargo.lock generated
View file

@ -55,7 +55,6 @@ version = "0.0.0"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"anki_i18n", "anki_i18n",
"askama",
"async-trait", "async-trait",
"blake3", "blake3",
"bytes", "bytes",
@ -73,7 +72,7 @@ dependencies = [
"itertools", "itertools",
"lazy_static", "lazy_static",
"linkcheck", "linkcheck",
"nom 7.0.0", "nom",
"num-integer", "num-integer",
"num_enum", "num_enum",
"once_cell", "once_cell",
@ -158,64 +157,12 @@ dependencies = [
"nodrop", "nodrop",
] ]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd"
[[package]]
name = "askama"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d298738b6e47e1034e560e5afe63aa488fea34e25ec11b855a76f0d7b8e73134"
dependencies = [
"askama_derive",
"askama_escape",
"askama_shared",
]
[[package]]
name = "askama_derive"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2925c4c290382f9d2fa3d1c1b6a63fa1427099721ecca4749b154cc9c25522"
dependencies = [
"askama_shared",
"proc-macro2",
"syn",
]
[[package]]
name = "askama_escape"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb"
[[package]]
name = "askama_shared"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2582b77e0f3c506ec4838a25fa8a5f97b9bed72bb6d3d272ea1c031d8bd373bc"
dependencies = [
"askama_escape",
"humansize",
"nom 6.1.2",
"num-traits",
"percent-encoding",
"proc-macro2",
"quote",
"serde",
"syn",
"toml",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.51" version = "0.1.51"
@ -256,18 +203,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]] [[package]]
name = "blake3" name = "blake3"
version = "1.0.0" version = "1.0.0"
@ -650,12 +585,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]] [[package]]
name = "futf" name = "futf"
version = "0.1.4" version = "0.1.4"
@ -925,12 +854,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
[[package]]
name = "humansize"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -1127,19 +1050,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec 0.5.2",
"bitflags",
"cfg-if",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.103" version = "0.2.103"
@ -1346,19 +1256,6 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [
"bitvec",
"funty",
"lexical-core",
"memchr",
"version_check",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.0.0" version = "7.0.0"
@ -1902,12 +1799,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@ -2473,12 +2364,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.1" version = "0.8.1"
@ -2542,12 +2427,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.2.0" version = "3.2.0"
@ -3162,12 +3041,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]] [[package]]
name = "xml5ever" name = "xml5ever"
version = "0.16.1" version = "0.16.1"

View file

@ -101,16 +101,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.arrayvec-0.4.12.bazel"), build_file = Label("//cargo/remote:BUILD.arrayvec-0.4.12.bazel"),
) )
maybe(
http_archive,
name = "raze__arrayvec__0_5_2",
url = "https://crates.io/api/v1/crates/arrayvec/0.5.2/download",
type = "tar.gz",
sha256 = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b",
strip_prefix = "arrayvec-0.5.2",
build_file = Label("//cargo/remote:BUILD.arrayvec-0.5.2.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__arrayvec__0_7_1", name = "raze__arrayvec__0_7_1",
@ -121,46 +111,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.arrayvec-0.7.1.bazel"), build_file = Label("//cargo/remote:BUILD.arrayvec-0.7.1.bazel"),
) )
maybe(
http_archive,
name = "raze__askama__0_10_5",
url = "https://crates.io/api/v1/crates/askama/0.10.5/download",
type = "tar.gz",
sha256 = "d298738b6e47e1034e560e5afe63aa488fea34e25ec11b855a76f0d7b8e73134",
strip_prefix = "askama-0.10.5",
build_file = Label("//cargo/remote:BUILD.askama-0.10.5.bazel"),
)
maybe(
http_archive,
name = "raze__askama_derive__0_10_5",
url = "https://crates.io/api/v1/crates/askama_derive/0.10.5/download",
type = "tar.gz",
sha256 = "ca2925c4c290382f9d2fa3d1c1b6a63fa1427099721ecca4749b154cc9c25522",
strip_prefix = "askama_derive-0.10.5",
build_file = Label("//cargo/remote:BUILD.askama_derive-0.10.5.bazel"),
)
maybe(
http_archive,
name = "raze__askama_escape__0_10_1",
url = "https://crates.io/api/v1/crates/askama_escape/0.10.1/download",
type = "tar.gz",
sha256 = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb",
strip_prefix = "askama_escape-0.10.1",
build_file = Label("//cargo/remote:BUILD.askama_escape-0.10.1.bazel"),
)
maybe(
http_archive,
name = "raze__askama_shared__0_11_1",
url = "https://crates.io/api/v1/crates/askama_shared/0.11.1/download",
type = "tar.gz",
sha256 = "2582b77e0f3c506ec4838a25fa8a5f97b9bed72bb6d3d272ea1c031d8bd373bc",
strip_prefix = "askama_shared-0.11.1",
build_file = Label("//cargo/remote:BUILD.askama_shared-0.11.1.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__async_trait__0_1_51", name = "raze__async_trait__0_1_51",
@ -211,16 +161,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.bitflags-1.3.2.bazel"), build_file = Label("//cargo/remote:BUILD.bitflags-1.3.2.bazel"),
) )
maybe(
http_archive,
name = "raze__bitvec__0_19_5",
url = "https://crates.io/api/v1/crates/bitvec/0.19.5/download",
type = "tar.gz",
sha256 = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321",
strip_prefix = "bitvec-0.19.5",
build_file = Label("//cargo/remote:BUILD.bitvec-0.19.5.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__blake3__1_0_0", name = "raze__blake3__1_0_0",
@ -641,16 +581,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.form_urlencoded-1.0.1.bazel"), build_file = Label("//cargo/remote:BUILD.form_urlencoded-1.0.1.bazel"),
) )
maybe(
http_archive,
name = "raze__funty__1_1_0",
url = "https://crates.io/api/v1/crates/funty/1.1.0/download",
type = "tar.gz",
sha256 = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7",
strip_prefix = "funty-1.1.0",
build_file = Label("//cargo/remote:BUILD.funty-1.1.0.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__futf__0_1_4", name = "raze__futf__0_1_4",
@ -921,16 +851,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.httpdate-1.0.1.bazel"), build_file = Label("//cargo/remote:BUILD.httpdate-1.0.1.bazel"),
) )
maybe(
http_archive,
name = "raze__humansize__1_1_1",
url = "https://crates.io/api/v1/crates/humansize/1.1.1/download",
type = "tar.gz",
sha256 = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026",
strip_prefix = "humansize-1.1.1",
build_file = Label("//cargo/remote:BUILD.humansize-1.1.1.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__humantime__2_1_0", name = "raze__humantime__2_1_0",
@ -1121,16 +1041,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.lazy_static-1.4.0.bazel"), build_file = Label("//cargo/remote:BUILD.lazy_static-1.4.0.bazel"),
) )
maybe(
http_archive,
name = "raze__lexical_core__0_7_6",
url = "https://crates.io/api/v1/crates/lexical-core/0.7.6/download",
type = "tar.gz",
sha256 = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe",
strip_prefix = "lexical-core-0.7.6",
build_file = Label("//cargo/remote:BUILD.lexical-core-0.7.6.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__libc__0_2_103", name = "raze__libc__0_2_103",
@ -1351,16 +1261,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.nodrop-0.1.14.bazel"), build_file = Label("//cargo/remote:BUILD.nodrop-0.1.14.bazel"),
) )
maybe(
http_archive,
name = "raze__nom__6_1_2",
url = "https://crates.io/api/v1/crates/nom/6.1.2/download",
type = "tar.gz",
sha256 = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2",
strip_prefix = "nom-6.1.2",
build_file = Label("//cargo/remote:BUILD.nom-6.1.2.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__nom__7_0_0", name = "raze__nom__7_0_0",
@ -1901,16 +1801,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.quote-1.0.9.bazel"), build_file = Label("//cargo/remote:BUILD.quote-1.0.9.bazel"),
) )
maybe(
http_archive,
name = "raze__radium__0_5_3",
url = "https://crates.io/api/v1/crates/radium/0.5.3/download",
type = "tar.gz",
sha256 = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8",
strip_prefix = "radium-0.5.3",
build_file = Label("//cargo/remote:BUILD.radium-0.5.3.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__rand__0_7_3", name = "raze__rand__0_7_3",
@ -2441,16 +2331,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.stable_deref_trait-1.2.0.bazel"), build_file = Label("//cargo/remote:BUILD.stable_deref_trait-1.2.0.bazel"),
) )
maybe(
http_archive,
name = "raze__static_assertions__1_1_0",
url = "https://crates.io/api/v1/crates/static_assertions/1.1.0/download",
type = "tar.gz",
sha256 = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f",
strip_prefix = "static_assertions-1.1.0",
build_file = Label("//cargo/remote:BUILD.static_assertions-1.1.0.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__string_cache__0_8_1", name = "raze__string_cache__0_8_1",
@ -2511,16 +2391,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.take_mut-0.2.2.bazel"), build_file = Label("//cargo/remote:BUILD.take_mut-0.2.2.bazel"),
) )
maybe(
http_archive,
name = "raze__tap__1_0_1",
url = "https://crates.io/api/v1/crates/tap/1.0.1/download",
type = "tar.gz",
sha256 = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369",
strip_prefix = "tap-1.0.1",
build_file = Label("//cargo/remote:BUILD.tap-1.0.1.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__tempfile__3_2_0", name = "raze__tempfile__3_2_0",
@ -3201,16 +3071,6 @@ def raze_fetch_remote_crates():
build_file = Label("//cargo/remote:BUILD.winreg-0.7.0.bazel"), build_file = Label("//cargo/remote:BUILD.winreg-0.7.0.bazel"),
) )
maybe(
http_archive,
name = "raze__wyz__0_2_0",
url = "https://crates.io/api/v1/crates/wyz/0.2.0/download",
type = "tar.gz",
sha256 = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214",
strip_prefix = "wyz-0.2.0",
build_file = Label("//cargo/remote:BUILD.wyz-0.2.0.bazel"),
)
maybe( maybe(
http_archive, http_archive,
name = "raze__xml5ever__0_16_1", name = "raze__xml5ever__0_16_1",

View file

@ -107,15 +107,6 @@
"license_file": null, "license_file": null,
"description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString." "description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString."
}, },
{
"name": "arrayvec",
"version": "0.5.2",
"authors": "bluss",
"repository": "https://github.com/bluss/arrayvec",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString."
},
{ {
"name": "arrayvec", "name": "arrayvec",
"version": "0.7.1", "version": "0.7.1",
@ -125,42 +116,6 @@
"license_file": null, "license_file": null,
"description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString." "description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString."
}, },
{
"name": "askama",
"version": "0.10.5",
"authors": "Dirkjan Ochtman <dirkjan@ochtman.nl>",
"repository": "https://github.com/djc/askama",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Type-safe, compiled Jinja-like templates for Rust"
},
{
"name": "askama_derive",
"version": "0.10.5",
"authors": "Dirkjan Ochtman <dirkjan@ochtman.nl>",
"repository": "https://github.com/djc/askama",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Procedural macro package for Askama"
},
{
"name": "askama_escape",
"version": "0.10.1",
"authors": "Dirkjan Ochtman <dirkjan@ochtman.nl>",
"repository": "https://github.com/djc/askama",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Optimized HTML escaping code, extracted from Askama"
},
{
"name": "askama_shared",
"version": "0.11.1",
"authors": "Dirkjan Ochtman <dirkjan@ochtman.nl>",
"repository": "https://github.com/djc/askama",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Shared code for Askama"
},
{ {
"name": "async-trait", "name": "async-trait",
"version": "0.1.51", "version": "0.1.51",
@ -206,15 +161,6 @@
"license_file": null, "license_file": null,
"description": "A macro to generate structures which behave like bitflags." "description": "A macro to generate structures which behave like bitflags."
}, },
{
"name": "bitvec",
"version": "0.19.5",
"authors": "myrrlyn <self@myrrlyn.dev>",
"repository": "https://github.com/myrrlyn/bitvec",
"license": "MIT",
"license_file": null,
"description": "A crate for manipulating memory, bit by bit"
},
{ {
"name": "blake3", "name": "blake3",
"version": "1.0.0", "version": "1.0.0",
@ -593,15 +539,6 @@
"license_file": null, "license_file": null,
"description": "Parser and serializer for the application/x-www-form-urlencoded syntax, as used by HTML forms." "description": "Parser and serializer for the application/x-www-form-urlencoded syntax, as used by HTML forms."
}, },
{
"name": "funty",
"version": "1.1.0",
"authors": "myrrlyn <self@myrrlyn.dev>",
"repository": "https://github.com/myrrlyn/funty",
"license": "MIT",
"license_file": null,
"description": "Trait generalization over the primitive types"
},
{ {
"name": "futf", "name": "futf",
"version": "0.1.4", "version": "0.1.4",
@ -845,15 +782,6 @@
"license_file": null, "license_file": null,
"description": "HTTP date parsing and formatting" "description": "HTTP date parsing and formatting"
}, },
{
"name": "humansize",
"version": "1.1.1",
"authors": "Leopold Arkham <leopold.arkham@gmail.com>",
"repository": "https://github.com/LeopoldArkham/humansize",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "A configurable crate to easily represent file sizes in a human-readable format."
},
{ {
"name": "humantime", "name": "humantime",
"version": "2.1.0", "version": "2.1.0",
@ -1025,15 +953,6 @@
"license_file": null, "license_file": null,
"description": "A macro for declaring lazily evaluated statics in Rust." "description": "A macro for declaring lazily evaluated statics in Rust."
}, },
{
"name": "lexical-core",
"version": "0.7.6",
"authors": "Alex Huszagh <ahuszagh@gmail.com>",
"repository": "https://github.com/Alexhuszagh/rust-lexical/tree/master/lexical-core",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Lexical, to- and from-string conversion routines."
},
{ {
"name": "libc", "name": "libc",
"version": "0.2.103", "version": "0.2.103",
@ -1232,15 +1151,6 @@
"license_file": null, "license_file": null,
"description": "A wrapper type to inhibit drop (destructor). ***Deprecated: Use ManuallyDrop or MaybeUninit instead!***" "description": "A wrapper type to inhibit drop (destructor). ***Deprecated: Use ManuallyDrop or MaybeUninit instead!***"
}, },
{
"name": "nom",
"version": "6.1.2",
"authors": "contact@geoffroycouprie.com",
"repository": "https://github.com/Geal/nom",
"license": "MIT",
"license_file": null,
"description": "A byte-oriented, zero-copy, parser combinators library"
},
{ {
"name": "nom", "name": "nom",
"version": "7.0.0", "version": "7.0.0",
@ -1727,15 +1637,6 @@
"license_file": null, "license_file": null,
"description": "Quasi-quoting macro quote!(...)" "description": "Quasi-quoting macro quote!(...)"
}, },
{
"name": "radium",
"version": "0.5.3",
"authors": "Nika Layzell <nika@thelayzells.com>|myrrlyn <self@myrrlyn.dev>",
"repository": "https://github.com/mystor/radium",
"license": "MIT",
"license_file": null,
"description": "Helper traits for working with maybe-atomic values"
},
{ {
"name": "rand", "name": "rand",
"version": "0.7.3", "version": "0.7.3",
@ -2222,15 +2123,6 @@
"license_file": null, "license_file": null,
"description": "An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental." "description": "An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental."
}, },
{
"name": "static_assertions",
"version": "1.1.0",
"authors": "Nikolai Vazquez",
"repository": "https://github.com/nvzqz/static-assertions-rs",
"license": "Apache-2.0 OR MIT",
"license_file": null,
"description": "Compile-time assertions to ensure that invariants are met."
},
{ {
"name": "string_cache", "name": "string_cache",
"version": "0.8.1", "version": "0.8.1",
@ -2285,15 +2177,6 @@
"license_file": null, "license_file": null,
"description": "Take a T from a &mut T temporarily" "description": "Take a T from a &mut T temporarily"
}, },
{
"name": "tap",
"version": "1.0.1",
"authors": "Elliott Linder <elliott.darfink@gmail.com>|myrrlyn <self@myrrlyn.dev>",
"repository": "https://github.com/myrrlyn/tap",
"license": "MIT",
"license_file": null,
"description": "Generic extensions for tapping values in Rust"
},
{ {
"name": "tempfile", "name": "tempfile",
"version": "3.2.0", "version": "3.2.0",
@ -2906,15 +2789,6 @@
"license_file": null, "license_file": null,
"description": "Rust bindings to MS Windows Registry API" "description": "Rust bindings to MS Windows Registry API"
}, },
{
"name": "wyz",
"version": "0.2.0",
"authors": "myrrlyn <self@myrrlyn.dev>",
"repository": "https://github.com/myrrlyn/wyz",
"license": "MIT",
"license_file": null,
"description": "myrrlyns utility collection"
},
{ {
"name": "xml5ever", "name": "xml5ever",
"version": "0.16.1", "version": "0.16.1",

View file

@ -1,62 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
# Unsupported target "arraystring" with type "bench" omitted
# Unsupported target "extend" with type "bench" omitted
rust_library(
name = "arrayvec",
srcs = glob(["**/*.rs"]),
crate_features = [
"array-sizes-33-128",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.5.2",
# buildifier: leave-alone
deps = [
],
)
# Unsupported target "serde" with type "test" omitted
# Unsupported target "tests" with type "test" omitted

View file

@ -1,63 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
rust_library(
name = "askama",
srcs = glob(["**/*.rs"]),
crate_features = [
"config",
"default",
"humansize",
"num-traits",
"urlencode",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
proc_macro_deps = [
"@raze__askama_derive__0_10_5//:askama_derive",
],
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.10.5",
# buildifier: leave-alone
deps = [
"@raze__askama_escape__0_10_1//:askama_escape",
"@raze__askama_shared__0_11_1//:askama_shared",
],
)

View file

@ -1,56 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
rust_library(
name = "askama_derive",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "proc-macro",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.10.5",
# buildifier: leave-alone
deps = [
"@raze__askama_shared__0_11_1//:askama_shared",
"@raze__proc_macro2__1_0_29//:proc_macro2",
"@raze__syn__1_0_77//:syn",
],
)

View file

@ -1,55 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
# Unsupported target "all" with type "bench" omitted
rust_library(
name = "askama_escape",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.10.1",
# buildifier: leave-alone
deps = [
],
)

View file

@ -1,69 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
rust_library(
name = "askama_shared",
srcs = glob(["**/*.rs"]),
crate_features = [
"config",
"humansize",
"num-traits",
"percent-encoding",
"serde",
"toml",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.11.1",
# buildifier: leave-alone
deps = [
"@raze__askama_escape__0_10_1//:askama_escape",
"@raze__humansize__1_1_1//:humansize",
"@raze__nom__6_1_2//:nom",
"@raze__num_traits__0_2_14//:num_traits",
"@raze__percent_encoding__2_1_0//:percent_encoding",
"@raze__proc_macro2__1_0_29//:proc_macro2",
"@raze__quote__1_0_9//:quote",
"@raze__serde__1_0_130//:serde",
"@raze__syn__1_0_77//:syn",
"@raze__toml__0_5_8//:toml",
],
)

View file

@ -1,65 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT"
])
# Generated Targets
# Unsupported target "macros" with type "bench" omitted
# Unsupported target "memcpy" with type "bench" omitted
# Unsupported target "slice" with type "bench" omitted
rust_library(
name = "bitvec",
srcs = glob(["**/*.rs"]),
crate_features = [
"alloc",
"std",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.19.5",
# buildifier: leave-alone
deps = [
"@raze__funty__1_1_0//:funty",
"@raze__radium__0_5_3//:radium",
"@raze__tap__1_0_1//:tap",
"@raze__wyz__0_2_0//:wyz",
],
)

View file

@ -1,53 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT"
])
# Generated Targets
rust_library(
name = "funty",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "1.1.0",
# buildifier: leave-alone
deps = [
],
)

View file

@ -1,57 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
# Unsupported target "custom_options" with type "example" omitted
# Unsupported target "sizes" with type "example" omitted
rust_library(
name = "humansize",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2015",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "1.1.1",
# buildifier: leave-alone
deps = [
],
)

View file

@ -1,102 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
# buildifier: disable=out-of-order-load
# buildifier: disable=load-on-top
load(
"@rules_rust//cargo:cargo_build_script.bzl",
"cargo_build_script",
)
cargo_build_script(
name = "lexical_core_build_script",
srcs = glob(["**/*.rs"]),
build_script_env = {
},
crate_features = [
"arrayvec",
"correct",
"default",
"ryu",
"static_assertions",
"std",
"table",
],
crate_root = "build.rs",
data = glob(["**"]),
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.7.6",
visibility = ["//visibility:private"],
deps = [
],
)
rust_library(
name = "lexical_core",
srcs = glob(["**/*.rs"]),
crate_features = [
"arrayvec",
"correct",
"default",
"ryu",
"static_assertions",
"std",
"table",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.7.6",
# buildifier: leave-alone
deps = [
":lexical_core_build_script",
"@raze__arrayvec__0_5_2//:arrayvec",
"@raze__bitflags__1_3_2//:bitflags",
"@raze__cfg_if__1_0_0//:cfg_if",
"@raze__ryu__1_0_5//:ryu",
"@raze__static_assertions__1_1_0//:static_assertions",
],
)

View file

@ -1,162 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT"
])
# Generated Targets
# buildifier: disable=out-of-order-load
# buildifier: disable=load-on-top
load(
"@rules_rust//cargo:cargo_build_script.bzl",
"cargo_build_script",
)
cargo_build_script(
name = "nom_build_script",
srcs = glob(["**/*.rs"]),
build_script_env = {
},
crate_features = [
"alloc",
"bitvec",
"default",
"funty",
"lexical",
"lexical-core",
"std",
],
crate_root = "build.rs",
data = glob(["**"]),
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "6.1.2",
visibility = ["//visibility:private"],
deps = [
"@raze__version_check__0_9_3//:version_check",
],
)
# Unsupported target "arithmetic" with type "bench" omitted
# Unsupported target "http" with type "bench" omitted
# Unsupported target "ini" with type "bench" omitted
# Unsupported target "ini_complete" with type "bench" omitted
# Unsupported target "ini_str" with type "bench" omitted
# Unsupported target "json" with type "bench" omitted
# Unsupported target "number" with type "bench" omitted
# Unsupported target "json" with type "example" omitted
# Unsupported target "s_expression" with type "example" omitted
# Unsupported target "string" with type "example" omitted
rust_library(
name = "nom",
srcs = glob(["**/*.rs"]),
crate_features = [
"alloc",
"bitvec",
"default",
"funty",
"lexical",
"lexical-core",
"std",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "6.1.2",
# buildifier: leave-alone
deps = [
":nom_build_script",
"@raze__bitvec__0_19_5//:bitvec",
"@raze__funty__1_1_0//:funty",
"@raze__lexical_core__0_7_6//:lexical_core",
"@raze__memchr__2_4_1//:memchr",
],
)
# Unsupported target "arithmetic" with type "test" omitted
# Unsupported target "arithmetic_ast" with type "test" omitted
# Unsupported target "bitstream" with type "test" omitted
# Unsupported target "blockbuf-arithmetic" with type "test" omitted
# Unsupported target "css" with type "test" omitted
# Unsupported target "custom_errors" with type "test" omitted
# Unsupported target "escaped" with type "test" omitted
# Unsupported target "float" with type "test" omitted
# Unsupported target "fnmut" with type "test" omitted
# Unsupported target "inference" with type "test" omitted
# Unsupported target "ini" with type "test" omitted
# Unsupported target "ini_str" with type "test" omitted
# Unsupported target "issues" with type "test" omitted
# Unsupported target "json" with type "test" omitted
# Unsupported target "mp4" with type "test" omitted
# Unsupported target "multiline" with type "test" omitted
# Unsupported target "named_args" with type "test" omitted
# Unsupported target "overflow" with type "test" omitted
# Unsupported target "reborrow_fold" with type "test" omitted
# Unsupported target "test1" with type "test" omitted

View file

@ -42,7 +42,6 @@ cargo_build_script(
build_script_env = { build_script_env = {
}, },
crate_features = [ crate_features = [
"default",
"std", "std",
], ],
crate_root = "build.rs", crate_root = "build.rs",
@ -66,7 +65,6 @@ rust_library(
name = "num_traits", name = "num_traits",
srcs = glob(["**/*.rs"]), srcs = glob(["**/*.rs"]),
crate_features = [ crate_features = [
"default",
"std", "std",
], ],
crate_root = "src/lib.rs", crate_root = "src/lib.rs",

View file

@ -43,7 +43,6 @@ cargo_build_script(
}, },
crate_features = [ crate_features = [
"abi3", "abi3",
"abi3-py38",
"abi3-py39", "abi3-py39",
"default", "default",
"extension-module", "extension-module",
@ -93,7 +92,6 @@ rust_library(
srcs = glob(["**/*.rs"]), srcs = glob(["**/*.rs"]),
crate_features = [ crate_features = [
"abi3", "abi3",
"abi3-py38",
"abi3-py39", "abi3-py39",
"default", "default",
"extension-module", "extension-module",

View file

@ -44,7 +44,6 @@ cargo_build_script(
}, },
crate_features = [ crate_features = [
"abi3", "abi3",
"abi3-py38",
"abi3-py39", "abi3-py39",
"default", "default",
"resolve-config", "resolve-config",
@ -70,7 +69,6 @@ rust_library(
srcs = glob(["**/*.rs"]), srcs = glob(["**/*.rs"]),
crate_features = [ crate_features = [
"abi3", "abi3",
"abi3-py38",
"abi3-py39", "abi3-py39",
"default", "default",
"resolve-config", "resolve-config",

View file

@ -1,83 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT"
])
# Generated Targets
# buildifier: disable=out-of-order-load
# buildifier: disable=load-on-top
load(
"@rules_rust//cargo:cargo_build_script.bzl",
"cargo_build_script",
)
cargo_build_script(
name = "radium_build_script",
srcs = glob(["**/*.rs"]),
build_script_env = {
},
crate_features = [
],
crate_root = "build.rs",
data = glob(["**"]),
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.5.3",
visibility = ["//visibility:private"],
deps = [
],
)
rust_library(
name = "radium",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.5.3",
# buildifier: leave-alone
deps = [
":radium_build_script",
],
)

View file

@ -1,53 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT OR Apache-2.0"
])
# Generated Targets
rust_library(
name = "static_assertions",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2015",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "1.1.0",
# buildifier: leave-alone
deps = [
],
)

View file

@ -1,53 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT"
])
# Generated Targets
rust_library(
name = "tap",
srcs = glob(["**/*.rs"]),
crate_features = [
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2015",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "1.0.1",
# buildifier: leave-alone
deps = [
],
)

View file

@ -1,54 +0,0 @@
"""
@generated
cargo-raze crate build file.
DO NOT EDIT! Replaced on runs of cargo-raze
"""
# buildifier: disable=load
load("@bazel_skylib//lib:selects.bzl", "selects")
# buildifier: disable=load
load(
"@rules_rust//rust:rust.bzl",
"rust_binary",
"rust_library",
"rust_test",
)
package(default_visibility = [
# Public for visibility by "@raze__crate__version//" targets.
#
# Prefer access through "//cargo", which limits external
# visibility to explicit Cargo.toml dependencies.
"//visibility:public",
])
licenses([
"notice", # MIT from expression "MIT"
])
# Generated Targets
rust_library(
name = "wyz",
srcs = glob(["**/*.rs"]),
crate_features = [
"alloc",
],
crate_root = "src/lib.rs",
crate_type = "lib",
data = [],
edition = "2018",
rustc_flags = [
"--cap-lints=allow",
],
tags = [
"cargo-raze",
"manual",
],
version = "0.2.0",
# buildifier: leave-alone
deps = [
],
)

View file

@ -9,12 +9,45 @@ import "anki/generic.proto";
import "anki/cards.proto"; import "anki/cards.proto";
service StatsService { service StatsService {
rpc CardStats(cards.CardId) returns (generic.String); rpc CardStats(cards.CardId) returns (CardStatsResponse);
rpc Graphs(GraphsRequest) returns (GraphsResponse); rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences); rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty); rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
} }
message CardStatsResponse {
message StatsRevlogEntry {
int64 time = 1;
RevlogEntry.ReviewKind review_kind = 2;
uint32 button_chosen = 3;
// seconds
uint32 interval = 4;
// per mill
uint32 ease = 5;
float taken_secs = 6;
}
repeated StatsRevlogEntry revlog = 1;
int64 card_id = 2;
int64 note_id = 3;
string deck = 4;
// Unix timestamps
int64 added = 5;
generic.Int64 first_review = 6;
generic.Int64 latest_review = 7;
generic.Int64 due_date = 8;
generic.Int32 due_position = 9;
// days
uint32 interval = 10;
// per mill
uint32 ease = 11;
uint32 reviews = 12;
uint32 lapses = 13;
float average_secs = 14;
float total_secs = 15;
string card_type = 16;
string notetype = 17;
}
message GraphsRequest { message GraphsRequest {
string search = 1; string search = 1;
uint32 days = 2; uint32 days = 2;

View file

@ -62,7 +62,13 @@ SKIP_UNROLL_INPUT = {
} }
SKIP_UNROLL_OUTPUT = {"GetPreferences"} SKIP_UNROLL_OUTPUT = {"GetPreferences"}
SKIP_DECODE = {"Graphs", "GetGraphPreferences", "GetChangeNotetypeInfo", "CompleteTag"} SKIP_DECODE = {
"Graphs",
"GetGraphPreferences",
"GetChangeNotetypeInfo",
"CompleteTag",
"CardStats",
}
def python_type(field): def python_type(field):

View file

@ -24,6 +24,7 @@ SearchNode = search_pb2.SearchNode
Progress = collection_pb2.Progress Progress = collection_pb2.Progress
EmptyCardsReport = card_rendering_pb2.EmptyCardsReport EmptyCardsReport = card_rendering_pb2.EmptyCardsReport
GraphPreferences = stats_pb2.GraphPreferences GraphPreferences = stats_pb2.GraphPreferences
CardStats = stats_pb2.CardStatsResponse
Preferences = config_pb2.Preferences Preferences = config_pb2.Preferences
UndoStatus = collection_pb2.UndoStatus UndoStatus = collection_pb2.UndoStatus
OpChanges = collection_pb2.OpChanges OpChanges = collection_pb2.OpChanges
@ -807,23 +808,8 @@ class Collection(DeprecatedNamesMixin):
return CollectionStats(self) return CollectionStats(self)
def card_stats(self, card_id: CardId, include_revlog: bool) -> str: def card_stats_data(self, card_id: CardId) -> bytes:
import anki.stats as st return self._backend.card_stats(card_id)
if include_revlog:
revlog_style = "margin-top: 2em;"
else:
revlog_style = "display: none;"
style = f"""<style>
.revlog-learn {{ color: {st.colLearn} }}
.revlog-review {{ color: {st.colMature} }}
.revlog-relearn {{ color: {st.colRelearn} }}
.revlog-ease1 {{ color: {st.colRelearn} }}
table.review-log {{ {revlog_style} }}
</style>"""
return style + self._backend.card_stats(card_id)
def studied_today(self) -> str: def studied_today(self) -> str:
return self._backend.studied_today() return self._backend.studied_today()
@ -1149,9 +1135,17 @@ table.review-log {{ {revlog_style} }}
def _remNotes(self, ids: list[NoteId]) -> None: def _remNotes(self, ids: list[NoteId]) -> None:
pass pass
@deprecated(replaced_by=card_stats) @deprecated(replaced_by=card_stats_data)
def card_stats(self, card_id: CardId, include_revlog: bool) -> str:
from anki.stats import _legacy_card_stats
return _legacy_card_stats(self, card_id, include_revlog)
@deprecated(replaced_by=card_stats_data)
def cardStats(self, card: Card) -> str: def cardStats(self, card: Card) -> str:
return self.card_stats(card.id, include_revlog=False) from anki.stats import _legacy_card_stats
return _legacy_card_stats(self, card.id, False)
@deprecated(replaced_by=after_note_updates) @deprecated(replaced_by=after_note_updates)
def updateFieldCache(self, nids: list[NoteId]) -> None: def updateFieldCache(self, nids: list[NoteId]) -> None:

View file

@ -7,6 +7,7 @@ from __future__ import annotations
import datetime import datetime
import json import json
import random
import time import time
from typing import Sequence from typing import Sequence
@ -14,17 +15,36 @@ import anki.cards
import anki.collection import anki.collection
from anki.consts import * from anki.consts import *
from anki.lang import FormatTimeSpan from anki.lang import FormatTimeSpan
from anki.utils import ids2str from anki.utils import base62, ids2str
# Card stats # Card stats
########################################################################## ##########################################################################
_legacy_nightmode = False
def _legacy_card_stats(
col: anki.collection.Collection, card_id: anki.cards.CardId, include_revlog: bool
) -> str:
"A quick hack to preserve compatibility with the old HTML string API."
random_id = f"cardinfo-{base62(random.randint(0, 2 ** 64 - 1))}"
return f"""
<div id="{random_id}"></div>
<script src="js/vendor/bootstrap.bundle.min.js"></script>
<link href="pages/card-info-base.css" rel="stylesheet" />
<link href="pages/card-info.css" rel="stylesheet" />
<script src="pages/card-info.js"></script>
<script>
if ({1 if _legacy_nightmode else 0}) {{
document.documentElement.className = "night-mode";
}}
anki.cardInfo(document.getElementById('{random_id}'), {card_id}, {include_revlog});
</script>
"""
class CardStats: class CardStats:
""" """Do not use - this class is only left around for backwards compatibility."""
New code should just call collection.card_stats() directly - this class
is only left around for backwards compatibility.
"""
def __init__(self, col: anki.collection.Collection, card: anki.cards.Card) -> None: def __init__(self, col: anki.collection.Collection, card: anki.cards.Card) -> None:
if col: if col:
@ -33,7 +53,7 @@ class CardStats:
self.txt = "" self.txt = ""
def report(self, include_revlog: bool = False) -> str: def report(self, include_revlog: bool = False) -> str:
return self.col.card_stats(self.card.id, include_revlog=include_revlog) return _legacy_card_stats(self.col, self.card.id, include_revlog)
# legacy # legacy

View file

@ -4,6 +4,7 @@
import os import os
import tempfile import tempfile
from anki.collection import CardStats
from tests.shared import getEmptyCol from tests.shared import getEmptyCol
@ -14,12 +15,15 @@ def test_stats():
col.addNote(note) col.addNote(note)
c = note.cards()[0] c = note.cards()[0]
# card stats # card stats
assert col.card_stats(c.id, include_revlog=True) card_stats = CardStats()
card_stats.ParseFromString(col.card_stats_data(c.id))
assert card_stats.note_id == note.id
col.reset() col.reset()
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
col.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert col.card_stats(c.id, include_revlog=True) card_stats.ParseFromString(col.card_stats_data(c.id))
assert len(card_stats.revlog) == 2
def test_graphs_empty(): def test_graphs_empty():

View file

@ -4,36 +4,53 @@
from __future__ import annotations from __future__ import annotations
import aqt import aqt
from anki.cards import Card from anki.cards import Card, CardId
from anki.stats import CardStats
from aqt.qt import * from aqt.qt import *
from aqt.utils import disable_help_button, qconnect, restoreGeom, saveGeom from aqt.utils import (
addCloseShortcut,
disable_help_button,
qconnect,
restoreGeom,
saveGeom,
)
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView
class CardInfoDialog(QDialog): class CardInfoDialog(QDialog):
TITLE = "browser card info"
GEOMETRY_KEY = "revlog"
silentlyClose = True silentlyClose = True
def __init__(self, parent: QWidget, mw: aqt.AnkiQt, card: Card) -> None: def __init__(self, parent: QWidget, mw: aqt.AnkiQt, card: Card) -> None:
super().__init__(parent) super().__init__(parent)
disable_help_button(self) self.mw = mw
cs = CardStats(mw.col, card) self._setup_ui(card.id)
info = cs.report(include_revlog=True)
l = QVBoxLayout()
l.setContentsMargins(0, 0, 0, 0)
w = AnkiWebView(title="browser card info")
l.addWidget(w)
w.stdHtml(info + "<p>", context=self)
bb = QDialogButtonBox(QDialogButtonBox.Close)
l.addWidget(bb)
qconnect(bb.rejected, self.reject)
self.setLayout(l)
self.setWindowModality(Qt.WindowModal)
self.resize(500, 400)
restoreGeom(self, "revlog")
self.show() self.show()
def _setup_ui(self, card_id: CardId) -> None:
self.setWindowModality(Qt.ApplicationModal)
self.mw.garbage_collect_on_dialog_finish(self)
disable_help_button(self)
restoreGeom(self, self.GEOMETRY_KEY)
addCloseShortcut(self)
self.web = AnkiWebView(title=self.TITLE)
self.web.setVisible(False)
self.web.load_ts_page("card-info")
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.web)
buttons = QDialogButtonBox(QDialogButtonBox.Close)
buttons.setContentsMargins(10, 0, 10, 10)
layout.addWidget(buttons)
qconnect(buttons.rejected, self.reject)
self.setLayout(layout)
self.web.eval(
f"anki.cardInfo(document.getElementById('main'), {card_id}, true);"
)
def reject(self) -> None: def reject(self) -> None:
saveGeom(self, "revlog") self.web = None
saveGeom(self, self.GEOMETRY_KEY)
return QDialog.reject(self) return QDialog.reject(self)

View file

@ -5,6 +5,7 @@ _pages = [
"congrats", "congrats",
"deck-options", "deck-options",
"change-notetype", "change-notetype",
"card-info",
] ]
[copy_files_into_group( [copy_files_into_group(

View file

@ -20,6 +20,7 @@ from waitress.server import create_server
import aqt import aqt
from anki import hooks from anki import hooks
from anki.cards import CardId
from anki.collection import GraphPreferences, OpChanges from anki.collection import GraphPreferences, OpChanges
from anki.decks import UpdateDeckConfigs from anki.decks import UpdateDeckConfigs
from anki.models import NotetypeNames from anki.models import NotetypeNames
@ -411,6 +412,10 @@ def complete_tag() -> bytes:
return aqt.mw.col.tags.complete_tag(request.data) return aqt.mw.col.tags.complete_tag(request.data)
def card_stats() -> bytes:
return aqt.mw.col.card_stats_data(CardId(int(request.data)))
# these require a collection # these require a collection
post_handlers = { post_handlers = {
"graphData": graph_data, "graphData": graph_data,
@ -426,6 +431,7 @@ post_handlers = {
"i18nResources": i18n_resources, "i18nResources": i18n_resources,
"congratsInfo": congrats_info, "congratsInfo": congrats_info,
"completeTag": complete_tag, "completeTag": complete_tag,
"cardStats": card_stats,
} }

View file

@ -246,6 +246,7 @@ QTabWidget {{ background-color: {}; }}
s.colCram = self.color(colors.SUSPENDED_BG) s.colCram = self.color(colors.SUSPENDED_BG)
s.colSusp = self.color(colors.SUSPENDED_BG) s.colSusp = self.color(colors.SUSPENDED_BG)
s.colMature = self.color(colors.REVIEW_COUNT) s.colMature = self.color(colors.REVIEW_COUNT)
s._legacy_nightmode = self._night_mode_preference
theme_manager = ThemeManager() theme_manager = ThemeManager()

View file

@ -45,7 +45,6 @@ _anki_compile_data = glob([
]) + [ ]) + [
"Cargo.toml", # prevents a warning about num_enum "Cargo.toml", # prevents a warning about num_enum
"//:buildinfo.txt", "//:buildinfo.txt",
"templates/.empty", # required for askama
] ]
_anki_features = [ _anki_features = [
@ -73,7 +72,6 @@ rust_library(
deps = [ deps = [
":build_script", ":build_script",
"//rslib/cargo:ammonia", "//rslib/cargo:ammonia",
"//rslib/cargo:askama",
"//rslib/cargo:blake3", "//rslib/cargo:blake3",
"//rslib/cargo:bytes", "//rslib/cargo:bytes",
"//rslib/cargo:chrono", "//rslib/cargo:chrono",

View file

@ -30,7 +30,6 @@ anki_i18n = { path="i18n" }
nom = "7.0.0" nom = "7.0.0"
proc-macro-nested = "0.1.7" proc-macro-nested = "0.1.7"
slog-term = "2.8.0" slog-term = "2.8.0"
askama = "0.10.5"
blake3 = "1.0.0" blake3 = "1.0.0"
bytes = "1.1.0" bytes = "1.1.0"
chrono = "0.4.19" chrono = "0.4.19"

View file

@ -21,15 +21,6 @@ alias(
], ],
) )
alias(
name = "askama",
actual = "@raze__askama__0_10_5//:askama",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "async_trait", name = "async_trait",
actual = "@raze__async_trait__0_1_51//:async_trait", actual = "@raze__async_trait__0_1_51//:async_trait",

View file

@ -3,12 +3,11 @@
use super::Backend; use super::Backend;
pub(super) use crate::backend_proto::stats_service::Service as StatsService; pub(super) use crate::backend_proto::stats_service::Service as StatsService;
use crate::{backend_proto as pb, prelude::*}; use crate::{backend_proto as pb, prelude::*, revlog::RevlogReviewKind};
impl StatsService for Backend { impl StatsService for Backend {
fn card_stats(&self, input: pb::CardId) -> Result<pb::String> { fn card_stats(&self, input: pb::CardId) -> Result<pb::CardStatsResponse> {
self.with_col(|col| col.card_stats(input.into())) self.with_col(|col| col.card_stats(input.into()))
.map(Into::into)
} }
fn graphs(&self, input: pb::GraphsRequest) -> Result<pb::GraphsResponse> { fn graphs(&self, input: pb::GraphsRequest) -> Result<pb::GraphsResponse> {
@ -24,3 +23,15 @@ impl StatsService for Backend {
.map(Into::into) .map(Into::into)
} }
} }
impl From<RevlogReviewKind> for i32 {
fn from(kind: RevlogReviewKind) -> Self {
(match kind {
RevlogReviewKind::Learning => pb::revlog_entry::ReviewKind::Learning,
RevlogReviewKind::Review => pb::revlog_entry::ReviewKind::Review,
RevlogReviewKind::Relearning => pb::revlog_entry::ReviewKind::Relearning,
RevlogReviewKind::Filtered => pb::revlog_entry::ReviewKind::Filtered,
RevlogReviewKind::Manual => pb::revlog_entry::ReviewKind::Manual,
}) as i32
}
}

View file

@ -1,68 +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
use askama::Template; use crate::{backend_proto as pb, card::CardQueue, prelude::*, revlog::RevlogEntry};
use chrono::prelude::*;
use crate::{
card::CardQueue,
i18n::I18n,
prelude::*,
revlog::{RevlogEntry, RevlogReviewKind},
scheduler::timespan::time_span,
};
struct CardStats {
added: TimestampSecs,
first_review: Option<TimestampSecs>,
latest_review: Option<TimestampSecs>,
due: Due,
interval_secs: u32,
ease: u32,
reviews: u32,
lapses: u32,
average_secs: f32,
total_secs: f32,
card_type: String,
notetype: String,
deck: String,
nid: NoteId,
cid: CardId,
revlog: Vec<RevlogEntry>,
}
#[derive(Template)]
#[template(path = "../src/stats/card_stats.html")]
struct CardStatsTemplate {
stats: Vec<(String, String)>,
revlog: Vec<RevlogText>,
revlog_titles: RevlogText,
}
enum Due {
Time(TimestampSecs),
Position(i32),
Unknown,
}
struct RevlogText {
time: String,
kind: String,
kind_class: String,
rating: String,
rating_class: String,
interval: String,
ease: String,
taken_secs: String,
}
impl Collection { impl Collection {
pub fn card_stats(&mut self, cid: CardId) -> Result<String> { pub fn card_stats(&mut self, cid: CardId) -> Result<pb::CardStatsResponse> {
let stats = self.gather_card_stats(cid)?;
Ok(self.card_stats_to_string(stats))
}
fn gather_card_stats(&mut self, cid: CardId) -> Result<CardStats> {
let card = self.storage.get_card(cid)?.ok_or(AnkiError::NotFound)?; let card = self.storage.get_card(cid)?.ok_or(AnkiError::NotFound)?;
let note = self let note = self
.storage .storage
@ -75,184 +17,92 @@ impl Collection {
.storage .storage
.get_deck(card.deck_id)? .get_deck(card.deck_id)?
.ok_or(AnkiError::NotFound)?; .ok_or(AnkiError::NotFound)?;
let revlog = self.storage.get_revlog_entries_for_card(card.id)?; let revlog = self.storage.get_revlog_entries_for_card(card.id)?;
let average_secs;
let total_secs;
let normal_answer_count = revlog.iter().filter(|r| r.button_chosen > 0).count();
if normal_answer_count == 0 {
average_secs = 0.0;
total_secs = 0.0;
} else {
total_secs = revlog
.iter()
.map(|e| (e.taken_millis as f32) / 1000.0)
.sum();
average_secs = total_secs / normal_answer_count as f32;
}
let due = if card.original_due != 0 { let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
card.original_due let (due_date, due_position) = self.due_date_and_position_strings(&card)?;
} else {
card.due Ok(pb::CardStatsResponse {
}; card_id: card.id.into(),
let due = match card.queue { note_id: card.note_id.into(),
CardQueue::New => Due::Position(due), deck: deck.human_name(),
CardQueue::Learn => Due::Time(TimestampSecs::now()), added: card.id.as_secs().0,
CardQueue::Review | CardQueue::DayLearn => Due::Time({ first_review: revlog.first().map(|entry| pb::generic::Int64 {
let days_remaining = due - (self.timing_today()?.days_elapsed as i32); val: entry.id.as_secs().0,
let mut due = TimestampSecs::now();
due.0 += (days_remaining as i64) * 86_400;
due
}), }),
_ => Due::Unknown, latest_review: revlog.last().map(|entry| pb::generic::Int64 {
}; val: entry.id.as_secs().0,
}),
Ok(CardStats { due_date,
added: card.id.as_secs(), due_position,
first_review: revlog.first().map(|e| e.id.as_secs()), interval: card.interval,
latest_review: revlog.last().map(|e| e.id.as_secs()), ease: card.ease_factor as u32,
due,
interval_secs: card.interval * 86_400,
ease: (card.ease_factor as u32) / 10,
reviews: card.reps, reviews: card.reps,
lapses: card.lapses, lapses: card.lapses,
average_secs, average_secs,
total_secs, total_secs,
card_type: nt.get_template(card.template_idx)?.name.clone(), card_type: nt.get_template(card.template_idx)?.name.clone(),
notetype: nt.name.clone(), notetype: nt.name.clone(),
deck: deck.human_name(), revlog: revlog
nid: card.note_id, .iter()
cid: card.id, .rev()
revlog: revlog.into_iter().map(Into::into).collect(), .map(|entry| stats_revlog_entry(entry))
.collect(),
}) })
} }
fn card_stats_to_string(&mut self, cs: CardStats) -> String { fn due_date_and_position_strings(
let tr = &self.tr; &mut self,
card: &Card,
let mut stats = vec![(tr.card_stats_added().into(), cs.added.date_string())]; ) -> Result<(Option<pb::generic::Int64>, Option<pb::generic::Int32>)> {
if let Some(first) = cs.first_review { let due = if card.original_due != 0 {
stats.push((tr.card_stats_first_review().into(), first.date_string())) card.original_due
}
if let Some(last) = cs.latest_review {
stats.push((tr.card_stats_latest_review().into(), last.date_string()))
}
match cs.due {
Due::Time(secs) => {
stats.push((tr.statistics_due_date().into(), secs.date_string()));
}
Due::Position(pos) => {
stats.push((tr.card_stats_new_card_position().into(), pos.to_string()));
}
Due::Unknown => {}
};
if cs.interval_secs > 0 {
stats.push((
tr.card_stats_interval().into(),
time_span(cs.interval_secs as f32, tr, true),
));
}
if cs.ease > 0 {
stats.push((tr.card_stats_ease().into(), format!("{}%", cs.ease)));
}
stats.push((tr.card_stats_review_count().into(), cs.reviews.to_string()));
stats.push((tr.card_stats_lapse_count().into(), cs.lapses.to_string()));
if cs.total_secs > 0.0 {
stats.push((
tr.card_stats_average_time().into(),
time_span(cs.average_secs, tr, true),
));
stats.push((
tr.card_stats_total_time().into(),
time_span(cs.total_secs, tr, true),
));
}
stats.push((tr.card_stats_card_template().into(), cs.card_type));
stats.push((tr.card_stats_note_type().into(), cs.notetype));
stats.push((tr.card_stats_deck_name().into(), cs.deck));
stats.push((tr.card_stats_card_id().into(), cs.cid.0.to_string()));
stats.push((tr.card_stats_note_id().into(), cs.nid.0.to_string()));
let revlog = cs
.revlog
.into_iter()
.rev()
.map(|e| revlog_to_text(e, tr))
.collect();
let revlog_titles = RevlogText {
time: tr.card_stats_review_log_date().into(),
kind: tr.card_stats_review_log_type().into(),
kind_class: "".to_string(),
rating: tr.card_stats_review_log_rating().into(),
interval: tr.card_stats_interval().into(),
ease: tr.card_stats_ease().into(),
rating_class: "".to_string(),
taken_secs: tr.card_stats_review_log_time_taken().into(),
};
CardStatsTemplate {
stats,
revlog,
revlog_titles,
}
.render()
.unwrap()
}
}
fn revlog_to_text(e: RevlogEntry, tr: &I18n) -> RevlogText {
let dt = Local.timestamp(e.id.as_secs().0, 0);
let time = dt.format("<b>%Y-%m-%d</b> @ %H:%M").to_string();
let kind = match e.review_kind {
RevlogReviewKind::Learning => tr.card_stats_review_log_type_learn().into(),
RevlogReviewKind::Review => tr.card_stats_review_log_type_review().into(),
RevlogReviewKind::Relearning => tr.card_stats_review_log_type_relearn().into(),
RevlogReviewKind::Filtered => tr.card_stats_review_log_type_filtered().into(),
RevlogReviewKind::Manual => tr.card_stats_review_log_type_manual().into(),
};
let kind_class = match e.review_kind {
RevlogReviewKind::Learning => String::from("revlog-learn"),
RevlogReviewKind::Review => String::from("revlog-review"),
RevlogReviewKind::Relearning => String::from("revlog-relearn"),
RevlogReviewKind::Filtered => String::from("revlog-filtered"),
RevlogReviewKind::Manual => String::from("revlog-manual"),
};
let rating = e.button_chosen.to_string();
let interval = if e.interval == 0 {
String::from("")
} else { } else {
let interval_secs = e.interval_secs(); card.due
time_span(interval_secs as f32, tr, true)
}; };
let ease = if e.ease_factor > 0 { Ok(match card.queue {
format!("{}%", e.ease_factor / 10) CardQueue::New => (None, Some(pb::generic::Int32 { val: due })),
} else { CardQueue::Learn => (
"".to_string() Some(pb::generic::Int64 {
}; val: TimestampSecs::now().0,
let rating_class = if e.button_chosen == 1 { }),
String::from("revlog-ease1") None,
} else { ),
"".to_string() CardQueue::Review | CardQueue::DayLearn => (
}; {
let taken_secs = tr let days_remaining = due - (self.timing_today()?.days_elapsed as i32);
.statistics_seconds_taken((e.taken_millis / 1000) as i32) let mut due = TimestampSecs::now();
.into(); due.0 += (days_remaining as i64) * 86_400;
Some(pb::generic::Int64 { val: due.0 })
},
None,
),
_ => (None, None),
})
}
}
RevlogText { fn average_and_total_secs_strings(revlog: &[RevlogEntry]) -> (f32, f32) {
time, let normal_answer_count = revlog.iter().filter(|r| r.button_chosen > 0).count();
kind, let total_secs: f32 = revlog
kind_class, .iter()
rating, .map(|entry| (entry.taken_millis as f32) / 1000.0)
rating_class, .sum();
interval, if normal_answer_count == 0 || total_secs == 0.0 {
ease, (0.0, 0.0)
taken_secs, } else {
(total_secs / normal_answer_count as f32, total_secs)
}
}
fn stats_revlog_entry(entry: &RevlogEntry) -> pb::card_stats_response::StatsRevlogEntry {
pb::card_stats_response::StatsRevlogEntry {
time: entry.id.as_secs().0,
review_kind: entry.review_kind.into(),
button_chosen: entry.button_chosen as u32,
interval: entry.interval_secs(),
ease: entry.ease_factor,
taken_secs: entry.taken_millis as f32 / 1000.,
} }
} }

View file

@ -1,34 +0,0 @@
<table class="card-stats" width="100%">
{% for row in stats %}
<tr>
<td align="left" style="padding-right: 3px;">
<b>{{ row.0 }}</b>
</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% if !revlog.is_empty() %}
<table class="review-log" width="100%">
<tr>
<th>{{ revlog_titles.time }}</th>
<th align="right">{{ revlog_titles.kind }}</th>
<th align="center">{{ revlog_titles.rating }}</th>
<th>{{ revlog_titles.interval }}</th>
<th align="right">{{ revlog_titles.ease }}</th>
<th align="right">{{ revlog_titles.taken_secs }}</th>
</tr>
{% for entry in revlog %}
<tr>
<td>{{ entry.time|safe }}</td>
<td align="right" class="{{ entry.kind_class }}">{{ entry.kind }}</td>
<td align="center" class="{{ entry.rating_class }}">{{ entry.rating }}</td>
<td>{{ entry.interval }}</td>
<td align="right">{{ entry.ease }}</td>
<td align="right">{{ entry.taken_secs }}</td>
</tr>
{% endfor %}
</table>
{% endif %}

View file

69
ts/card-info/BUILD.bazel Normal file
View file

@ -0,0 +1,69 @@
load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:compile_sass.bzl", "compile_sass")
load("//ts:typescript.bzl", "typescript")
compile_sass(
srcs = ["card-info-base.scss"],
group = "base_css",
visibility = ["//visibility:public"],
deps = [
"//sass:base_lib",
"//sass:scrollbar_lib",
"//sass/bootstrap",
],
)
compile_svelte()
typescript(
name = "index",
deps = [
":svelte",
"//ts/components",
"//ts/lib",
"@npm//@fluent",
],
)
esbuild(
name = "card-info",
args = {
"globalName": "anki",
"loader": {".svg": "text"},
},
entry_point = "index.ts",
output_css = "card-info.css",
visibility = ["//visibility:public"],
deps = [
":base_css",
":index",
":svelte",
"//ts/components",
"//ts/lib",
"@npm//protobufjs",
],
)
exports_files(["card-info.html"])
# Tests
################
prettier_test()
eslint_test()
svelte_check(
name = "svelte_check",
srcs = glob([
"*.ts",
"*.svelte",
]) + [
"//sass:button_mixins_lib",
"//sass/bootstrap",
"//ts/components",
],
)

View file

@ -0,0 +1,110 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr2 from "../lib/ftl";
import { Stats, unwrapOptionalNumber } from "../lib/proto";
import { Timestamp, timeSpan, DAY } from "../lib/time";
import Revlog from "./Revlog.svelte";
export let stats: Stats.CardStatsResponse;
function dateString(timestamp: number): string {
return new Timestamp(timestamp).dateString();
}
interface StatsRow {
label: string;
value: string | number;
}
const statsRows: StatsRow[] = [];
statsRows.push({ label: tr2.cardStatsAdded(), value: dateString(stats.added) });
const firstReview = unwrapOptionalNumber(stats.firstReview);
if (firstReview !== undefined) {
statsRows.push({
label: tr2.cardStatsFirstReview(),
value: dateString(firstReview),
});
}
const latestReview = unwrapOptionalNumber(stats.latestReview);
if (latestReview !== undefined) {
statsRows.push({
label: tr2.cardStatsLatestReview(),
value: dateString(latestReview),
});
}
const dueDate = unwrapOptionalNumber(stats.dueDate);
if (dueDate !== undefined) {
statsRows.push({ label: tr2.statisticsDueDate(), value: dateString(dueDate) });
}
const duePosition = unwrapOptionalNumber(stats.duePosition);
if (duePosition !== undefined) {
statsRows.push({
label: tr2.cardStatsNewCardPosition(),
value: dateString(duePosition),
});
}
if (stats.interval) {
statsRows.push({
label: tr2.cardStatsInterval(),
value: timeSpan(stats.interval * DAY),
});
}
if (stats.ease) {
statsRows.push({ label: tr2.cardStatsEase(), value: `${stats.ease / 10}%` });
}
statsRows.push({ label: tr2.cardStatsReviewCount(), value: stats.reviews });
statsRows.push({ label: tr2.cardStatsLapseCount(), value: stats.lapses });
if (stats.totalSecs) {
statsRows.push({
label: tr2.cardStatsAverageTime(),
value: timeSpan(stats.averageSecs),
});
statsRows.push({
label: tr2.cardStatsTotalTime(),
value: timeSpan(stats.totalSecs),
});
}
statsRows.push({ label: tr2.cardStatsCardTemplate(), value: stats.cardType });
statsRows.push({ label: tr2.cardStatsNoteType(), value: stats.notetype });
statsRows.push({ label: tr2.cardStatsDeckName(), value: stats.deck });
statsRows.push({ label: tr2.cardStatsCardId(), value: stats.cardId });
statsRows.push({ label: tr2.cardStatsNoteId(), value: stats.noteId });
</script>
<div class="container">
<div>
<table class="stats-table">
{#each statsRows as row, _index}
<tr>
<th style="text-align:start">{row.label}</th>
<td>{row.value}</td>
</tr>
{/each}
</table>
<Revlog {stats} />
</div>
</div>
<style>
.container {
display: flex;
justify-content: center;
white-space: nowrap;
}
.stats-table {
width: 100%;
text-align: start;
}
</style>

141
ts/card-info/Revlog.svelte Normal file
View file

@ -0,0 +1,141 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr2 from "../lib/ftl";
import { Stats } from "../lib/proto";
import { Timestamp, timeSpan } from "../lib/time";
export let stats: Stats.CardStatsResponse;
type IStatsRevlogEntry = Stats.CardStatsResponse.IStatsRevlogEntry;
function reviewKindClass(entry: IStatsRevlogEntry): string {
switch (entry.reviewKind) {
case Stats.RevlogEntry.ReviewKind.LEARNING:
return "revlog-learn";
case Stats.RevlogEntry.ReviewKind.REVIEW:
return "revlog-review";
case Stats.RevlogEntry.ReviewKind.RELEARNING:
return "revlog-relearn";
}
return "";
}
function reviewKindLabel(entry: IStatsRevlogEntry): string {
switch (entry.reviewKind) {
case Stats.RevlogEntry.ReviewKind.LEARNING:
return tr2.cardStatsReviewLogTypeLearn();
case Stats.RevlogEntry.ReviewKind.REVIEW:
return tr2.cardStatsReviewLogTypeReview();
case Stats.RevlogEntry.ReviewKind.RELEARNING:
return tr2.cardStatsReviewLogTypeRelearn();
case Stats.RevlogEntry.ReviewKind.FILTERED:
return tr2.cardStatsReviewLogTypeFiltered();
case Stats.RevlogEntry.ReviewKind.MANUAL:
return tr2.cardStatsReviewLogTypeManual();
}
}
function ratingClass(entry: IStatsRevlogEntry): string {
if (entry.buttonChosen === 1) {
return "revlog-ease1";
}
return "";
}
interface RevlogRow {
date: string;
time: string;
reviewKind: string;
reviewKindClass: string;
rating: number;
ratingClass: string;
interval: string;
ease: string;
takenSecs: string;
}
function revlogRowFromEntry(entry: IStatsRevlogEntry): RevlogRow {
const timestamp = new Timestamp(entry.time!);
return {
date: timestamp.dateString(),
time: timestamp.timeString(),
reviewKind: reviewKindLabel(entry),
reviewKindClass: reviewKindClass(entry),
rating: entry.buttonChosen!,
ratingClass: ratingClass(entry),
interval: timeSpan(entry.interval!),
ease: entry.ease ? `${entry.ease / 10}%` : "",
takenSecs: timeSpan(entry.takenSecs!, true),
};
}
const revlogRows: RevlogRow[] = stats.revlog.map((entry) =>
revlogRowFromEntry(entry)
);
</script>
{#if stats.revlog.length}
<div class="revlog-container">
<table class="revlog-table">
<tr>
<th>{tr2.cardStatsReviewLogDate()}</th>
<th>{tr2.cardStatsReviewLogType()}</th>
<th>{tr2.cardStatsReviewLogRating()}</th>
<th>{tr2.cardStatsInterval()}</th>
<th>{tr2.cardStatsEase()}</th>
<th>{tr2.cardStatsReviewLogTimeTaken()}</th>
</tr>
{#each revlogRows as row, _index}
<tr>
<td class="left"><b>{row.date}</b> @ {row.time}</td>
<td class="center {row.reviewKindClass}">
{row.reviewKind}
</td>
<td class="center {row.ratingClass}">{row.rating}</td>
<td class="center">{row.interval}</td>
<td class="center">{row.ease}</td>
<td class="right">{row.takenSecs}</td>
</tr>
{/each}
</table>
</div>
{/if}
<style>
.left {
text-align: start;
}
.right {
text-align: end;
}
.center {
text-align: center;
}
.revlog-container {
margin: 4em -2em 0 -2em;
}
.revlog-table {
width: 100%;
border-spacing: 2em 0em;
}
.revlog-learn {
color: var(--new-count);
}
.revlog-review {
color: var(--review-count);
}
.revlog-relearn,
.revlog-ease1 {
color: var(--learn-count);
}
</style>

View file

@ -0,0 +1,6 @@
@use "core";
@use "scrollbar";
.night-mode {
@include scrollbar.night-mode;
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="card-info-base.css" rel="stylesheet" />
<link href="card-info.css" rel="stylesheet" />
<script src="../js/vendor/bootstrap.bundle.min.js"></script>
<script src="card-info.js"></script>
</head>
<body>
<div id="main"></div>
</body>
</html>

33
ts/card-info/index.ts Normal file
View file

@ -0,0 +1,33 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { getCardStats } from "./lib";
import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode";
import CardInfo from "./CardInfo.svelte";
export async function cardInfo(
target: HTMLDivElement,
cardId: number,
includeRevlog: boolean
): Promise<CardInfo> {
checkNightMode();
const [stats] = await Promise.all([
getCardStats(cardId),
setupI18n({
modules: [
ModuleName.CARD_STATS,
ModuleName.SCHEDULING,
ModuleName.STATISTICS,
],
}),
]);
if (!includeRevlog) {
stats.revlog = [];
}
return new CardInfo({
target,
props: { stats },
});
}

11
ts/card-info/lib.ts Normal file
View file

@ -0,0 +1,11 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { Stats } from "../lib/proto";
import { postRequest } from "../lib/postrequest";
export async function getCardStats(cardId: number): Promise<Stats.CardStatsResponse> {
return Stats.CardStatsResponse.decode(
await postRequest("/_anki/cardStats", JSON.stringify(cardId))
);
}

View file

@ -0,0 +1,5 @@
{
"extends": "../tsconfig.json",
"include": ["*"],
"references": [{ "path": "../lib" }]
}

View file

@ -5,9 +5,28 @@ import { anki } from "./backend_proto";
import Cards = anki.cards; import Cards = anki.cards;
import DeckConfig = anki.deckconfig; import DeckConfig = anki.deckconfig;
import Generic = anki.generic;
import Notetypes = anki.notetypes; import Notetypes = anki.notetypes;
import Scheduler = anki.scheduler; import Scheduler = anki.scheduler;
import Stats = anki.stats; import Stats = anki.stats;
import Tags = anki.tags; import Tags = anki.tags;
export { Stats, Cards, DeckConfig, Notetypes, Scheduler, Tags }; export { Stats, Cards, DeckConfig, Notetypes, Scheduler, Tags };
export function unwrapOptionalNumber(
msg:
| Generic.IInt64
| Generic.IUInt32
| Generic.IInt32
| Generic.OptionalInt32
| Generic.OptionalUInt32
| null
| undefined
): number | undefined {
if (msg && msg !== null) {
if (msg.val !== null) {
return msg.val;
}
}
return undefined;
}

View file

@ -177,3 +177,27 @@ export function dayLabel(daysStart: number, daysEnd: number): string {
} }
} }
} }
/** Helper for converting Unix timestamps to date strings. */
export class Timestamp {
private date: Date;
constructor(seconds: number) {
this.date = new Date(seconds * 1000);
}
/** YYYY-MM-DD */
dateString(): string {
const year = this.date.getFullYear();
const month = ("0" + (this.date.getMonth() + 1)).slice(-2);
const date = ("0" + this.date.getDate()).slice(-2);
return `${year}-${month}-${date}`;
}
/** HH:MM */
timeString(): string {
const hours = ("0" + this.date.getHours()).slice(-2);
const minutes = ("0" + this.date.getMinutes()).slice(-2);
return `${hours}:${minutes}`;
}
}