add schema change prompt to removal, tweak return struct

This commit is contained in:
Damien Elmes 2021-04-18 11:56:41 +10:00
parent 55e1176653
commit 76eb119870
13 changed files with 130 additions and 41 deletions

View File

@ -24,3 +24,4 @@ undo-forget-card = Forget Card
undo-set-flag = Set Flag
undo-build-filtered-deck = Build Deck
undo-expand-collapse = Expand/Collapse
undo-deck-config = Study Options

View File

@ -22,7 +22,7 @@ DeckTreeNode = _pb.DeckTreeNode
DeckNameId = _pb.DeckNameId
FilteredDeckConfig = _pb.Deck.Filtered
DeckCollapseScope = _pb.SetDeckCollapsedIn.Scope
DeckConfigForUpdate = _pb.DeckConfigForUpdate
DeckConfigsForUpdate = _pb.DeckConfigsForUpdate
# legacy code may pass this in as the type argument to .id()
defaultDeck = 0
@ -325,8 +325,8 @@ class DeckManager:
# 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 get_deck_configs_for_update(self, deck_id: DeckId) -> DeckConfigsForUpdate:
return self.col._backend.get_deck_configs_for_update(deck_id)
def all_config(self) -> List[DeckConfigDict]:
"A list of all deck config."

View File

@ -276,9 +276,9 @@ def i18n_resources() -> bytes:
return aqt.mw.col.i18n_resources(modules=args["modules"])
def deck_config_for_update() -> bytes:
def deck_configs_for_update() -> bytes:
args = from_json_bytes(request.data)
return aqt.mw.col.decks.get_deck_config_for_update(
return aqt.mw.col.decks.get_deck_configs_for_update(
deck_id=args["deckId"]
).SerializeToString()
@ -287,7 +287,7 @@ post_handlers = {
"graphData": graph_data,
"graphPreferences": graph_preferences,
"setGraphPreferences": set_graph_preferences,
"deckConfigForUpdate": deck_config_for_update,
"deckConfigsForUpdate": deck_configs_for_update,
# pylint: disable=unnecessary-lambda
"i18nResources": i18n_resources,
"congratsInfo": congrats_info,

View File

@ -228,8 +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);
rpc GetDeckConfigsForUpdate(DeckId) returns (DeckConfigsForUpdate);
rpc UpdateDeckConfigs(UpdateDeckConfigsIn) returns (OpChanges);
}
service TagsService {
@ -895,7 +895,7 @@ message AddOrUpdateDeckConfigLegacyIn {
bool preserve_usn_and_mtime = 2;
}
message DeckConfigForUpdate {
message DeckConfigsForUpdate {
message ConfigWithExtra {
DeckConfig config = 1;
uint32 use_count = 2;
@ -909,13 +909,16 @@ message DeckConfigForUpdate {
repeated ConfigWithExtra all_config = 1;
CurrentDeck current_deck = 2;
DeckConfig defaults = 3;
bool schema_modified = 4;
}
message UpdateDeckConfigIn {
int64 target_deck_id = 2;
DeckConfig desired_config = 3;
repeated int64 removed_config_ids = 4;
bool apply_to_children = 5;
message UpdateDeckConfigsIn {
int64 target_deck_id = 1;
/// Unchanged, non-selected configs can be omitted. Deck will
/// be set to whichever entry comes last.
repeated DeckConfig configs = 2;
repeated int64 removed_config_ids = 3;
bool apply_to_children = 4;
}
message SetTagCollapsedIn {

View File

@ -4,7 +4,7 @@
use super::Backend;
use crate::{
backend_proto as pb,
deckconf::{DeckConf, DeckConfSchema11},
deckconf::{DeckConf, DeckConfSchema11, UpdateDeckConfigsIn},
prelude::*,
};
pub(super) use pb::deckconfig_service::Service as DeckConfigService;
@ -62,12 +62,13 @@ impl DeckConfigService for Backend {
.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 get_deck_configs_for_update(&self, input: pb::DeckId) -> Result<pb::DeckConfigsForUpdate> {
self.with_col(|col| col.get_deck_configs_for_update(input.into()))
}
fn update_deck_config(&self, _input: pb::UpdateDeckConfigIn) -> Result<pb::OpChanges> {
todo!();
fn update_deck_configs(&self, input: pb::UpdateDeckConfigsIn) -> Result<pb::OpChanges> {
self.with_col(|col| col.update_deck_configs(input.into()))
.map(Into::into)
}
}
@ -82,3 +83,26 @@ impl From<DeckConf> for pb::DeckConfig {
}
}
}
impl From<pb::UpdateDeckConfigsIn> for UpdateDeckConfigsIn {
fn from(c: pb::UpdateDeckConfigsIn) -> Self {
UpdateDeckConfigsIn {
target_deck_id: c.target_deck_id.into(),
configs: c.configs.into_iter().map(Into::into).collect(),
removed_config_ids: c.removed_config_ids.into_iter().map(Into::into).collect(),
apply_to_children: c.apply_to_children,
}
}
}
impl From<pb::DeckConfig> for DeckConf {
fn from(c: pb::DeckConfig) -> Self {
DeckConf {
id: c.id.into(),
name: c.name,
mtime_secs: c.mtime_secs.into(),
usn: c.usn.into(),
inner: c.config.unwrap_or_default(),
}
}
}

View File

@ -4,6 +4,18 @@
mod schema11;
mod update;
pub use {
crate::backend_proto::{
deck_config::config::{LeechAction, NewCardOrder, ReviewCardOrder, ReviewMix},
deck_config::Config as DeckConfigInner,
},
schema11::{DeckConfSchema11, NewCardOrderSchema11},
update::UpdateDeckConfigsIn,
};
/// 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;
use crate::{
collection::Collection,
define_newtype,
@ -13,14 +25,6 @@ use crate::{
types::Usn,
};
pub use crate::backend_proto::{
deck_config::config::{LeechAction, NewCardOrder, ReviewCardOrder, ReviewMix},
deck_config::Config as DeckConfigInner,
};
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;
define_newtype!(DeckConfId, i64);
#[derive(Debug, PartialEq, Clone)]

View File

@ -3,17 +3,36 @@
use std::collections::{HashMap, HashSet};
use pb::deck_config_for_update::{ConfigWithExtra, CurrentDeck};
use pb::deck_configs_for_update::{ConfigWithExtra, CurrentDeck};
use crate::{backend_proto as pb, prelude::*};
pub struct UpdateDeckConfigsIn {
pub target_deck_id: DeckId,
/// Deck will be set to last provided deck config.
pub configs: Vec<DeckConf>,
pub removed_config_ids: Vec<DeckId>,
pub apply_to_children: bool,
}
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 {
pub fn get_deck_configs_for_update(
&mut self,
deck: DeckId,
) -> Result<pb::DeckConfigsForUpdate> {
Ok(pb::DeckConfigsForUpdate {
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()),
schema_modified: self.storage.schema_modified()?,
})
}
/// Information required for the deck options screen.
pub fn update_deck_configs(&mut self, input: UpdateDeckConfigsIn) -> Result<OpOutput<()>> {
self.transact(Op::UpdateDeckConfig, |col| {
col.update_deck_configs_inner(input)
})
}
}
@ -73,4 +92,12 @@ impl Collection {
})
.collect())
}
fn update_deck_configs_inner(&mut self, input: UpdateDeckConfigsIn) -> Result<()> {
if input.configs.is_empty() {
return Err(AnkiError::invalid_input("config not provided"));
}
todo!();
}
}

View File

@ -31,6 +31,7 @@ pub enum Op {
UnburyUnsuspend,
UpdateCard,
UpdateDeck,
UpdateDeckConfig,
UpdateNote,
UpdatePreferences,
UpdateTag,
@ -70,6 +71,7 @@ impl Op {
Op::EmptyFilteredDeck => tr.studying_empty(),
Op::ExpandCollapse => tr.undo_expand_collapse(),
Op::SetCurrentDeck => tr.browsing_change_deck(),
Op::UpdateDeckConfig => tr.undo_deck_config(),
}
.into()
}

View File

@ -297,6 +297,13 @@ impl SqliteStorage {
Ok(())
}
pub(crate) fn schema_modified(&self) -> Result<bool> {
self.db
.prepare_cached("select scm > ls from col")?
.query_row(NO_PARAMS, |r| r.get(0))
.map_err(Into::into)
}
pub(crate) fn get_schema_mtime(&self) -> Result<TimestampMillis> {
self.db
.prepare_cached("select scm from col")?

View File

@ -15,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
return `${entry.name} (${count})`;
}
function myblur(this: HTMLSelectElement) {
function blur(this: HTMLSelectElement) {
state.setCurrentIndex(parseInt(this.value));
}
</script>
@ -60,7 +60,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div>{tr.actionsOptionsFor({ val: state.currentDeck.name })}</div>
<!-- svelte-ignore a11y-no-onchange -->
<select class="form-select" on:change={myblur}>
<select class="form-select" on:change={blur}>
{#each $configList as entry}
<option value={entry.idx} selected={entry.current}>
{configLabel(entry)}

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
-->
<script lang="ts">
// import * as tr from "anki/i18n";
import * as tr from "anki/i18n";
import { textInputModal } from "./textInputModal";
import type { DeckConfigState } from "./lib";
@ -34,8 +34,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
function removeConfig(): void {
// show pop-up after dropdown has gone away
setTimeout(() => {
if (confirm("Are you sure?")) {
if (state.defaultConfigSelected()) {
alert(tr.schedulingTheDefaultConfigurationCantBeRemoved());
return;
}
// fixme: move tr.qt_misc schema mod msg into core
// fixme: include name of deck in msg
const msg = state.removalWilLForceFullSync()
? "This will require a one-way sync. Are you sure?"
: "Are you sure?";
if (confirm(msg)) {
try {
state.removeCurrentConfig();
} catch (err) {

View File

@ -92,7 +92,7 @@ const exampleData = {
function startingState(): DeckConfigState {
return new DeckConfigState(
pb.BackendProto.DeckConfigForUpdate.fromObject(exampleData)
pb.BackendProto.DeckConfigsForUpdate.fromObject(exampleData)
);
}

View File

@ -13,9 +13,9 @@ import * as tr from "anki/i18n";
export async function getDeckConfigInfo(
deckId: number
): Promise<pb.BackendProto.DeckConfigForUpdate> {
return pb.BackendProto.DeckConfigForUpdate.decode(
await postRequest("/_anki/deckConfigForUpdate", JSON.stringify({ deckId }))
): Promise<pb.BackendProto.DeckConfigsForUpdate> {
return pb.BackendProto.DeckConfigsForUpdate.decode(
await postRequest("/_anki/deckConfigsForUpdate", JSON.stringify({ deckId }))
);
}
@ -44,7 +44,7 @@ export class DeckConfigState {
readonly currentConfig: Writable<ConfigInner>;
readonly configList: Readable<ConfigListEntry[]>;
readonly parentLimits: Readable<ParentLimits>;
readonly currentDeck: pb.BackendProto.DeckConfigForUpdate.CurrentDeck;
readonly currentDeck: pb.BackendProto.DeckConfigsForUpdate.CurrentDeck;
readonly defaults: ConfigInner;
private configs: ConfigWithCount[];
@ -52,9 +52,10 @@ export class DeckConfigState {
private configListSetter!: (val: ConfigListEntry[]) => void;
private parentLimitsSetter!: (val: ParentLimits) => void;
private removedConfigs: DeckConfigId[] = [];
private schemaModified: boolean;
constructor(data: pb.BackendProto.DeckConfigForUpdate) {
this.currentDeck = data.currentDeck as pb.BackendProto.DeckConfigForUpdate.CurrentDeck;
constructor(data: pb.BackendProto.DeckConfigsForUpdate) {
this.currentDeck = data.currentDeck as pb.BackendProto.DeckConfigsForUpdate.CurrentDeck;
this.defaults = data.defaults!.config! as ConfigInner;
this.configs = data.allConfig.map((config) => {
return {
@ -79,6 +80,7 @@ export class DeckConfigState {
this.parentLimitsSetter = set;
return;
});
this.schemaModified = data.schemaModified;
// create a temporary subscription to force our setters to be set immediately,
// so unit tests don't get stale results
@ -126,6 +128,14 @@ export class DeckConfigState {
this.updateConfigList();
}
removalWilLForceFullSync(): boolean {
return !this.schemaModified && this.configs[this.selectedIdx].config.id !== 0;
}
defaultConfigSelected(): boolean {
return this.configs[this.selectedIdx].config.id === 1;
}
/// Will throw if the default deck is selected.
removeCurrentConfig(): void {
const currentId = this.configs[this.selectedIdx].config.id;
@ -135,6 +145,7 @@ export class DeckConfigState {
if (currentId !== 0) {
this.removedConfigs.push(currentId);
this.schemaModified = true;
}
this.configs.splice(this.selectedIdx, 1);
this.selectedIdx = Math.max(0, this.selectedIdx - 1);