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-set-flag = Set Flag
undo-build-filtered-deck = Build Deck undo-build-filtered-deck = Build Deck
undo-expand-collapse = Expand/Collapse undo-expand-collapse = Expand/Collapse
undo-deck-config = Study Options

View file

@ -22,7 +22,7 @@ DeckTreeNode = _pb.DeckTreeNode
DeckNameId = _pb.DeckNameId DeckNameId = _pb.DeckNameId
FilteredDeckConfig = _pb.Deck.Filtered FilteredDeckConfig = _pb.Deck.Filtered
DeckCollapseScope = _pb.SetDeckCollapsedIn.Scope DeckCollapseScope = _pb.SetDeckCollapsedIn.Scope
DeckConfigForUpdate = _pb.DeckConfigForUpdate DeckConfigsForUpdate = _pb.DeckConfigsForUpdate
# legacy code may pass this in as the type argument to .id() # legacy code may pass this in as the type argument to .id()
defaultDeck = 0 defaultDeck = 0
@ -325,8 +325,8 @@ class DeckManager:
# Deck configurations # Deck configurations
############################################################# #############################################################
def get_deck_config_for_update(self, deck_id: DeckId) -> DeckConfigForUpdate: def get_deck_configs_for_update(self, deck_id: DeckId) -> DeckConfigsForUpdate:
return self.col._backend.get_deck_config_for_update(deck_id) return self.col._backend.get_deck_configs_for_update(deck_id)
def all_config(self) -> List[DeckConfigDict]: def all_config(self) -> List[DeckConfigDict]:
"A list of all deck config." "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"]) 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) 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"] deck_id=args["deckId"]
).SerializeToString() ).SerializeToString()
@ -287,7 +287,7 @@ post_handlers = {
"graphData": graph_data, "graphData": graph_data,
"graphPreferences": graph_preferences, "graphPreferences": graph_preferences,
"setGraphPreferences": set_graph_preferences, "setGraphPreferences": set_graph_preferences,
"deckConfigForUpdate": deck_config_for_update, "deckConfigsForUpdate": deck_configs_for_update,
# pylint: disable=unnecessary-lambda # pylint: disable=unnecessary-lambda
"i18nResources": i18n_resources, "i18nResources": i18n_resources,
"congratsInfo": congrats_info, "congratsInfo": congrats_info,

View file

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

View file

@ -4,7 +4,7 @@
use super::Backend; use super::Backend;
use crate::{ use crate::{
backend_proto as pb, backend_proto as pb,
deckconf::{DeckConf, DeckConfSchema11}, deckconf::{DeckConf, DeckConfSchema11, UpdateDeckConfigsIn},
prelude::*, prelude::*,
}; };
pub(super) use pb::deckconfig_service::Service as DeckConfigService; pub(super) use pb::deckconfig_service::Service as DeckConfigService;
@ -62,12 +62,13 @@ impl DeckConfigService for Backend {
.map(Into::into) .map(Into::into)
} }
fn get_deck_config_for_update(&self, input: pb::DeckId) -> Result<pb::DeckConfigForUpdate> { fn get_deck_configs_for_update(&self, input: pb::DeckId) -> Result<pb::DeckConfigsForUpdate> {
self.with_col(|col| col.get_deck_config_for_update(input.into())) self.with_col(|col| col.get_deck_configs_for_update(input.into()))
} }
fn update_deck_config(&self, _input: pb::UpdateDeckConfigIn) -> Result<pb::OpChanges> { fn update_deck_configs(&self, input: pb::UpdateDeckConfigsIn) -> Result<pb::OpChanges> {
todo!(); 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 schema11;
mod update; 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::{ use crate::{
collection::Collection, collection::Collection,
define_newtype, define_newtype,
@ -13,14 +25,6 @@ use crate::{
types::Usn, 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); define_newtype!(DeckConfId, i64);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]

View file

@ -3,17 +3,36 @@
use std::collections::{HashMap, HashSet}; 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::*}; 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 { impl Collection {
/// Information required for the deck options screen. /// Information required for the deck options screen.
pub fn get_deck_config_for_update(&mut self, deck: DeckId) -> Result<pb::DeckConfigForUpdate> { pub fn get_deck_configs_for_update(
Ok(pb::DeckConfigForUpdate { &mut self,
deck: DeckId,
) -> Result<pb::DeckConfigsForUpdate> {
Ok(pb::DeckConfigsForUpdate {
all_config: self.get_deck_config_with_extra_for_update()?, all_config: self.get_deck_config_with_extra_for_update()?,
current_deck: Some(self.get_current_deck_for_update(deck)?), current_deck: Some(self.get_current_deck_for_update(deck)?),
defaults: Some(DeckConf::default().into()), 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()) .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, UnburyUnsuspend,
UpdateCard, UpdateCard,
UpdateDeck, UpdateDeck,
UpdateDeckConfig,
UpdateNote, UpdateNote,
UpdatePreferences, UpdatePreferences,
UpdateTag, UpdateTag,
@ -70,6 +71,7 @@ impl Op {
Op::EmptyFilteredDeck => tr.studying_empty(), Op::EmptyFilteredDeck => tr.studying_empty(),
Op::ExpandCollapse => tr.undo_expand_collapse(), Op::ExpandCollapse => tr.undo_expand_collapse(),
Op::SetCurrentDeck => tr.browsing_change_deck(), Op::SetCurrentDeck => tr.browsing_change_deck(),
Op::UpdateDeckConfig => tr.undo_deck_config(),
} }
.into() .into()
} }

View file

@ -297,6 +297,13 @@ impl SqliteStorage {
Ok(()) 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> { pub(crate) fn get_schema_mtime(&self) -> Result<TimestampMillis> {
self.db self.db
.prepare_cached("select scm from col")? .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})`; return `${entry.name} (${count})`;
} }
function myblur(this: HTMLSelectElement) { function blur(this: HTMLSelectElement) {
state.setCurrentIndex(parseInt(this.value)); state.setCurrentIndex(parseInt(this.value));
} }
</script> </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> <div>{tr.actionsOptionsFor({ val: state.currentDeck.name })}</div>
<!-- svelte-ignore a11y-no-onchange --> <!-- svelte-ignore a11y-no-onchange -->
<select class="form-select" on:change={myblur}> <select class="form-select" on:change={blur}>
{#each $configList as entry} {#each $configList as entry}
<option value={entry.idx} selected={entry.current}> <option value={entry.idx} selected={entry.current}>
{configLabel(entry)} {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 License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
// import * as tr from "anki/i18n"; import * as tr from "anki/i18n";
import { textInputModal } from "./textInputModal"; import { textInputModal } from "./textInputModal";
import type { DeckConfigState } from "./lib"; 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 { function removeConfig(): void {
// show pop-up after dropdown has gone away
setTimeout(() => { 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 { try {
state.removeCurrentConfig(); state.removeCurrentConfig();
} catch (err) { } catch (err) {

View file

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