Skip to content

Commit

Permalink
deck config prototype work in progress
Browse files Browse the repository at this point in the history
Still in the early stages, and not hooked up yet.
  • Loading branch information
dae committed Apr 14, 2021
1 parent e6306bb commit 7f738c1
Show file tree
Hide file tree
Showing 32 changed files with 1,133 additions and 27 deletions.
4 changes: 4 additions & 0 deletions pylib/anki/decks.py
Expand Up @@ -22,6 +22,7 @@
DeckNameId = _pb.DeckNameId
FilteredDeckConfig = _pb.Deck.Filtered
DeckCollapseScope = _pb.SetDeckCollapsedIn.Scope
DeckConfigForUpdate = _pb.DeckConfigForUpdate

# legacy code may pass this in as the type argument to .id()
defaultDeck = 0
Expand Down Expand Up @@ -324,6 +325,9 @@ def renameForDragAndDrop(
# Deck configurations
#############################################################

def get_deck_config_for_update(self, deck_id: DeckId) -> DeckConfigForUpdate:
return self.col._backend.get_deck_config_for_update(deck_id)

def all_config(self) -> List[DeckConfigDict]:
"A list of all deck config."
return list(from_json_bytes(self.col._backend.all_deck_config_legacy()))
Expand Down
36 changes: 14 additions & 22 deletions qt/aqt/data/web/pages/BUILD.bazel
@@ -1,32 +1,24 @@
load("//ts:copy.bzl", "copy_files_into_group")

copy_files_into_group(
name = "graphs_page",
srcs = [
"graphs-base.css",
"graphs.css",
"graphs.html",
"graphs.js",
],
package = "//ts/graphs",
)
_pages = [
"graphs",
"congrats",
"deckconfig",
]

copy_files_into_group(
name = "congrats_page",
[copy_files_into_group(
name = name + "_page",
srcs = [
"congrats-base.css",
"congrats.css",
"congrats.html",
"congrats.js",
name + "-base.css",
name + ".css",
name + ".html",
name + ".js",
],
package = "//ts/congrats",
)
package = "//ts/" + name,
) for name in _pages]

filegroup(
name = "pages",
srcs = [
"congrats_page",
"graphs_page",
],
srcs = [name + "_page" for name in _pages],
visibility = ["//qt:__subpackages__"],
)
8 changes: 8 additions & 0 deletions qt/aqt/mediasrv.py
Expand Up @@ -274,10 +274,18 @@ def i18n_resources() -> bytes:
return aqt.mw.col.i18n_resources(modules=args["modules"])


def deck_config_for_update() -> bytes:
args = from_json_bytes(request.data)
return aqt.mw.col.decks.get_deck_config_for_update(
deck_id=args["deckId"]
).SerializeToString()


post_handlers = {
"graphData": graph_data,
"graphPreferences": graph_preferences,
"setGraphPreferences": set_graph_preferences,
"deckConfigForUpdate": deck_config_for_update,
# pylint: disable=unnecessary-lambda
"i18nResources": i18n_resources,
"congratsInfo": congrats_info,
Expand Down
26 changes: 26 additions & 0 deletions rslib/backend.proto
Expand Up @@ -228,6 +228,8 @@ service DeckConfigService {
rpc GetDeckConfigLegacy(DeckConfigId) returns (Json);
rpc NewDeckConfigLegacy(Empty) returns (Json);
rpc RemoveDeckConfig(DeckConfigId) returns (Empty);
rpc GetDeckConfigForUpdate(DeckId) returns (DeckConfigForUpdate);
rpc UpdateDeckConfig(UpdateDeckConfigIn) returns (OpChanges);
}

service TagsService {
Expand Down Expand Up @@ -893,6 +895,30 @@ message AddOrUpdateDeckConfigLegacyIn {
bool preserve_usn_and_mtime = 2;
}

message DeckConfigForUpdate {
message ConfigWithExtra {
DeckConfig config = 1;
uint32 use_count = 2;
}
message CurrentDeck {
string name = 1;
int64 config_id = 2;
uint32 parent_new_limit = 3;
uint32 parent_review_limit = 4;
}

repeated ConfigWithExtra all_config = 1;
CurrentDeck current_deck = 2;
DeckConfig defaults = 3;
}

message UpdateDeckConfigIn {
int64 target_deck_id = 2;
DeckConfig desired_config = 3;
repeated int64 removed_config_ids = 4;
bool apply_to_children = 5;
}

message SetTagCollapsedIn {
string name = 1;
bool collapsed = 2;
Expand Down
8 changes: 8 additions & 0 deletions rslib/src/backend/deckconfig.rs
Expand Up @@ -61,6 +61,14 @@ impl DeckConfigService for Backend {
self.with_col(|col| col.transact_no_undo(|col| col.remove_deck_config(input.into())))
.map(Into::into)
}

fn get_deck_config_for_update(&self, input: pb::DeckId) -> Result<pb::DeckConfigForUpdate> {
self.with_col(|col| col.get_deck_config_for_update(input.into()))
}

fn update_deck_config(&self, _input: pb::UpdateDeckConfigIn) -> Result<pb::OpChanges> {
todo!();
}
}

impl From<DeckConf> for pb::DeckConfig {
Expand Down
5 changes: 3 additions & 2 deletions rslib/src/deckconf/mod.rs
@@ -1,6 +1,9 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

mod schema11;
mod update;

use crate::{
collection::Collection,
define_newtype,
Expand All @@ -18,8 +21,6 @@ pub use schema11::{DeckConfSchema11, NewCardOrderSchema11};
/// Old deck config and cards table store 250% as 2500.
pub(crate) const INITIAL_EASE_FACTOR_THOUSANDS: u16 = (INITIAL_EASE_FACTOR * 1000.0) as u16;

mod schema11;

define_newtype!(DeckConfId, i64);

#[derive(Debug, PartialEq, Clone)]
Expand Down
82 changes: 82 additions & 0 deletions rslib/src/deckconf/update.rs
@@ -0,0 +1,82 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

use std::collections::{HashMap, HashSet};

use pb::deck_config_for_update::{ConfigWithExtra, CurrentDeck};

use crate::{backend_proto as pb, prelude::*};

impl Collection {
/// Information required for the deck options screen.
pub fn get_deck_config_for_update(&mut self, deck: DeckId) -> Result<pb::DeckConfigForUpdate> {
Ok(pb::DeckConfigForUpdate {
all_config: self.get_deck_config_with_extra_for_update()?,
current_deck: Some(self.get_current_deck_for_update(deck)?),
defaults: Some(DeckConf::default().into()),
})
}
}

impl Collection {
fn get_deck_config_with_extra_for_update(&self) -> Result<Vec<ConfigWithExtra>> {
// grab the config and sort it
let mut config = self.storage.all_deck_config()?;
config.sort_unstable_by(|a, b| a.name.cmp(&b.name));

// combine with use counts
let counts = self.get_deck_config_use_counts()?;
Ok(config
.into_iter()
.map(|config| ConfigWithExtra {
use_count: counts.get(&config.id).cloned().unwrap_or_default() as u32,
config: Some(config.into()),
})
.collect())
}

fn get_deck_config_use_counts(&self) -> Result<HashMap<DeckConfId, usize>> {
let mut counts = HashMap::new();
for deck in self.storage.get_all_decks()? {
if let Ok(normal) = deck.normal() {
*counts.entry(DeckConfId(normal.config_id)).or_default() += 1;
}
}

Ok(counts)
}

fn get_current_deck_for_update(&mut self, deck: DeckId) -> Result<CurrentDeck> {
let deck = self.get_deck(deck)?.ok_or(AnkiError::NotFound)?;

let mut parent_new_limit = u32::MAX;
let mut parent_review_limit = u32::MAX;
for config_id in self.parent_config_ids(&deck)? {
if let Some(config) = self.storage.get_deck_config(config_id)? {
parent_new_limit = parent_new_limit.min(config.inner.new_per_day);
parent_review_limit = parent_review_limit.min(config.inner.reviews_per_day);
}
}

Ok(CurrentDeck {
name: deck.name.clone(),
config_id: deck.normal()?.config_id,
parent_new_limit,
parent_review_limit,
})
}

/// Deck configs used by parent decks.
fn parent_config_ids(&self, deck: &Deck) -> Result<HashSet<DeckConfId>> {
Ok(self
.storage
.parent_decks(deck)?
.iter()
.filter_map(|deck| {
deck.normal()
.ok()
.map(|normal| DeckConfId(normal.config_id))
})
.collect())
}
}
116 changes: 116 additions & 0 deletions ts/deckconfig/BUILD.bazel
@@ -0,0 +1,116 @@
load("@npm//@bazel/typescript:index.bzl", "ts_library")
load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:vendor.bzl", "copy_bootstrap_icons")
load("//ts:compile_sass.bzl", "compile_sass")

compile_sass(
srcs = ["deckconfig-base.scss"],
group = "base_css",
visibility = ["//visibility:public"],
deps = [
"//ts/bootstrap:scss",
"//ts/sass:base_lib",
"//ts/sass:scrollbar_lib",
],
)

svelte_files = glob(["*.svelte"])

svelte_names = [f.replace(".svelte", "") for f in svelte_files]

compile_svelte(
name = "svelte",
srcs = svelte_files,
)

copy_bootstrap_icons(
name = "bootstrap-icons",
icons = [
"arrow-counterclockwise.svg",
],
)

ts_library(
name = "index",
srcs = ["index.ts"],
deps = [
"DeckConfigPage",
"lib",
"//ts/lib",
"@npm//svelte2tsx",
],
)

ts_library(
name = "lib",
srcs = [
"icons.ts",
"lib.ts",
"steps.ts",
],
module_name = "deckconfig",
deps = [
"//ts:image_module_support",
"//ts/lib",
"//ts/lib:backend_proto",
],
)

esbuild(
name = "deckconfig",
srcs = [
"//ts:protobuf-shim.js",
],
args = [
"--global-name=anki",
"--inject:$(location //ts:protobuf-shim.js)",
"--resolve-extensions=.mjs,.js",
"--log-level=warning",
"--loader:.svg=text",
],
entry_point = "index.ts",
external = [
"protobufjs/light",
],
output_css = True,
visibility = ["//visibility:public"],
deps = [
"index",
"//ts/lib",
"//ts/lib:backend_proto",
":bootstrap-icons",
"@npm//bootstrap",
":base_css",
] + svelte_names,
)

exports_files(["deckconfig.html"])

# Tests
################

prettier_test(
name = "format_check",
srcs = glob([
"*.ts",
"*.svelte",
]),
)

eslint_test(
name = "eslint",
srcs = glob([
"*.ts",
]),
)

svelte_check(
name = "svelte_check",
srcs = glob([
"*.ts",
"*.svelte",
]),
)
19 changes: 19 additions & 0 deletions ts/deckconfig/CheckBox.svelte
@@ -0,0 +1,19 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import ConfigEntry from "./ConfigEntry.svelte";
export let label: string;
export let subLabel: string;
export let value: boolean;
export let defaultValue: boolean;
</script>

<style>
</style>

<ConfigEntry {label} bind:value {defaultValue}>
<label> <input type="checkbox" bind:checked={value} /> {subLabel} </label>
</ConfigEntry>

0 comments on commit 7f738c1

Please sign in to comment.