diff --git a/proto/anki/config.proto b/proto/anki/config.proto index ea115f0fc81..354229789ee 100644 --- a/proto/anki/config.proto +++ b/proto/anki/config.proto @@ -66,6 +66,12 @@ message ConfigKey { } } +enum ExperimentalFeatureFlag { + TEST_FLAG = 0; + SVELTE_EDITOR = 1; + SVELTE_REVIEWER = 2; +} + message GetConfigBoolRequest { ConfigKey.Bool key = 1; } diff --git a/pylib/anki/config.py b/pylib/anki/config.py index 775557c848f..c72df83e79d 100644 --- a/pylib/anki/config.py +++ b/pylib/anki/config.py @@ -32,11 +32,14 @@ from anki.utils import from_json_bytes, to_json_bytes Config = config_pb2.ConfigKey +ExperimentFlag = config_pb2.ExperimentalFeatureFlag class ConfigManager: def __init__(self, col: anki.collection.Collection): self.col = col.weakref() + # Saved when the collection is loaded to prevent changes before restart. + self._experiments = self._get_experiments_dirty() def get_immutable(self, key: str) -> Any: try: @@ -44,6 +47,16 @@ def get_immutable(self, key: str) -> Any: except NotFoundError as exc: raise KeyError from exc + def experiment_enabled(self, key: ExperimentFlag.ValueType) -> bool: + return self._experiments.get(str(key), False) + + def _get_experiments_dirty(self) -> dict[str, bool]: + """This fetches the experiments in the state that they are saved in the database. + This should not be used to check if an experiment is enabled because this will update immediately without a restart. + + Use "experiment_enabled" to fetch an active experiment instead.""" + return self.get_immutable("experimentalFeatures") + def set(self, key: str, val: Any) -> None: self.col._backend.set_config_json_no_undo( key=key, diff --git a/qt/aqt/main.py b/qt/aqt/main.py index d25d5a79cb7..eecda929978 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -27,6 +27,7 @@ from anki._legacy import deprecated from anki.buildinfo import version as version_str from anki.collection import Collection, Config, GithubRelease, OpChanges, UndoStatus +from anki.config import ExperimentFlag from anki.decks import DeckDict, DeckId from anki.hooks import runHook from anki.notes import NoteId @@ -527,6 +528,8 @@ def _onsuccess(synced: bool) -> None: onsuccess() if not self.safeMode: self.maybe_check_for_addon_updates(self.setup_auto_update) + if self.col.conf.experiment_enabled(ExperimentFlag.TEST_FLAG): + showInfo('You have the "ping" experiment enabled') last_day_cutoff = self.col.sched.day_cutoff diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index c09ab01d7ed..5791aef27c0 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -766,6 +766,9 @@ def save_custom_colours() -> bytes: # DeckConfigService "get_ignored_before_count", "get_retention_workload", + # ConfigService + "set_config_json", + "get_config_json", ] diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 6ef1f1d496d..3ebc2c0bd71 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -403,6 +403,10 @@ def update_global(self) -> None: self.mw.pm.setUiScale(newScale) restart_required = True + conf = self.mw.col.conf + if conf._get_experiments_dirty() != conf._experiments: + restart_required = True + if restart_required: showInfo(tr.preferences_changes_will_take_effect_when_you()) diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 1d55006ba5a..c7fd8165796 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -143,6 +143,7 @@ def _profileForPage(self, kind: AnkiWebViewKind) -> QWebEngineProfile: AnkiWebViewKind.IMPORT_ANKI_PACKAGE, AnkiWebViewKind.IMPORT_CSV, AnkiWebViewKind.IMPORT_LOG, + AnkiWebViewKind.PREFERENCES, ) global _profile_with_api_access, _profile_without_api_access diff --git a/rslib/src/backend/config.rs b/rslib/src/backend/config.rs index b6e81ce2ad7..3b731067472 100644 --- a/rslib/src/backend/config.rs +++ b/rslib/src/backend/config.rs @@ -57,8 +57,14 @@ impl From for StringKey { impl crate::services::ConfigService for Collection { fn get_config_json(&mut self, input: generic::String) -> Result { - let val: Option = self.get_config_optional(input.val.as_str()); - val.or_not_found(input.val) + let key = input.val.as_str(); + let val: Option = self.get_config_optional(key); + let default = match key { + "experimentalFeatures" => Some(serde_json::from_str("{}").unwrap()), + _ => None, + }; + val.or(default) + .or_not_found(key) .and_then(|v| serde_json::to_vec(&v).map_err(Into::into)) .map(Into::into) } diff --git a/ts/lib/components/Switch.svelte b/ts/lib/components/Switch.svelte index d48f46fb338..343cddf3baa 100644 --- a/ts/lib/components/Switch.svelte +++ b/ts/lib/components/Switch.svelte @@ -20,6 +20,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html class:nightMode={$pageTheme.isDark} bind:checked={value} {disabled} + on:input /> diff --git a/ts/routes/preferences/+page.svelte b/ts/routes/preferences/+page.svelte index 09855e9dca9..a5e1200a16b 100644 --- a/ts/routes/preferences/+page.svelte +++ b/ts/routes/preferences/+page.svelte @@ -2,10 +2,15 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> -
@@ -22,13 +27,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -
diff --git a/ts/routes/preferences/+page.ts b/ts/routes/preferences/+page.ts new file mode 100644 index 00000000000..03d19aa0ee8 --- /dev/null +++ b/ts/routes/preferences/+page.ts @@ -0,0 +1,16 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { autoSavingPrefs } from "$lib/sveltelib/preferences"; +import type { PageLoad } from "./$types"; +import { getConfigJsonObject, setConfigJsonObject } from "./json"; + +const CONFIG_KEY = "experimentalFeatures"; + +export const load = (async () => { + const labPerfs = await autoSavingPrefs( + () => getConfigJsonObject(CONFIG_KEY), + ($config) => setConfigJsonObject(CONFIG_KEY, $config), + ); + + return { labPerfs }; +}) satisfies PageLoad; diff --git a/ts/routes/preferences/LabItem.svelte b/ts/routes/preferences/LabItem.svelte index 03aec37b06b..ca285b977e9 100644 --- a/ts/routes/preferences/LabItem.svelte +++ b/ts/routes/preferences/LabItem.svelte @@ -4,16 +4,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->