mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Fix 'Discard changes' dialog appearing even when no changes are made (#3495)
* Fix 'Discard changes' dialog appearing even when no changes are made https://forums.ankiweb.net/t/anki-24-10-beta/49989/166 * Fix geometry of deck options window not being saved evt.accept() does not seem to trigger reject().
This commit is contained in:
parent
8f4cab6a1a
commit
694a5089a9
3 changed files with 23 additions and 33 deletions
|
@ -69,8 +69,7 @@ class DeckOptionsDialog(QDialog):
|
||||||
|
|
||||||
def closeEvent(self, evt: QCloseEvent) -> None:
|
def closeEvent(self, evt: QCloseEvent) -> None:
|
||||||
if self._close_event_has_cleaned_up:
|
if self._close_event_has_cleaned_up:
|
||||||
evt.accept()
|
return super().closeEvent(evt)
|
||||||
return
|
|
||||||
evt.ignore()
|
evt.ignore()
|
||||||
self.check_pending_changes()
|
self.check_pending_changes()
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
globalThis.anki.deckOptionsPendingChanges = async (): Promise<void> => {
|
globalThis.anki.deckOptionsPendingChanges = async (): Promise<void> => {
|
||||||
await commitEditing();
|
await commitEditing();
|
||||||
if (bridgeCommandsAvailable()) {
|
if (bridgeCommandsAvailable()) {
|
||||||
if (data.state.isModified()) {
|
if (await data.state.isModified()) {
|
||||||
bridgeCommand("confirmDiscardChanges");
|
bridgeCommand("confirmDiscardChanges");
|
||||||
} else {
|
} else {
|
||||||
bridgeCommand("_close");
|
bridgeCommand("_close");
|
||||||
|
@ -28,6 +28,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
globalThis.$deckOptions = new Promise((resolve, _reject) => {
|
globalThis.$deckOptions = new Promise((resolve, _reject) => {
|
||||||
resolve(page);
|
resolve(page);
|
||||||
});
|
});
|
||||||
|
data.state.resolveOriginalConfigs();
|
||||||
if (bridgeCommandsAvailable()) {
|
if (bridgeCommandsAvailable()) {
|
||||||
bridgeCommand("deckOptionsReady");
|
bridgeCommand("deckOptionsReady");
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ import type {
|
||||||
import { DeckConfig, DeckConfig_Config, DeckConfigsForUpdate_CurrentDeck_Limits } from "@generated/anki/deck_config_pb";
|
import { DeckConfig, DeckConfig_Config, DeckConfigsForUpdate_CurrentDeck_Limits } from "@generated/anki/deck_config_pb";
|
||||||
import { updateDeckConfigs } from "@generated/backend";
|
import { updateDeckConfigs } from "@generated/backend";
|
||||||
import { localeCompare } from "@tslib/i18n";
|
import { localeCompare } from "@tslib/i18n";
|
||||||
import { cloneDeep, isEqual, pickBy } from "lodash-es";
|
import { promiseWithResolver } from "@tslib/promise";
|
||||||
|
import { cloneDeep, isEqual } from "lodash-es";
|
||||||
import { tick } from "svelte";
|
import { tick } from "svelte";
|
||||||
import type { Readable, Writable } from "svelte/store";
|
import type { Readable, Writable } from "svelte/store";
|
||||||
import { get, readable, writable } from "svelte/store";
|
import { get, readable, writable } from "svelte/store";
|
||||||
|
@ -33,7 +34,7 @@ export interface ConfigListEntry {
|
||||||
current: boolean;
|
current: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Configs =
|
type AllConfigs =
|
||||||
& Required<
|
& Required<
|
||||||
Pick<
|
Pick<
|
||||||
PlainMessage<UpdateDeckConfigsRequest>,
|
PlainMessage<UpdateDeckConfigsRequest>,
|
||||||
|
@ -48,10 +49,6 @@ type Configs =
|
||||||
>
|
>
|
||||||
& { currentConfig: DeckConfig_Config };
|
& { currentConfig: DeckConfig_Config };
|
||||||
|
|
||||||
type DeepPartial<T extends object, K extends keyof T> = {
|
|
||||||
[key in keyof T]: key extends K ? Partial<T[key]> : T[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
export class DeckOptionsState {
|
export class DeckOptionsState {
|
||||||
readonly currentConfig: Writable<DeckConfig_Config>;
|
readonly currentConfig: Writable<DeckConfig_Config>;
|
||||||
readonly currentAuxData: Writable<Record<string, unknown>>;
|
readonly currentAuxData: Writable<Record<string, unknown>>;
|
||||||
|
@ -68,7 +65,8 @@ export class DeckOptionsState {
|
||||||
readonly daysSinceLastOptimization: Writable<number>;
|
readonly daysSinceLastOptimization: Writable<number>;
|
||||||
readonly currentPresetName: Writable<string>;
|
readonly currentPresetName: Writable<string>;
|
||||||
/** Used to detect if there are any pending changes */
|
/** Used to detect if there are any pending changes */
|
||||||
readonly originalConfigs: Configs;
|
readonly originalConfigsPromise: Promise<AllConfigs>;
|
||||||
|
readonly originalConfigsResolve: (value: AllConfigs) => void;
|
||||||
|
|
||||||
private targetDeckId: DeckOptionsId;
|
private targetDeckId: DeckOptionsId;
|
||||||
private configs: ConfigWithCount[];
|
private configs: ConfigWithCount[];
|
||||||
|
@ -124,16 +122,9 @@ export class DeckOptionsState {
|
||||||
this.currentConfig.subscribe((val) => this.onCurrentConfigChanged(val));
|
this.currentConfig.subscribe((val) => this.onCurrentConfigChanged(val));
|
||||||
this.currentAuxData.subscribe((val) => this.onCurrentAuxDataChanged(val));
|
this.currentAuxData.subscribe((val) => this.onCurrentAuxDataChanged(val));
|
||||||
|
|
||||||
this.originalConfigs = cloneDeep<Configs>({
|
// Must be resolved after all components are mounted, as some components
|
||||||
configs: this.configs.map(c => c.config!),
|
// may modify the config during their initialization.
|
||||||
cardStateCustomizer: data.cardStateCustomizer,
|
[this.originalConfigsPromise, this.originalConfigsResolve] = promiseWithResolver<AllConfigs>();
|
||||||
limits: get(this.deckLimits),
|
|
||||||
newCardsIgnoreReviewLimit: data.newCardsIgnoreReviewLimit,
|
|
||||||
applyAllParentLimits: data.applyAllParentLimits,
|
|
||||||
fsrs: data.fsrs,
|
|
||||||
fsrsReschedule: get(this.fsrsReschedule),
|
|
||||||
currentConfig: get(this.currentConfig),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentIndex(index: number): void {
|
setCurrentIndex(index: number): void {
|
||||||
|
@ -326,24 +317,28 @@ export class DeckOptionsState {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
isModified(): boolean {
|
private getAllConfigs(): AllConfigs {
|
||||||
const original: DeepPartial<Configs, "limits"> = {
|
return cloneDeep({
|
||||||
...this.originalConfigs,
|
|
||||||
limits: omitUndefined(this.originalConfigs.limits),
|
|
||||||
};
|
|
||||||
const current: typeof original = {
|
|
||||||
configs: this.configs.map(c => c.config),
|
configs: this.configs.map(c => c.config),
|
||||||
cardStateCustomizer: get(this.cardStateCustomizer),
|
cardStateCustomizer: get(this.cardStateCustomizer),
|
||||||
limits: omitUndefined(get(this.deckLimits)),
|
limits: get(this.deckLimits),
|
||||||
newCardsIgnoreReviewLimit: get(this.newCardsIgnoreReviewLimit),
|
newCardsIgnoreReviewLimit: get(this.newCardsIgnoreReviewLimit),
|
||||||
applyAllParentLimits: get(this.applyAllParentLimits),
|
applyAllParentLimits: get(this.applyAllParentLimits),
|
||||||
fsrs: get(this.fsrs),
|
fsrs: get(this.fsrs),
|
||||||
fsrsReschedule: get(this.fsrsReschedule),
|
fsrsReschedule: get(this.fsrsReschedule),
|
||||||
currentConfig: get(this.currentConfig),
|
currentConfig: get(this.currentConfig),
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async isModified(): Promise<boolean> {
|
||||||
|
const original = await this.originalConfigsPromise;
|
||||||
|
const current = this.getAllConfigs();
|
||||||
return !isEqual(original, current);
|
return !isEqual(original, current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveOriginalConfigs(): void {
|
||||||
|
this.originalConfigsResolve(this.getAllConfigs());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bytesToObject(bytes: Uint8Array): Record<string, unknown> {
|
function bytesToObject(bytes: Uint8Array): Record<string, unknown> {
|
||||||
|
@ -413,11 +408,6 @@ export class ValueTab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a copy of the given object with the properties whose values are 'undefined' omitted */
|
|
||||||
function omitUndefined<T extends object>(obj: T): Partial<T> {
|
|
||||||
return pickBy(obj, val => val !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Ensure blur handler has fired so changes get committed. */
|
/** Ensure blur handler has fired so changes get committed. */
|
||||||
export async function commitEditing(): Promise<void> {
|
export async function commitEditing(): Promise<void> {
|
||||||
if (document.activeElement instanceof HTMLElement) {
|
if (document.activeElement instanceof HTMLElement) {
|
||||||
|
|
Loading…
Reference in a new issue