diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 9d2a4f3e..5c89236f 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -78,7 +78,7 @@ export class SettingsViewModel extends ViewModel { this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); if (!import.meta.env.DEV) { - this._activeTheme = await this.platform.getActiveTheme(); + this._activeTheme = await this.platform.themeLoader.getActiveTheme(); } this.emitChange(""); } @@ -132,7 +132,7 @@ export class SettingsViewModel extends ViewModel { } get themes() { - return this.platform.themes; + return this.platform.themeLoader.themes; } get activeTheme() { @@ -140,8 +140,7 @@ export class SettingsViewModel extends ViewModel { } setTheme(name) { - this.platform.setTheme(name); - this.platform.settingsStorage.setString("theme", name); + this.platform.themeLoader.setTheme(name); } _formatBytes(n) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7b803e24..2481d256 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,6 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; +import {ThemeLoader} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -164,8 +165,7 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; - // Mapping from theme-name to asset hashed location of css file - this._themeMapping = {}; + this._themeLoader = new ThemeLoader(this); } async init() { @@ -180,25 +180,9 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); - this._themeMapping = await this._createThemeMappingFromManifests(); - await this._loadThemeFromSetting(); - } - - async _createThemeMappingFromManifests() { - const mapping = {}; const manifests = this.config["themeManifests"]; - for (const manifestLocation of manifests) { - const {body}= await this.request(manifestLocation, {method: "GET", format: "json", cache: true}).response(); - Object.assign(mapping, body["source"]["built-asset"]); - } - return mapping; - } - - async _loadThemeFromSetting() { - const themeName = await this.settingsStorage.getString("theme"); - if (themeName) { - this.setTheme(themeName); - } + await this._themeLoader.init(manifests); + await this._themeLoader.loadThemeFromSetting(); } _createLogger(isDevelopment) { @@ -328,36 +312,11 @@ export class Platform { return DEFINE_VERSION; } - get themes() { - return Object.keys(this._themeMapping); + get themeLoader() { + return this._themeLoader; } - async getActiveTheme() { - // check if theme is set via settings - let theme = await this.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia) { - if (window.matchMedia("(prefers-color-scheme: dark)")) { - return this.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { - return this.config["defaultTheme"].light; - } - } - return undefined; - } - - setTheme(themeName) { - const themeLocation = this._themeMapping[themeName]; - if (!themeLocation) { - throw new Error(`Cannot find theme location for theme "${themeName}"!`); - } - this._replaceStylesheet(themeLocation); - } - - _replaceStylesheet(newPath) { + replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme document.querySelectorAll(".theme").forEach(e => e.remove()); diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts new file mode 100644 index 00000000..6c81a4d6 --- /dev/null +++ b/src/platform/web/ThemeLoader.ts @@ -0,0 +1,76 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import type {Platform} from "./Platform.js"; + +export class ThemeLoader { + private _platform: Platform; + private _themeMapping: Record = {}; + + constructor(platform: Platform) { + this._platform = platform; + } + + async init(manifestLocations: Iterable>): Promise { + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + Object.assign(this._themeMapping, body["source"]["built-asset"]); + } + } + + async loadThemeFromSetting() { + const themeName = await this._platform.settingsStorage.getString( "theme"); + if (themeName) { + this.setTheme(themeName); + } + } + + setTheme(themeName: string) { + const themeLocation = this._themeMapping[themeName]; + if (!themeLocation) { + throw new Error( `Cannot find theme location for theme "${themeName}"!`); + } + this._platform.replaceStylesheet(themeLocation); + this._platform.settingsStorage.setString("theme", themeName); + } + + get themes(): string[] { + return Object.keys(this._themeMapping); + } + + async getActiveTheme(): Promise { + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia) { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return this._platform.config["defaultTheme"].light; + } + } + return undefined; + } +} diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index a6b3e363..69827c33 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,8 +97,8 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.if(vm => vm.activeTheme, (theme, t) => { - return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; + t.if(vm => vm.activeTheme, (t, vm) => { + return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)): null; }), ); settingNodes.push( @@ -139,7 +139,8 @@ export class SettingsView extends TemplateView { })]; } - _themeOptions(t, vm, activeTheme) { + _themeOptions(t, vm) { + const activeTheme = vm.activeTheme; const optionTags = []; for (const name of vm.themes) { optionTags.push(t.option({value: name, selected: name === activeTheme}, name));