Live theme changes (#1497)

* Allow theme change at runtime and add hook

* Save or restore default palette on theme change

* Update aqt widget styles on theme change

* styling fixes

- drop _light_palette, as default_palette serves the same purpose
- save default platform theme, and restore it when switching away
from nightmode
- update macOS light/dark mode on theme switch
- fix unreadable menus on Windows

* update night-mode classes on theme change

This is the easy part - CSS styling that uses standard_css or our
css variables should update automatically. The main remaining issue
is JS code that sets colors based on the theme at the time it's run -
eg the graph code, and the editor.

* switch night mode value on toggle

* expose current theme via a store; switch graphs to use it

https://github.com/ankitects/anki/issues/1471#issuecomment-972402492

* start using currentTheme in editor/components

This fixes basic editing - there are still components that need updating.

* add simple xcodeproj for code completion

* add helper to get currently-active system theme on macOS

* fix setCurrentTheme not being immediately available

* live update tag color

* style().name() doesn't work on Qt5

* automatic theme switching on Windows/Mac

* currentTheme -> pageTheme

* Replace `nightModeKey` with `pageTheme`

Co-authored-by: Damien Elmes <gpg@ankiweb.net>
This commit is contained in:
RumovZ 2021-11-24 22:17:41 +01:00 committed by GitHub
parent 63404de5df
commit f2173fddb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 659 additions and 182 deletions

View file

@ -37,4 +37,8 @@ preferences-you-can-restore-backups-via-fileswitch = You can restore backups via
preferences-legacy-timezone-handling = Legacy timezone handling (buggy, but required for AnkiDroid <= 2.14) preferences-legacy-timezone-handling = Legacy timezone handling (buggy, but required for AnkiDroid <= 2.14)
preferences-default-search-text = Default search text preferences-default-search-text = Default search text
preferences-default-search-text-example = eg. 'deck:current ' preferences-default-search-text-example = eg. 'deck:current '
preferences-theme-label = Theme: { $theme }
preferences-theme-follow-system = Follow System
preferences-theme-light = Light
preferences-theme-dark = Dark
preferences-v3-scheduler = V3 scheduler preferences-v3-scheduler = V3 scheduler

View file

@ -19,6 +19,14 @@ class SidebarSearchBar(QLineEdit):
self.timer.setInterval(600) self.timer.setInterval(600)
self.timer.setSingleShot(True) self.timer.setSingleShot(True)
self.setFrame(False) self.setFrame(False)
self.setup_style()
qconnect(self.timer.timeout, self.onSearch)
qconnect(self.textChanged, self.onTextChanged)
aqt.gui_hooks.theme_did_change.append(self.setup_style)
def setup_style(self) -> None:
border = theme_manager.color(colors.MEDIUM_BORDER) border = theme_manager.color(colors.MEDIUM_BORDER)
styles = [ styles = [
"padding: 1px", "padding: 1px",
@ -32,9 +40,6 @@ class SidebarSearchBar(QLineEdit):
self.setStyleSheet("QLineEdit { %s }" % ";".join(styles)) self.setStyleSheet("QLineEdit { %s }" % ";".join(styles))
qconnect(self.timer.timeout, self.onSearch)
qconnect(self.textChanged, self.onTextChanged)
def onTextChanged(self, text: str) -> None: def onTextChanged(self, text: str) -> None:
if not self.timer.isActive(): if not self.timer.isActive():
self.timer.start() self.timer.start()
@ -49,3 +54,6 @@ class SidebarSearchBar(QLineEdit):
self.onSearch() self.onSearch()
else: else:
QLineEdit.keyPressEvent(self, evt) QLineEdit.keyPressEvent(self, evt)
def cleanup(self) -> None:
aqt.gui_hooks.theme_did_change.remove(self.setup_style)

View file

@ -31,6 +31,7 @@ class SidebarToolbar(QToolBar):
self.setIconSize(QSize(16, 16)) self.setIconSize(QSize(16, 16))
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.setStyle(QStyleFactory.create("fusion")) self.setStyle(QStyleFactory.create("fusion"))
aqt.gui_hooks.theme_did_change.append(self._update_icons)
def _setup_tools(self) -> None: def _setup_tools(self) -> None:
for row, tool in enumerate(self._tools): for row, tool in enumerate(self._tools):
@ -48,3 +49,10 @@ class SidebarToolbar(QToolBar):
def _on_action_group_triggered(self, action: QAction) -> None: def _on_action_group_triggered(self, action: QAction) -> None:
index = self._action_group.actions().index(action) index = self._action_group.actions().index(action)
self.sidebar.tool = self._tools[index][0] self.sidebar.tool = self._tools[index][0]
def cleanup(self) -> None:
aqt.gui_hooks.theme_did_change.remove(self._update_icons)
def _update_icons(self) -> None:
for idx, action in enumerate(self._action_group.actions()):
action.setIcon(theme_manager.icon_from_resources(self._tools[idx][1]))

View file

@ -91,6 +91,16 @@ class SidebarTreeView(QTreeView):
qconnect(self.expanded, self._on_expansion) qconnect(self.expanded, self._on_expansion)
qconnect(self.collapsed, self._on_collapse) qconnect(self.collapsed, self._on_collapse)
self._setup_style()
# these do not really belong here, they should be in a higher-level class
self.toolbar = SidebarToolbar(self)
self.searchBar = SidebarSearchBar(self)
gui_hooks.flag_label_did_change.append(self.refresh)
gui_hooks.theme_did_change.append(self._setup_style)
def _setup_style(self) -> None:
# match window background color and tweak style # match window background color and tweak style
bgcolor = QPalette().window().color().name() bgcolor = QPalette().window().color().name()
border = theme_manager.color(colors.MEDIUM_BORDER) border = theme_manager.color(colors.MEDIUM_BORDER)
@ -105,14 +115,11 @@ class SidebarTreeView(QTreeView):
self.setStyleSheet("QTreeView { %s }" % ";".join(styles)) self.setStyleSheet("QTreeView { %s }" % ";".join(styles))
# these do not really belong here, they should be in a higher-level class
self.toolbar = SidebarToolbar(self)
self.searchBar = SidebarSearchBar(self)
gui_hooks.flag_label_did_change.append(self.refresh)
def cleanup(self) -> None: def cleanup(self) -> None:
self.toolbar.cleanup()
self.searchBar.cleanup()
gui_hooks.flag_label_did_change.remove(self.refresh) gui_hooks.flag_label_did_change.remove(self.refresh)
gui_hooks.theme_did_change.remove(self._setup_style)
@property @property
def tool(self) -> SidebarTool: def tool(self) -> SidebarTool:

View file

@ -59,6 +59,7 @@ class Table:
def cleanup(self) -> None: def cleanup(self) -> None:
self._save_header() self._save_header()
gui_hooks.theme_did_change.remove(self._setup_style)
# Public Methods # Public Methods
###################################################################### ######################################################################
@ -342,17 +343,10 @@ class Table:
self._view.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self._view.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self._view.horizontalScrollBar().setSingleStep(10) self._view.horizontalScrollBar().setSingleStep(10)
self._update_font() self._update_font()
if not theme_manager.night_mode: self._setup_style()
self._view.setStyleSheet(
"QTableView{ selection-background-color: rgba(150, 150, 150, 50); "
"selection-color: black; }"
)
elif theme_manager.macos_dark_mode():
self._view.setStyleSheet(
f"QTableView {{ gridline-color: {colors.FRAME_BG} }}"
)
self._view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self._view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
qconnect(self._view.customContextMenuRequested, self._on_context_menu) qconnect(self._view.customContextMenuRequested, self._on_context_menu)
gui_hooks.theme_did_change.append(self._setup_style)
def _update_font(self) -> None: def _update_font(self) -> None:
# we can't choose different line heights efficiently, so we need # we can't choose different line heights efficiently, so we need
@ -365,6 +359,19 @@ class Table:
curmax = bsize curmax = bsize
self._view.verticalHeader().setDefaultSectionSize(curmax + 6) self._view.verticalHeader().setDefaultSectionSize(curmax + 6)
def _setup_style(self) -> None:
if not theme_manager.night_mode:
self._view.setStyleSheet(
"QTableView{ selection-background-color: rgba(150, 150, 150, 50); "
"selection-color: black; }"
)
elif theme_manager.macos_dark_mode():
self._view.setStyleSheet(
f"QTableView {{ gridline-color: {colors.FRAME_BG} }}"
)
else:
self._view.setStyleSheet("")
def _setup_headers(self) -> None: def _setup_headers(self) -> None:
vh = self._view.verticalHeader() vh = self._view.verticalHeader()
hh = self._view.horizontalHeader() hh = self._view.horizontalHeader()

View file

@ -55,7 +55,7 @@ class CardLayout(QDialog):
self.model = note.note_type() self.model = note.note_type()
self.templates = self.model["tmpls"] self.templates = self.model["tmpls"]
self.fill_empty_action_toggled = fill_empty self.fill_empty_action_toggled = fill_empty
self.night_mode_is_enabled = self.mw.pm.night_mode() self.night_mode_is_enabled = theme_manager.night_mode
self.mobile_emulation_enabled = False self.mobile_emulation_enabled = False
self.have_autoplayed = False self.have_autoplayed = False
self.mm._remove_from_cache(self.model["id"]) self.mm._remove_from_cache(self.model["id"])

View file

@ -55,6 +55,9 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QComboBox" name="theme"/>
</item>
<item> <item>
<widget class="QComboBox" name="video_driver"/> <widget class="QComboBox" name="video_driver"/>
</item> </item>
@ -86,13 +89,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="nightMode">
<property name="text">
<string>preferences_night_mode</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QComboBox" name="useCurrent"> <widget class="QComboBox" name="useCurrent">
<item> <item>
@ -602,12 +598,12 @@
</widget> </widget>
<tabstops> <tabstops>
<tabstop>lang</tabstop> <tabstop>lang</tabstop>
<tabstop>theme</tabstop>
<tabstop>video_driver</tabstop> <tabstop>video_driver</tabstop>
<tabstop>showPlayButtons</tabstop> <tabstop>showPlayButtons</tabstop>
<tabstop>interrupt_audio</tabstop> <tabstop>interrupt_audio</tabstop>
<tabstop>pastePNG</tabstop> <tabstop>pastePNG</tabstop>
<tabstop>paste_strips_formatting</tabstop> <tabstop>paste_strips_formatting</tabstop>
<tabstop>nightMode</tabstop>
<tabstop>useCurrent</tabstop> <tabstop>useCurrent</tabstop>
<tabstop>default_search_text</tabstop> <tabstop>default_search_text</tabstop>
<tabstop>uiScale</tabstop> <tabstop>uiScale</tabstop>

View file

@ -47,7 +47,7 @@ from aqt.qt import *
from aqt.qt import sip from aqt.qt import sip
from aqt.sync import sync_collection, sync_login from aqt.sync import sync_collection, sync_login
from aqt.taskman import TaskManager from aqt.taskman import TaskManager
from aqt.theme import theme_manager from aqt.theme import Theme, theme_manager
from aqt.undo import UndoActionsInfo from aqt.undo import UndoActionsInfo
from aqt.utils import ( from aqt.utils import (
HelpPage, HelpPage,
@ -145,11 +145,11 @@ class AnkiQt(QMainWindow):
self.setupMediaServer() self.setupMediaServer()
self.setupSound() self.setupSound()
self.setupSpellCheck() self.setupSpellCheck()
self.setupProgress()
self.setupStyle() self.setupStyle()
self.setupMainWindow() self.setupMainWindow()
self.setupSystemSpecific() self.setupSystemSpecific()
self.setupMenus() self.setupMenus()
self.setupProgress()
self.setupErrorHandler() self.setupErrorHandler()
self.setupSignals() self.setupSignals()
self.setupAutoUpdate() self.setupAutoUpdate()
@ -1004,8 +1004,14 @@ title="{}" {}>{}</button>""".format(
return True return True
def setupStyle(self) -> None: def setupStyle(self) -> None:
theme_manager.night_mode = self.pm.night_mode() theme_manager.apply_style()
theme_manager.apply_style(self.app) self.progress.timer(
5 * 1000, theme_manager.apply_style_if_system_style_changed, True, False
)
def set_theme(self, theme: Theme) -> None:
self.pm.set_theme(theme)
self.setupStyle()
# Key handling # Key handling
########################################################################## ##########################################################################

View file

@ -3,20 +3,40 @@
"""Platform-specific functionality.""" """Platform-specific functionality."""
from __future__ import annotations
import os import os
import sys import sys
from ctypes import CDLL from ctypes import CDLL
import aqt.utils import aqt.utils
from anki.utils import isMac from anki.utils import isMac, isWin
def set_dark_mode(enabled: bool) -> bool: def get_windows_dark_mode() -> bool:
"True if Windows system is currently in dark mode."
if not isWin:
return False
from winreg import ( # pylint: disable=import-error
HKEY_CURRENT_USER,
OpenKey,
QueryValueEx,
)
key = OpenKey(
HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
)
return not QueryValueEx(key, "AppsUseLightTheme")[0]
def set_macos_dark_mode(enabled: bool) -> bool:
"True if setting successful." "True if setting successful."
if not isMac: if not isMac:
return False return False
try: try:
_set_dark_mode(enabled) _ankihelper().set_darkmode_enabled(enabled)
return True return True
except Exception as e: except Exception as e:
# swallow exceptions, as library will fail on macOS 10.13 # swallow exceptions, as library will fail on macOS 10.13
@ -24,9 +44,28 @@ def set_dark_mode(enabled: bool) -> bool:
return False return False
def _set_dark_mode(enabled: bool) -> None: def get_macos_dark_mode() -> bool:
"True if macOS system is currently in dark mode."
if not isMac:
return False
try:
return _ankihelper().system_is_dark()
except Exception as e:
# swallow exceptions, as library will fail on macOS 10.13
print(e)
return False
_ankihelper_dll: CDLL | None = None
def _ankihelper() -> CDLL:
global _ankihelper_dll
if _ankihelper_dll:
return _ankihelper_dll
if getattr(sys, "frozen", False): if getattr(sys, "frozen", False):
path = os.path.join(sys.prefix, "libankihelper.dylib") path = os.path.join(sys.prefix, "libankihelper.dylib")
else: else:
path = os.path.join(aqt.utils.aqt_data_folder(), "lib", "libankihelper.dylib") path = os.path.join(aqt.utils.aqt_data_folder(), "lib", "libankihelper.dylib")
CDLL(path).set_darkmode_enabled(enabled) _ankihelper_dll = CDLL(path)
return _ankihelper_dll

View file

@ -11,6 +11,7 @@ from aqt import AnkiQt
from aqt.operations.collection import set_preferences from aqt.operations.collection import set_preferences
from aqt.profiles import VideoDriver from aqt.profiles import VideoDriver
from aqt.qt import * from aqt.qt import *
from aqt.theme import Theme
from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr
@ -199,7 +200,17 @@ class Preferences(QDialog):
def setup_global(self) -> None: def setup_global(self) -> None:
"Setup options global to all profiles." "Setup options global to all profiles."
self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100)) self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100))
self.form.nightMode.setChecked(self.mw.pm.night_mode()) themes = [
tr.preferences_theme_label(theme=theme)
for theme in (
tr.preferences_theme_follow_system(),
tr.preferences_theme_light(),
tr.preferences_theme_dark(),
)
]
self.form.theme.addItems(themes)
self.form.theme.setCurrentIndex(self.mw.pm.theme().value)
qconnect(self.form.theme.currentIndexChanged, self.on_theme_changed)
self.setup_language() self.setup_language()
self.setup_video_driver() self.setup_video_driver()
@ -216,15 +227,14 @@ class Preferences(QDialog):
self.mw.pm.setUiScale(newScale) self.mw.pm.setUiScale(newScale)
restart_required = True restart_required = True
if self.mw.pm.night_mode() != self.form.nightMode.isChecked():
self.mw.pm.set_night_mode(not self.mw.pm.night_mode())
restart_required = True
if restart_required: if restart_required:
showInfo(tr.preferences_changes_will_take_effect_when_you()) showInfo(tr.preferences_changes_will_take_effect_when_you())
self.updateOptions() self.updateOptions()
def on_theme_changed(self, index: int) -> None:
self.mw.set_theme(Theme(index))
# legacy - one of Henrik's add-ons is currently wrapping them # legacy - one of Henrik's add-ons is currently wrapping them
def setupOptions(self) -> None: def setupOptions(self) -> None:

View file

@ -23,6 +23,7 @@ from anki.sync import SyncAuth
from anki.utils import int_time, isMac, isWin from anki.utils import int_time, isMac, isWin
from aqt import appHelpSite from aqt import appHelpSite
from aqt.qt import * from aqt.qt import *
from aqt.theme import Theme
from aqt.utils import disable_help_button, showWarning, tr from aqt.utils import disable_help_button, showWarning, tr
# Profile handling # Profile handling
@ -515,6 +516,12 @@ create table if not exists profiles
def set_night_mode(self, on: bool) -> None: def set_night_mode(self, on: bool) -> None:
self.meta["night_mode"] = on self.meta["night_mode"] = on
def theme(self) -> Theme:
return Theme(self.meta.get("theme", 0))
def set_theme(self, theme: Theme) -> None:
self.meta["theme"] = theme.value
def dark_mode_widgets(self) -> bool: def dark_mode_widgets(self) -> bool:
return self.meta.get("dark_mode_widgets", False) return self.meta.get("dark_mode_widgets", False)

View file

@ -3,12 +3,14 @@
from __future__ import annotations from __future__ import annotations
import enum
import platform import platform
from dataclasses import dataclass from dataclasses import dataclass
import aqt
from anki.utils import isMac from anki.utils import isMac
from aqt import QApplication, colors, gui_hooks, isWin from aqt import QApplication, colors, gui_hooks, isWin
from aqt.platform import set_dark_mode from aqt.platform import get_macos_dark_mode, get_windows_dark_mode, set_macos_dark_mode
from aqt.qt import ( from aqt.qt import (
QColor, QColor,
QGuiApplication, QGuiApplication,
@ -37,6 +39,12 @@ class ColoredIcon:
return ColoredIcon(path=self.path, color=color) return ColoredIcon(path=self.path, color=color)
class Theme(enum.IntEnum):
FOLLOW_SYSTEM = 0
LIGHT = 1
DARK = 2
class ThemeManager: class ThemeManager:
_night_mode_preference = False _night_mode_preference = False
_icon_cache_light: dict[str, QIcon] = {} _icon_cache_light: dict[str, QIcon] = {}
@ -44,6 +52,7 @@ class ThemeManager:
_icon_size = 128 _icon_size = 128
_dark_mode_available: bool | None = None _dark_mode_available: bool | None = None
default_palette: QPalette | None = None default_palette: QPalette | None = None
_default_style: str | None = None
# Qt applies a gradient to the buttons in dark mode # Qt applies a gradient to the buttons in dark mode
# from about #505050 to #606060. # from about #505050 to #606060.
@ -58,7 +67,7 @@ class ThemeManager:
return False return False
if self._dark_mode_available is None: if self._dark_mode_available is None:
self._dark_mode_available = set_dark_mode(True) self._dark_mode_available = set_macos_dark_mode(True)
from aqt import mw from aqt import mw
@ -143,28 +152,57 @@ class ThemeManager:
def qcolor(self, colors: tuple[str, str]) -> QColor: def qcolor(self, colors: tuple[str, str]) -> QColor:
return QColor(self.color(colors)) return QColor(self.color(colors))
def apply_style(self, app: QApplication) -> None: def _determine_night_mode(self) -> bool:
self.default_palette = QGuiApplication.palette() theme = aqt.mw.pm.theme()
if theme == Theme.LIGHT:
return False
elif theme == Theme.DARK:
return True
else:
if isWin:
return get_windows_dark_mode()
elif isMac:
return get_macos_dark_mode()
else:
# not supported on Linux
return False
def apply_style_if_system_style_changed(self) -> None:
theme = aqt.mw.pm.theme()
if theme != Theme.FOLLOW_SYSTEM:
return
if self._determine_night_mode() != self.night_mode:
self.apply_style()
def apply_style(self) -> None:
"Apply currently configured style."
app = aqt.mw.app
self.night_mode = self._determine_night_mode()
if not self.default_palette:
self.default_palette = QGuiApplication.palette()
self._default_style = app.style().objectName()
self._apply_palette(app) self._apply_palette(app)
self._apply_style(app) self._apply_style(app)
gui_hooks.theme_did_change()
def _apply_style(self, app: QApplication) -> None: def _apply_style(self, app: QApplication) -> None:
buf = "" buf = ""
if isWin and platform.release() == "10" and not self.night_mode: if isWin and platform.release() == "10":
# add missing bottom border to menubar # day mode is missing a bottom border; background must be
buf += """ # also set for border to apply
QMenuBar { buf += f"""
border-bottom: 1px solid #aaa; QMenuBar {{
background: white; border-bottom: 1px solid {self.color(colors.BORDER)};
} background: {self.color(colors.WINDOW_BG)};
}}
""" """
# qt bug? setting the above changes the browser sidebar # qt bug? setting the above changes the browser sidebar
# to white as well, so set it back # to white as well, so set it back
buf += """ buf += f"""
QTreeWidget { QTreeWidget {{
background: #eee; background: {self.color(colors.WINDOW_BG)};
} }}
""" """
if self.night_mode: if self.night_mode:
@ -209,7 +247,11 @@ QTabWidget {{ background-color: {}; }}
app.setStyleSheet(buf) app.setStyleSheet(buf)
def _apply_palette(self, app: QApplication) -> None: def _apply_palette(self, app: QApplication) -> None:
set_macos_dark_mode(self.night_mode)
if not self.night_mode: if not self.night_mode:
app.setStyle(QStyleFactory.create(self._default_style)) # type: ignore
app.setPalette(self.default_palette)
return return
if not self.macos_dark_mode(): if not self.macos_dark_mode():

View file

@ -252,6 +252,7 @@ class AnkiWebView(QWebEngineView):
context=Qt.ShortcutContext.WidgetWithChildrenShortcut, context=Qt.ShortcutContext.WidgetWithChildrenShortcut,
activated=self.onEsc, activated=self.onEsc,
) )
gui_hooks.theme_did_change.append(self.on_theme_did_change)
def set_title(self, title: str) -> None: def set_title(self, title: str) -> None:
self.title = title # type: ignore[assignment] self.title = title # type: ignore[assignment]
@ -445,7 +446,7 @@ div[contenteditable="true"]:focus {{
lang_dir = "ltr" lang_dir = "ltr"
return f""" return f"""
body {{ zoom: {zoom}; background-color: {body_bg}; direction: {lang_dir}; }} body {{ zoom: {zoom}; background-color: --window-bg; direction: {lang_dir}; }}
html {{ {font} }} html {{ {font} }}
{button_style} {button_style}
:root {{ --window-bg: {window_bg_day} }} :root {{ --window-bg: {window_bg_day} }}
@ -551,6 +552,8 @@ html {{ {font} }}
self._maybeRunActions() self._maybeRunActions()
def _maybeRunActions(self) -> None: def _maybeRunActions(self) -> None:
if sip.isdeleted(self):
return
while self._pendingActions and self._domDone: while self._pendingActions and self._domDone:
name, args = self._pendingActions.pop(0) name, args = self._pendingActions.pop(0)
@ -675,4 +678,29 @@ document.head.appendChild(style);
# this will fail when __del__ is called during app shutdown # this will fail when __del__ is called during app shutdown
return return
gui_hooks.theme_did_change.remove(self.on_theme_did_change)
mw.mediaServer.clear_page_html(id(self)) mw.mediaServer.clear_page_html(id(self))
def on_theme_did_change(self) -> None:
# avoid flashes if page reloaded
self._page.setBackgroundColor(
self.get_window_bg_color(theme_manager.night_mode)
)
# update night-mode class, and legacy nightMode/night-mode body classes
self.eval(
f"""
(function() {{
const doc = document.documentElement.classList;
const body = document.body.classList;
if ({1 if theme_manager.night_mode else 0}) {{
doc.add("night-mode");
body.add("night-mode");
body.add("nightMode");
}} else {{
doc.remove("night-mode");
body.remove("night-mode");
body.remove("nightMode");
}}
}})();
"""
)

View file

@ -4,6 +4,7 @@
@import Foundation; @import Foundation;
@import AppKit; @import AppKit;
/// Force our app to be either light or dark mode.
void set_darkmode_enabled(BOOL enabled) { void set_darkmode_enabled(BOOL enabled) {
NSAppearance *appearance; NSAppearance *appearance;
if (enabled) { if (enabled) {
@ -14,3 +15,20 @@ void set_darkmode_enabled(BOOL enabled) {
[NSApplication sharedApplication].appearance = appearance; [NSApplication sharedApplication].appearance = appearance;
} }
/// True if the system is set to dark mode.
BOOL system_is_dark(void) {
BOOL styleSet = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleInterfaceStyle"] != nil;
return styleSet;
// FIXME: confirm whether this is required on 10.15/16 (it
// does not appear to be on 11)
// BOOL autoSwitch = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleInterfaceStyleSwitchesAutomatically"];
//
// if (@available(macOS 10.15, *)) {
// return autoSwitch ? !styleSet : styleSet;
// } else {
// return styleSet;
// }
}

View file

@ -0,0 +1,280 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
1327600A274613D9001D63D7 /* AnkiHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13276009274613D8001D63D7 /* AnkiHelper.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
13276009274613D8001D63D7 /* AnkiHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnkiHelper.m; sourceTree = "<group>"; };
138B770F2746137F003A3E4F /* libankihelper.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libankihelper.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
138B770D2746137F003A3E4F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
138B77062746137F003A3E4F = {
isa = PBXGroup;
children = (
13276009274613D8001D63D7 /* AnkiHelper.m */,
138B77102746137F003A3E4F /* Products */,
);
sourceTree = "<group>";
};
138B77102746137F003A3E4F /* Products */ = {
isa = PBXGroup;
children = (
138B770F2746137F003A3E4F /* libankihelper.dylib */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
138B770B2746137F003A3E4F /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
138B770E2746137F003A3E4F /* ankihelper */ = {
isa = PBXNativeTarget;
buildConfigurationList = 138B77182746137F003A3E4F /* Build configuration list for PBXNativeTarget "ankihelper" */;
buildPhases = (
138B770B2746137F003A3E4F /* Headers */,
138B770C2746137F003A3E4F /* Sources */,
138B770D2746137F003A3E4F /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = ankihelper;
productName = ankihelper;
productReference = 138B770F2746137F003A3E4F /* libankihelper.dylib */;
productType = "com.apple.product-type.library.dynamic";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
138B77072746137F003A3E4F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1310;
TargetAttributes = {
138B770E2746137F003A3E4F = {
CreatedOnToolsVersion = 13.1;
};
};
};
buildConfigurationList = 138B770A2746137F003A3E4F /* Build configuration list for PBXProject "ankihelper" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 138B77062746137F003A3E4F;
productRefGroup = 138B77102746137F003A3E4F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
138B770E2746137F003A3E4F /* ankihelper */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
138B770C2746137F003A3E4F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1327600A274613D9001D63D7 /* AnkiHelper.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
138B77162746137F003A3E4F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.6;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
138B77172746137F003A3E4F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.6;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
138B77192746137F003A3E4F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7ZM8SLJM4P;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
138B771A2746137F003A3E4F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7ZM8SLJM4P;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
138B770A2746137F003A3E4F /* Build configuration list for PBXProject "ankihelper" */ = {
isa = XCConfigurationList;
buildConfigurations = (
138B77162746137F003A3E4F /* Debug */,
138B77172746137F003A3E4F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
138B77182746137F003A3E4F /* Build configuration list for PBXNativeTarget "ankihelper" */ = {
isa = XCConfigurationList;
buildConfigurations = (
138B77192746137F003A3E4F /* Debug */,
138B771A2746137F003A3E4F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 138B77072746137F003A3E4F /* Project object */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>ankihelper.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View file

@ -540,6 +540,10 @@ hooks = [
there are no outstanding ops. there are no outstanding ops.
""", """,
), ),
Hook(
name="theme_did_change",
doc="Called after night mode is toggled.",
),
# Webview # Webview
################### ###################
Hook( Hook(

View file

@ -9,7 +9,6 @@ import { ChangeNotetypeState, getChangeNotetypeInfo, getNotetypeNames } from "./
import { setupI18n, ModuleName } from "../lib/i18n"; import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode"; import { checkNightMode } from "../lib/nightmode";
import ChangeNotetypePage from "./ChangeNotetypePage.svelte"; import ChangeNotetypePage from "./ChangeNotetypePage.svelte";
import { nightModeKey } from "../components/context-keys";
export async function changeNotetypePage( export async function changeNotetypePage(
target: HTMLDivElement, target: HTMLDivElement,
@ -28,14 +27,11 @@ export async function changeNotetypePage(
}), }),
]); ]);
const nightMode = checkNightMode(); checkNightMode();
const context = new Map();
context.set(nightModeKey, nightMode);
const state = new ChangeNotetypeState(names, info); const state = new ChangeNotetypeState(names, info);
return new ChangeNotetypePage({ return new ChangeNotetypePage({
target, target,
props: { state }, props: { state },
context,
} as any); } as any);
} }

View file

@ -16,13 +16,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</script> </script>
<script lang="ts"> <script lang="ts">
import { getContext, setContext } from "svelte"; import { setContext } from "svelte";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import Item from "./Item.svelte"; import Item from "./Item.svelte";
import type { Registration } from "./registration"; import type { Registration } from "./registration";
import { sectionKey, nightModeKey } from "./context-keys"; import { sectionKey } from "./context-keys";
import { insertElement, appendElement } from "./identifier"; import { insertElement, appendElement } from "./identifier";
import { makeInterface } from "./registration"; import { makeInterface } from "./registration";
import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className: string = ""; let className: string = "";
@ -89,15 +90,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: if (buttonToolbarRef && api) { $: if (buttonToolbarRef && api) {
createApi(); createApi();
} }
const nightMode = getContext<boolean>(nightModeKey);
</script> </script>
<div <div
bind:this={buttonToolbarRef} bind:this={buttonToolbarRef}
{id} {id}
class="button-toolbar btn-toolbar {className}" class="button-toolbar btn-toolbar {className}"
class:nightMode class:nightMode={$pageTheme.isDark}
{style} {style}
role="toolbar" role="toolbar"
on:focusout on:focusout

View file

@ -3,8 +3,8 @@ 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 { onMount, createEventDispatcher, getContext } from "svelte"; import { onMount, createEventDispatcher } from "svelte";
import { nightModeKey } from "./context-keys"; import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className = ""; let className = "";
@ -15,8 +15,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let buttonRef: HTMLButtonElement; let buttonRef: HTMLButtonElement;
const nightMode = getContext(nightModeKey);
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
onMount(() => dispatch("mount", { button: buttonRef })); onMount(() => dispatch("mount", { button: buttonRef }));
</script> </script>
@ -26,8 +24,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
tabindex={tabbable ? 0 : -1} tabindex={tabbable ? 0 : -1}
bind:this={buttonRef} bind:this={buttonRef}
class="dropdown-item btn {className}" class="dropdown-item btn {className}"
class:btn-day={!nightMode} class:btn-day={!$pageTheme.isDark}
class:btn-night={nightMode} class:btn-night={$pageTheme.isDark}
title={tooltip} title={tooltip}
on:click on:click
on:mouseenter on:mouseenter

View file

@ -5,7 +5,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import IconConstrain from "./IconConstrain.svelte"; import IconConstrain from "./IconConstrain.svelte";
import { getContext, onMount, createEventDispatcher } from "svelte"; import { getContext, onMount, createEventDispatcher } from "svelte";
import { nightModeKey, dropdownKey } from "./context-keys"; import { dropdownKey } from "./context-keys";
import { pageTheme } from "../sveltelib/theme";
import type { DropdownProps } from "./dropdown"; import type { DropdownProps } from "./dropdown";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
@ -23,7 +24,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let buttonRef: HTMLButtonElement; let buttonRef: HTMLButtonElement;
const nightMode = getContext<boolean>(nightModeKey);
const dropdownProps = getContext<DropdownProps>(dropdownKey) ?? { dropdown: false }; const dropdownProps = getContext<DropdownProps>(dropdownKey) ?? { dropdown: false };
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -36,8 +36,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
class="icon-button btn {className}" class="icon-button btn {className}"
class:active class:active
class:dropdown-toggle={dropdownProps.dropdown} class:dropdown-toggle={dropdownProps.dropdown}
class:btn-day={!nightMode} class:btn-day={!$pageTheme.isDark}
class:btn-night={nightMode} class:btn-night={$pageTheme.isDark}
title={tooltip} title={tooltip}
{...dropdownProps} {...dropdownProps}
{disabled} {disabled}

View file

@ -4,8 +4,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { onMount, createEventDispatcher, getContext } from "svelte"; import { onMount, createEventDispatcher, getContext } from "svelte";
import { nightModeKey, dropdownKey } from "./context-keys"; import { dropdownKey } from "./context-keys";
import type { DropdownProps } from "./dropdown"; import type { DropdownProps } from "./dropdown";
import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className: string = ""; let className: string = "";
@ -21,7 +22,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let disabled = false; export let disabled = false;
export let tabbable = false; export let tabbable = false;
const nightMode = getContext<boolean>(nightModeKey);
const dropdownProps = getContext<DropdownProps>(dropdownKey) ?? { dropdown: false }; const dropdownProps = getContext<DropdownProps>(dropdownKey) ?? { dropdown: false };
let buttonRef: HTMLButtonElement; let buttonRef: HTMLButtonElement;
@ -36,8 +36,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
class="label-button {extendClassName(className, theme)}" class="label-button {extendClassName(className, theme)}"
class:active class:active
class:dropdown-toggle={dropdownProps.dropdown} class:dropdown-toggle={dropdownProps.dropdown}
class:btn-day={theme === "anki" && !nightMode} class:btn-day={theme === "anki" && !$pageTheme.isDark}
class:btn-night={theme === "anki" && nightMode} class:btn-night={theme === "anki" && $pageTheme.isDark}
title={tooltip} title={tooltip}
{...dropdownProps} {...dropdownProps}
{disabled} {disabled}

View file

@ -3,8 +3,8 @@ 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 { onMount, createEventDispatcher, getContext } from "svelte"; import { onMount, createEventDispatcher } from "svelte";
import { nightModeKey } from "./context-keys"; import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className = ""; let className = "";
@ -13,8 +13,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let disabled = false; export let disabled = false;
const nightMode = getContext<boolean>(nightModeKey);
let buttonRef: HTMLSelectElement; let buttonRef: HTMLSelectElement;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -29,9 +27,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{id} {id}
{disabled} {disabled}
class="{className} form-select" class="{className} form-select"
class:btn-day={!nightMode} class:btn-day={!$pageTheme.isDark}
class:btn-night={nightMode} class:btn-night={$pageTheme.isDark}
class:visible-down-arrow={nightMode} class:visible-down-arrow={$pageTheme.isDark}
title={tooltip} title={tooltip}
on:change on:change
> >

View file

@ -1,6 +1,5 @@
// Copyright: Ankitects Pty Ltd and contributors // 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
export const nightModeKey = Symbol("nightMode");
export const touchDeviceKey = Symbol("touchDevice"); export const touchDeviceKey = Symbol("touchDevice");
export const sectionKey = Symbol("section"); export const sectionKey = Symbol("section");

View file

@ -3,19 +3,16 @@ 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 { getContext } from "svelte"; import { pageTheme } from "../sveltelib/theme";
import { nightModeKey } from "../components/context-keys";
export let choices: string[]; export let choices: string[];
export let value: number = 0; export let value: number = 0;
const nightMode = getContext<boolean>(nightModeKey);
</script> </script>
<select <select
bind:value bind:value
class:nightMode class:nightMode={$pageTheme.isDark}
class:visible-down-arrow={nightMode} class:visible-down-arrow={$pageTheme.isDark}
class="enum-selector form-select" class="enum-selector form-select"
> >
{#each choices as choice, idx} {#each choices as choice, idx}

View file

@ -3,15 +3,12 @@ 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 { getContext } from "svelte"; import { pageTheme } from "../sveltelib/theme";
import { nightModeKey } from "../components/context-keys";
export let value: number; export let value: number;
export let min = 1; export let min = 1;
export let max = 9999; export let max = 9999;
const nightMode = getContext<boolean>(nightModeKey);
function checkMinMax() { function checkMinMax() {
if (value > max) { if (value > max) {
value = max; value = max;
@ -29,7 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{max} {max}
bind:value bind:value
class="spin-box form-control" class="spin-box form-control"
class:nightMode class:nightMode={$pageTheme.isDark}
on:blur={checkMinMax} on:blur={checkMinMax}
/> />

View file

@ -3,8 +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 { getContext } from "svelte"; import { pageTheme } from "../sveltelib/theme";
import { nightModeKey } from "../components/context-keys";
export let value: number; export let value: number;
export let min = 1; export let min = 1;
@ -13,8 +12,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let stringValue: string; let stringValue: string;
$: stringValue = value.toFixed(2); $: stringValue = value.toFixed(2);
const nightMode = getContext<boolean>(nightModeKey);
function update(this: HTMLInputElement): void { function update(this: HTMLInputElement): void {
value = Math.min(max, Math.max(min, parseFloat(this.value))); value = Math.min(max, Math.max(min, parseFloat(this.value)));
} }
@ -23,7 +20,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<input <input
type="number" type="number"
class="form-control" class="form-control"
class:nightMode class:nightMode={$pageTheme.isDark}
{min} {min}
{max} {max}
step="0.01" step="0.01"

View file

@ -3,8 +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 { getContext } from "svelte"; import { pageTheme } from "../sveltelib/theme";
import { nightModeKey } from "../components/context-keys";
import { stepsToString, stringToSteps } from "./steps"; import { stepsToString, stringToSteps } from "./steps";
export let value: number[]; export let value: number[];
@ -12,8 +11,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let stringValue: string; let stringValue: string;
$: stringValue = stepsToString(value); $: stringValue = stepsToString(value);
const nightMode = getContext<boolean>(nightModeKey);
function update(this: HTMLInputElement): void { function update(this: HTMLInputElement): void {
value = stringToSteps(this.value); value = stringToSteps(this.value);
} }
@ -23,7 +20,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
type="text" type="text"
value={stringValue} value={stringValue}
class="form-control" class="form-control"
class:nightMode class:nightMode={$pageTheme.isDark}
on:blur={update} on:blur={update}
/> />

View file

@ -3,14 +3,11 @@ 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 { getContext } from "svelte"; import { pageTheme } from "../sveltelib/theme";
import { nightModeKey } from "../components/context-keys";
export let id: string | undefined; export let id: string | undefined;
export let value: boolean; export let value: boolean;
export let disabled = false; export let disabled = false;
const nightMode = getContext<boolean>(nightModeKey);
</script> </script>
<div class="form-check form-switch"> <div class="form-check form-switch">
@ -18,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{id} {id}
type="checkbox" type="checkbox"
class="form-check-input" class="form-check-input"
class:nightMode class:nightMode={$pageTheme.isDark}
bind:checked={value} bind:checked={value}
{disabled} {disabled}
/> />

View file

@ -4,7 +4,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy, getContext } from "svelte"; import { onMount, onDestroy, getContext } from "svelte";
import { nightModeKey, modalsKey } from "../components/context-keys"; import { modalsKey } from "../components/context-keys";
import { pageTheme } from "../sveltelib/theme";
import Modal from "bootstrap/js/dist/modal"; import Modal from "bootstrap/js/dist/modal";
export let title: string; export let title: string;
@ -40,8 +41,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onDestroy(() => { onDestroy(() => {
modalRef.removeEventListener("shown.bs.modal", onShown); modalRef.removeEventListener("shown.bs.modal", onShown);
}); });
const nightMode = getContext<boolean>(nightModeKey);
</script> </script>
<div <div
@ -52,13 +51,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
aria-hidden="true" aria-hidden="true"
> >
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content" class:default-colors={nightMode}> <div class="modal-content" class:default-colors={$pageTheme.isDark}>
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalLabel">{title}</h5> <h5 class="modal-title" id="modalLabel">{title}</h5>
<button <button
type="button" type="button"
class="btn-close" class="btn-close"
class:invert={nightMode} class:invert={$pageTheme.isDark}
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"
/> />
@ -73,7 +72,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
id="prompt-input" id="prompt-input"
bind:this={inputRef} bind:this={inputRef}
type="text" type="text"
class:nightMode class:nightMode={$pageTheme.isDark}
class="form-control" class="form-control"
bind:value bind:value
/> />

View file

@ -11,7 +11,7 @@ import { getDeckOptionsInfo, DeckOptionsState } from "./lib";
import { setupI18n, ModuleName } from "../lib/i18n"; import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode"; import { checkNightMode } from "../lib/nightmode";
import DeckOptionsPage from "./DeckOptionsPage.svelte"; import DeckOptionsPage from "./DeckOptionsPage.svelte";
import { nightModeKey, touchDeviceKey, modalsKey } from "../components/context-keys"; import { touchDeviceKey, modalsKey } from "../components/context-keys";
export async function deckOptions( export async function deckOptions(
target: HTMLDivElement, target: HTMLDivElement,
@ -29,9 +29,9 @@ export async function deckOptions(
}), }),
]); ]);
checkNightMode();
const context = new Map(); const context = new Map();
const nightMode = checkNightMode();
context.set(nightModeKey, nightMode);
const modals = new Map(); const modals = new Map();
context.set(modalsKey, modals); context.set(modalsKey, modals);

View file

@ -18,8 +18,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</script> </script>
<script lang="ts"> <script lang="ts">
import { onDestroy, getContext } from "svelte"; import { onDestroy } from "svelte";
import { nightModeKey } from "../components/context-keys"; import { pageTheme } from "../sveltelib/theme";
import { convertMathjax } from "./mathjax"; import { convertMathjax } from "./mathjax";
import { randomUUID } from "../lib/uuid"; import { randomUUID } from "../lib/uuid";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
@ -30,8 +30,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let autofocus = false; export let autofocus = false;
export let fontSize = 20; export let fontSize = 20;
const nightMode = getContext<boolean>(nightModeKey); $: [converted, title] = convertMathjax(mathjax, $pageTheme.isDark, fontSize);
$: [converted, title] = convertMathjax(mathjax, nightMode, fontSize);
$: empty = title === "MathJax"; $: empty = title === "MathJax";
$: encoded = encodeURIComponent(converted); $: encoded = encodeURIComponent(converted);

View file

@ -11,7 +11,6 @@ import type { DecoratedElement, DecoratedElementConstructor } from "./decorated"
import { nodeIsElement } from "../lib/dom"; import { nodeIsElement } from "../lib/dom";
import { noop } from "../lib/functional"; import { noop } from "../lib/functional";
import { placeCaretAfter } from "../domlib/place-caret"; import { placeCaretAfter } from "../domlib/place-caret";
import { nightModeKey } from "../components/context-keys";
import Mathjax_svelte from "./Mathjax.svelte"; import Mathjax_svelte from "./Mathjax.svelte";
@ -148,12 +147,6 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
this.innerHTML = ""; this.innerHTML = "";
this.style.whiteSpace = "normal"; this.style.whiteSpace = "normal";
const context = new Map();
context.set(
nightModeKey,
document.documentElement.classList.contains("night-mode"),
);
this.component = new Mathjax_svelte({ this.component = new Mathjax_svelte({
target: this, target: this,
props: { props: {
@ -161,7 +154,6 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
block: this.block, block: this.block,
autofocus: this.hasAttribute("focusonmount"), autofocus: this.hasAttribute("focusonmount"),
}, },
context,
} as any); } as any);
} }

View file

@ -3,8 +3,8 @@ 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 { onMount, createEventDispatcher, getContext } from "svelte"; import { onMount, createEventDispatcher } from "svelte";
import { nightModeKey } from "../components/context-keys"; import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className = ""; let className = "";
@ -12,8 +12,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let selected = false; export let selected = false;
export let active = false; export let active = false;
const nightMode = getContext<boolean>(nightModeKey);
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let buttonRef: HTMLButtonElement; let buttonRef: HTMLButtonElement;
@ -33,8 +31,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{id} {id}
tabindex="-1" tabindex="-1"
class="autocomplete-item btn {className}" class="autocomplete-item btn {className}"
class:btn-day={!nightMode} class:btn-day={!$pageTheme.isDark}
class:btn-night={nightMode} class:btn-night={$pageTheme.isDark}
class:selected class:selected
class:active class:active
on:mousedown|preventDefault on:mousedown|preventDefault

View file

@ -3,8 +3,8 @@ 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 { createEventDispatcher, getContext } from "svelte"; import { createEventDispatcher } from "svelte";
import { nightModeKey } from "../components/context-keys"; import { pageTheme } from "../sveltelib/theme";
export let offsetX = 0; export let offsetX = 0;
export let offsetY = 0; export let offsetY = 0;
@ -13,7 +13,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let activeSize = 5; export let activeSize = 5;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const nightMode = getContext(nightModeKey);
const onPointerdown = const onPointerdown =
(north: boolean, west: boolean) => (north: boolean, west: boolean) =>
@ -26,9 +25,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
class="d-contents" class="d-contents"
style="--offsetX: {offsetX}px; --offsetY: {offsetY}px; --activeSize: {activeSize}px;" style="--offsetX: {offsetX}px; --offsetY: {offsetY}px; --activeSize: {activeSize}px;"
> >
<div class:nightMode class="bordered" on:mousedown|preventDefault />
<div <div
class:nightMode class:nightMode={$pageTheme.isDark}
class="bordered"
on:mousedown|preventDefault
/>
<div
class:nightMode={$pageTheme.isDark}
class:active class:active
class="control nw" class="control nw"
on:mousedown|preventDefault on:mousedown|preventDefault
@ -36,7 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on:pointermove on:pointermove
/> />
<div <div
class:nightMode class:nightMode={$pageTheme.isDark}
class:active class:active
class="control ne" class="control ne"
on:mousedown|preventDefault on:mousedown|preventDefault
@ -44,7 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on:pointermove on:pointermove
/> />
<div <div
class:nightMode class:nightMode={$pageTheme.isDark}
class:active class:active
class="control sw" class="control sw"
on:mousedown|preventDefault on:mousedown|preventDefault
@ -52,7 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on:pointermove on:pointermove
/> />
<div <div
class:nightMode class:nightMode={$pageTheme.isDark}
class:active class:active
class="control se" class="control se"
on:mousedown|preventDefault on:mousedown|preventDefault

View file

@ -7,6 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { EditingInputAPI } from "./EditingArea.svelte"; import type { EditingInputAPI } from "./EditingArea.svelte";
import contextProperty from "../sveltelib/context-property"; import contextProperty from "../sveltelib/context-property";
import type { OnNextInsertTrigger } from "../sveltelib/input-manager"; import type { OnNextInsertTrigger } from "../sveltelib/input-manager";
import { pageTheme } from "../sveltelib/theme";
export interface RichTextInputAPI extends EditingInputAPI { export interface RichTextInputAPI extends EditingInputAPI {
name: "rich-text"; name: "rich-text";
@ -38,7 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import SetContext from "./SetContext.svelte"; import SetContext from "./SetContext.svelte";
import ContentEditable from "../editable/ContentEditable.svelte"; import ContentEditable from "../editable/ContentEditable.svelte";
import { onMount, getContext, getAllContexts } from "svelte"; import { onMount, getAllContexts } from "svelte";
import { import {
nodeIsElement, nodeIsElement,
nodeContainsInlineContent, nodeContainsInlineContent,
@ -53,7 +54,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { on } from "../lib/events"; import { on } from "../lib/events";
import { nodeStore } from "../sveltelib/node-store"; import { nodeStore } from "../sveltelib/node-store";
import type { DecoratedElement } from "../editable/decorated"; import type { DecoratedElement } from "../editable/decorated";
import { nightModeKey } from "../components/context-keys";
export let hidden: boolean; export let hidden: boolean;
@ -235,12 +235,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
unsubscribeToEditingArea(); unsubscribeToEditingArea();
}; };
}); });
const nightMode = getContext<boolean>(nightModeKey);
</script> </script>
<RichTextStyles <RichTextStyles
color={nightMode ? "white" : "black"} color={$pageTheme.isDark ? "white" : "black"}
let:attachToShadow={attachStyles} let:attachToShadow={attachStyles}
let:promise={stylesPromise} let:promise={stylesPromise}
let:stylesDidLoad let:stylesDidLoad

View file

@ -3,8 +3,8 @@ 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 { onMount, getContext, createEventDispatcher } from "svelte"; import { onMount, createEventDispatcher } from "svelte";
import { nightModeKey } from "../components/context-keys"; import { pageTheme } from "../sveltelib/theme";
let className: string = ""; let className: string = "";
export { className as class }; export { className as class };
@ -21,8 +21,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
setTimeout(() => (flashing = false), 300); setTimeout(() => (flashing = false), 300);
} }
const nightMode = getContext<boolean>(nightModeKey);
let button: HTMLButtonElement; let button: HTMLButtonElement;
onMount(() => dispatch("mount", { button })); onMount(() => dispatch("mount", { button }));
@ -33,8 +31,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
class="tag btn d-inline-flex align-items-center text-nowrap ps-2 pe-1 {className}" class="tag btn d-inline-flex align-items-center text-nowrap ps-2 pe-1 {className}"
class:selected class:selected
class:flashing class:flashing
class:btn-day={!nightMode} class:btn-day={!$pageTheme.isDark}
class:btn-night={nightMode} class:btn-night={$pageTheme.isDark}
tabindex="-1" tabindex="-1"
title={tooltip} title={tooltip}
on:mousemove on:mousemove

View file

@ -6,10 +6,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import Tag from "./Tag.svelte"; import Tag from "./Tag.svelte";
import WithTooltip from "../components/WithTooltip.svelte"; import WithTooltip from "../components/WithTooltip.svelte";
import { createEventDispatcher, getContext } from "svelte"; import { createEventDispatcher } from "svelte";
import { nightModeKey } from "../components/context-keys";
import { controlPressed, shiftPressed } from "../lib/keys"; import { controlPressed, shiftPressed } from "../lib/keys";
import { delimChar } from "./tags"; import { delimChar } from "./tags";
import { pageTheme } from "../sveltelib/theme";
export let name: string; export let name: string;
let className: string = ""; let className: string = "";
@ -58,14 +58,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
function hasMultipleParts(name: string): boolean { function hasMultipleParts(name: string): boolean {
return name.split(delimChar).length > 1; return name.split(delimChar).length > 1;
} }
const nightMode = getContext<boolean>(nightModeKey);
const hoverClass = "tag-icon-hover"; const hoverClass = "tag-icon-hover";
</script> </script>
<svelte:body on:keydown={setControlShift} on:keyup={setControlShift} /> <svelte:body on:keydown={setControlShift} on:keyup={setControlShift} />
<div class:select-mode={selectMode} class:night-mode={nightMode}> <div class:select-mode={selectMode} class:night-mode={$pageTheme.isDark}>
{#if active} {#if active}
<Tag class={className} on:mousemove={setControlShift} on:click={onClick}> <Tag class={className} on:mousemove={setControlShift} on:click={onClick}>
{name} {name}

View file

@ -62,16 +62,8 @@ export const i18n = setupI18n({
import OldEditorAdapter from "./OldEditorAdapter.svelte"; import OldEditorAdapter from "./OldEditorAdapter.svelte";
import type { NoteEditorAPI } from "./OldEditorAdapter.svelte"; import type { NoteEditorAPI } from "./OldEditorAdapter.svelte";
import { nightModeKey } from "../components/context-keys";
async function setupNoteEditor(): Promise<NoteEditorAPI> { async function setupNoteEditor(): Promise<NoteEditorAPI> {
const context = new Map<symbol, unknown>();
context.set(
nightModeKey,
document.documentElement.classList.contains("night-mode"),
);
await i18n; await i18n;
const api: Partial<NoteEditorAPI> = {}; const api: Partial<NoteEditorAPI> = {};
@ -79,7 +71,6 @@ async function setupNoteEditor(): Promise<NoteEditorAPI> {
const noteEditor = new OldEditorAdapter({ const noteEditor = new OldEditorAdapter({
target: document.body, target: document.body,
props: { api: api as NoteEditorAPI }, props: { api: api as NoteEditorAPI },
context,
}); });
Object.assign(globalThis, { Object.assign(globalThis, {

View file

@ -5,12 +5,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import type { SvelteComponent } from "svelte/internal"; import type { SvelteComponent } from "svelte/internal";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { pageTheme } from "../sveltelib/theme";
import { bridgeCommand } from "../lib/bridgecommand"; import { bridgeCommand } from "../lib/bridgecommand";
import WithGraphData from "./WithGraphData.svelte"; import WithGraphData from "./WithGraphData.svelte";
export let nightMode: boolean;
export let graphs: SvelteComponent[]; export let graphs: SvelteComponent[];
export let initialSearch: string; export let initialSearch: string;
@ -45,7 +45,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{sourceData} {sourceData}
{preferences} {preferences}
{revlogRange} {revlogRange}
{nightMode} nightMode={$pageTheme.isDark}
on:search={browserSearch} on:search={browserSearch}
/> />
{/each} {/each}

View file

@ -31,14 +31,12 @@ export function graphs(
controller = null as SvelteComponent | null, controller = null as SvelteComponent | null,
} = {}, } = {},
): void { ): void {
const nightMode = checkNightMode(); checkNightMode();
setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(() => { setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(() => {
new GraphsPage({ new GraphsPage({
target, target,
props: { props: {
graphs, graphs,
nightMode,
initialSearch: search, initialSearch: search,
initialDays: days, initialDays: days,
controller, controller,

View file

@ -1,8 +1,8 @@
// Copyright: Ankitects Pty Ltd and contributors // 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
/// Add night-mode class to body if hash location is #night, and return /// Add night-mode class to documentElement if hash location is #night, and
/// true if added. /// return true if added.
export function checkNightMode(): boolean { export function checkNightMode(): boolean {
const nightMode = window.location.hash == "#night"; const nightMode = window.location.hash == "#night";
if (nightMode) { if (nightMode) {

35
ts/sveltelib/theme.ts Normal file
View file

@ -0,0 +1,35 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { readable, get } from "svelte/store";
import { registerPackage } from "../lib/register-package";
interface ThemeInfo {
isDark: boolean;
}
function getThemeFromRoot(): ThemeInfo {
return {
isDark: document.documentElement.classList.contains("night-mode"),
};
}
let setPageTheme: ((theme: ThemeInfo) => void) | null = null;
/// The current theme that applies to this document/shadow root. When
/// previewing cards in the card layout screen, this may not match the
/// theme Anki is using in its UI.
export const pageTheme = readable(getThemeFromRoot(), (set) => {
setPageTheme = set;
});
// ensure setPageTheme is set immediately
get(pageTheme);
// Update theme when root element's class changes.
const observer = new MutationObserver((_mutationsList, _observer) => {
setPageTheme!(getThemeFromRoot());
});
observer.observe(document.documentElement, { attributeFilter: ["class"] });
registerPackage("anki/theme", {
pageTheme,
});