From f16a2e5d22abcea46f33aa162634f1bf514257d2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 18 Apr 2022 16:17:56 +0530 Subject: [PATCH 01/33] Don't add asset hash to manifest json on build --- vite.config.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vite.config.js b/vite.config.js index 87e3d063..a44d0917 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,25 +16,33 @@ export default defineConfig(({mode}) => { sourcemap: true, rollupOptions: { output: { - assetFileNames: (asset) => asset.name.includes("config.json") ? "assets/[name][extname]": "assets/[name].[hash][extname]", + assetFileNames: (asset) => + asset.name.includes("config.json") || + asset.name.match(/theme-.+\.json/) + ? "assets/[name][extname]" + : "assets/[name].[hash][extname]", }, }, }, plugins: [ themeBuilder({ themeConfig: { - themes: {"element": "./src/platform/web/ui/css/themes/element"}, + themes: { + element: "./src/platform/web/ui/css/themes/element", + }, default: "element", }, - compiledVariables + compiledVariables, }), // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), injectServiceWorker("./src/platform/web/sw.js", ["index.html"], { // placeholders to replace at end of build by chunk name - "index": {DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH}, - "sw": definePlaceholders + index: { + DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, + }, + sw: definePlaceholders, }), ], define: definePlaceholders, From 541cd96eeb3ef9d8ab0174b51bc89b2e98659434 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 18 Apr 2022 16:49:11 +0530 Subject: [PATCH 02/33] Add script to cleanup after build --- package.json | 2 +- scripts/cleanup.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100755 scripts/cleanup.sh diff --git a/package.json b/package.json index 5b53790e..679000e1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", "test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js", "start": "vite --port 3000", - "build": "vite build", + "build": "vite build && ./scripts/cleanup.sh", "build:sdk": "./scripts/sdk/build.sh" }, "repository": { diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 00000000..cdad04eb --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Remove icons created in .tmp +rm -rf .tmp From cc2c74fdff151683cda8b2287de875be4ef620e0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 19 Apr 2022 12:16:02 +0530 Subject: [PATCH 03/33] Generate theme summary on build --- .../build-plugins/rollup-plugin-build-themes.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index e7a2bb2b..01969b1a 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -231,6 +231,7 @@ module.exports = function buildThemes(options) { generateBundle(_, bundle) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + const themeSummary = {}; for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); const compiledVariables = options.compiledVariables.get(location); @@ -249,6 +250,22 @@ module.exports = function buildThemes(options) { source: JSON.stringify(manifest), }); } + /** + * Generate a mapping from theme name to asset hashed location of said theme in build output. + * This can be used to enumerate themes during runtime. + */ + for (const [, chunkArray] of chunkMap) { + chunkArray.forEach((chunk) => { + const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); + const assetHashedFileName = assetMap.get(chunk.fileName).fileName; + themeSummary[`${name}-${variant}`] = assetHashedFileName; + }); + } + this.emitFile({ + type: "asset", + name: "theme-summary.json", + source: JSON.stringify(themeSummary), + }); }, } } From daae7442bb0175edf02dc6bc3e1030e4d4701dee Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 14:29:31 +0530 Subject: [PATCH 04/33] Create theme chooser --- .../rollup-plugin-build-themes.js | 14 ++++++++++ .../session/settings/SettingsViewModel.js | 8 ++++++ src/platform/web/Platform.js | 27 +++++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 9 +++++++ 4 files changed, 58 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 01969b1a..805590f4 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,6 +31,17 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } +function addThemesToConfig(bundle, themeSummary) { + for (const [fileName, info] of Object.entries(bundle)) { + if (fileName === "assets/config.json") { + const source = new TextDecoder().decode(info.source); + const config = JSON.parse(source); + config["themes"] = themeSummary; + info.source = new TextEncoder().encode(JSON.stringify(config)); + } + } +} + function parseBundle(bundle) { const chunkMap = new Map(); const assetMap = new Map(); @@ -215,6 +226,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: dark)", href: `./${darkThemeLocation}`, + class: "default-theme", } }, { @@ -224,6 +236,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: light)", href: `./${lightThemeLocation}`, + class: "default-theme", } }, ]; @@ -261,6 +274,7 @@ module.exports = function buildThemes(options) { themeSummary[`${name}-${variant}`] = assetHashedFileName; }); } + addThemesToConfig(bundle, themeSummary); this.emitFile({ type: "asset", name: "theme-summary.json", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 7464a659..649983e5 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -127,6 +127,14 @@ export class SettingsViewModel extends ViewModel { return this._formatBytes(this._estimate?.usage); } + get themes() { + return this.platform.themes; + } + + setTheme(name) { + this.platform.setTheme(name); + } + _formatBytes(n) { if (typeof n === "number") { return Math.round(n / (1024 * 1024)).toFixed(1) + " MB"; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7d66301d..6ed4509d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -307,6 +307,33 @@ export class Platform { return DEFINE_VERSION; } + get themes() { + return Object.keys(this.config["themes"]); + } + + setTheme(themeName) { + const themeLocation = this.config["themes"][themeName]; + if (!themeLocation) { + throw new Error(`Cannot find theme location for theme "${themeName}"!`); + } + this._replaceStylesheet(themeLocation); + } + + _replaceStylesheet(newPath) { + // remove default theme + const defaultStylesheets = document.getElementsByClassName("default-theme"); + for (const tag of defaultStylesheets) { + tag.remove(); + } + // add new theme + const head = document.querySelector("head"); + const styleTag = document.createElement("link"); + styleTag.href = `./${newPath}`; + styleTag.rel = "stylesheet"; + styleTag.type = "text/css"; + head.appendChild(styleTag); + } + dispose() { this._disposables.dispose(); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 93e44307..b2c78f58 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,6 +97,7 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), + row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)), ); settingNodes.push( t.h3("Application"), @@ -135,4 +136,12 @@ export class SettingsView extends TemplateView { vm.i18n`no resizing`; })]; } + + _themeOptions(t, vm) { + const optionTags = []; + for (const name of vm.themes) { + optionTags.push(t.option({value: name}, name)); + } + return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); + } } From ecb83bb277d036767eb3ded9053a2979454dd3b4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 14:47:16 +0530 Subject: [PATCH 05/33] Store and load theme from setting --- src/domain/session/settings/SettingsViewModel.js | 1 + src/platform/web/Platform.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 649983e5..0bdb1355 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -133,6 +133,7 @@ export class SettingsViewModel extends ViewModel { setTheme(name) { this.platform.setTheme(name); + this.platform.settingsStorage.setString("theme", name); } _formatBytes(n) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 6ed4509d..d1b96909 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -178,6 +178,14 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); + await this._loadThemeFromSetting(); + } + + async _loadThemeFromSetting() { + const themeName = await this.settingsStorage.getString("theme"); + if (themeName) { + this.setTheme(themeName); + } } _createLogger(isDevelopment) { From c611d3f85c2ceab9967d4d8109df100b07ff4a41 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 15:56:45 +0530 Subject: [PATCH 06/33] Select current theme in dropdown --- .../build-plugins/rollup-plugin-build-themes.js | 9 ++++++--- .../session/settings/SettingsViewModel.js | 6 ++++++ src/platform/web/Platform.js | 17 +++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 8 +++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 805590f4..f36db855 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,12 +31,13 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } -function addThemesToConfig(bundle, themeSummary) { +function addThemesToConfig(bundle, themeSummary, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { if (fileName === "assets/config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); config["themes"] = themeSummary; + config["defaultTheme"] = defaultThemes; info.source = new TextEncoder().encode(JSON.stringify(config)); } } @@ -83,7 +84,7 @@ function parseBundle(bundle) { } module.exports = function buildThemes(options) { - let manifest, variants, defaultDark, defaultLight; + let manifest, variants, defaultDark, defaultLight,defaultThemes = {}; let isDevelopment = false; const virtualModuleId = '@theme/' const resolvedVirtualModuleId = '\0' + virtualModuleId; @@ -110,9 +111,11 @@ module.exports = function buildThemes(options) { // This is the default theme, stash the file name for later if (details.dark) { defaultDark = fileName; + defaultThemes["dark"] = `${name}-${variant}`; } else { defaultLight = fileName; + defaultThemes["light"] = `${name}-${variant}`; } } // emit the css as built theme bundle @@ -274,7 +277,7 @@ module.exports = function buildThemes(options) { themeSummary[`${name}-${variant}`] = assetHashedFileName; }); } - addThemesToConfig(bundle, themeSummary); + addThemesToConfig(bundle, themeSummary, defaultThemes); this.emitFile({ type: "asset", name: "theme-summary.json", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 0bdb1355..083c209e 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -50,6 +50,7 @@ export class SettingsViewModel extends ViewModel { this.minSentImageSizeLimit = 400; this.maxSentImageSizeLimit = 4000; this.pushNotifications = new PushNotificationStatus(); + this._activeTheme = undefined; } get _session() { @@ -76,6 +77,7 @@ export class SettingsViewModel extends ViewModel { this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); + this._activeTheme = await this.platform.getActiveTheme(); this.emitChange(""); } @@ -131,6 +133,10 @@ export class SettingsViewModel extends ViewModel { return this.platform.themes; } + get activeTheme() { + return this._activeTheme; + } + setTheme(name) { this.platform.setTheme(name); this.platform.settingsStorage.setString("theme", name); diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index d1b96909..415081ba 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -319,6 +319,23 @@ export class Platform { return Object.keys(this.config["themes"]); } + 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.config["themes"][themeName]; if (!themeLocation) { diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index b2c78f58..3e62bba6 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,7 +97,9 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)), + t.map(vm => vm.activeTheme, (theme, t) => { + return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)); + }), ); settingNodes.push( t.h3("Application"), @@ -137,10 +139,10 @@ export class SettingsView extends TemplateView { })]; } - _themeOptions(t, vm) { + _themeOptions(t, vm, activeTheme) { const optionTags = []; for (const name of vm.themes) { - optionTags.push(t.option({value: name}, name)); + optionTags.push(t.option({value: name, selected: name === activeTheme}, name)); } return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); } From 12a70469eb963b4ea01f442a603fce8282240afa Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 16:01:02 +0530 Subject: [PATCH 07/33] Fix formatting --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index f36db855..9776fb92 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -84,7 +84,7 @@ function parseBundle(bundle) { } module.exports = function buildThemes(options) { - let manifest, variants, defaultDark, defaultLight,defaultThemes = {}; + let manifest, variants, defaultDark, defaultLight, defaultThemes = {}; let isDevelopment = false; const virtualModuleId = '@theme/' const resolvedVirtualModuleId = '\0' + virtualModuleId; From af9cbd727f017ffd6d3b420938ec0023f5d535cc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 16:33:31 +0530 Subject: [PATCH 08/33] Remove existing stylesheets when changing themes --- scripts/build-plugins/rollup-plugin-build-themes.js | 4 ++-- src/platform/web/Platform.js | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 9776fb92..f429bc80 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -229,7 +229,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: dark)", href: `./${darkThemeLocation}`, - class: "default-theme", + class: "theme", } }, { @@ -239,7 +239,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: light)", href: `./${lightThemeLocation}`, - class: "default-theme", + class: "theme", } }, ]; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 415081ba..24480f59 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -345,17 +345,15 @@ export class Platform { } _replaceStylesheet(newPath) { - // remove default theme - const defaultStylesheets = document.getElementsByClassName("default-theme"); - for (const tag of defaultStylesheets) { - tag.remove(); - } - // add new theme const head = document.querySelector("head"); + // remove default theme + document.querySelectorAll(".theme").forEach(e => e.remove()); + // add new theme const styleTag = document.createElement("link"); styleTag.href = `./${newPath}`; styleTag.rel = "stylesheet"; styleTag.type = "text/css"; + styleTag.className = "theme"; head.appendChild(styleTag); } From bb3368959ffd36782261a3dc1cea64e2f14ee6c8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 17:24:58 +0530 Subject: [PATCH 09/33] Use sh instead of bash --- scripts/cleanup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh index cdad04eb..6917af5e 100755 --- a/scripts/cleanup.sh +++ b/scripts/cleanup.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh # Remove icons created in .tmp rm -rf .tmp From c39f0d2efb5a39ee2b34998cb996a12dd82e845e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 17:49:16 +0530 Subject: [PATCH 10/33] Don't show theme chooser on dev --- src/domain/session/settings/SettingsViewModel.js | 4 +++- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 083c209e..9d2a4f3e 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -77,7 +77,9 @@ export class SettingsViewModel extends ViewModel { this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); - this._activeTheme = await this.platform.getActiveTheme(); + if (!import.meta.env.DEV) { + this._activeTheme = await this.platform.getActiveTheme(); + } this.emitChange(""); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 3e62bba6..eef3bc64 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -93,12 +93,12 @@ export class SettingsView extends TemplateView { ]); }) ); - + settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), t.map(vm => vm.activeTheme, (theme, t) => { - return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)); + return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; }), ); settingNodes.push( From 5204fe5c99a6ad576c94718e9846351116e070e0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 May 2022 14:22:37 +0530 Subject: [PATCH 11/33] This emitFile is no longer needed --- scripts/build-plugins/rollup-plugin-build-themes.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index f429bc80..871e8fab 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -278,11 +278,6 @@ module.exports = function buildThemes(options) { }); } addThemesToConfig(bundle, themeSummary, defaultThemes); - this.emitFile({ - type: "asset", - name: "theme-summary.json", - source: JSON.stringify(themeSummary), - }); }, } } From e8a4ab5ecc37dcb6150fb7573da12a773ea2ac72 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 May 2022 16:58:06 +0530 Subject: [PATCH 12/33] built-asset must be a mapping A mapping from theme-name to location of css file --- scripts/build-plugins/rollup-plugin-build-themes.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 871e8fab..d3733e01 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -253,8 +253,13 @@ module.exports = function buildThemes(options) { const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; + const builtAsset = {}; + for (const chunk of chunkArray) { + const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); + builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + } manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "built-asset": builtAsset, "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, "icon": icon From 855298bdaf7b8eaa7ae08d0dc154f524709df8f4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 12:40:32 +0530 Subject: [PATCH 13/33] Read from manifest --- .../rollup-plugin-build-themes.js | 24 +++++++------------ src/platform/web/Platform.js | 17 +++++++++++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index d3733e01..e9251224 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,12 +31,12 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } -function addThemesToConfig(bundle, themeSummary, defaultThemes) { +function addThemesToConfig(bundle, manifestLocations, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { if (fileName === "assets/config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); - config["themes"] = themeSummary; + config["themeManifests"] = manifestLocations; config["defaultTheme"] = defaultThemes; info.source = new TextEncoder().encode(JSON.stringify(config)); } @@ -247,13 +247,17 @@ module.exports = function buildThemes(options) { generateBundle(_, bundle) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - const themeSummary = {}; + const manifestLocations = []; for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; const builtAsset = {}; + /** + * Generate a mapping from theme name to asset hashed location of said theme in build output. + * This can be used to enumerate themes during runtime. + */ for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; @@ -265,24 +269,14 @@ module.exports = function buildThemes(options) { "icon": icon }; const name = `theme-${manifest.name}.json`; + manifestLocations.push(`assets/${name}`); this.emitFile({ type: "asset", name, source: JSON.stringify(manifest), }); } - /** - * Generate a mapping from theme name to asset hashed location of said theme in build output. - * This can be used to enumerate themes during runtime. - */ - for (const [, chunkArray] of chunkMap) { - chunkArray.forEach((chunk) => { - const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - const assetHashedFileName = assetMap.get(chunk.fileName).fileName; - themeSummary[`${name}-${variant}`] = assetHashedFileName; - }); - } - addThemesToConfig(bundle, themeSummary, defaultThemes); + addThemesToConfig(bundle, manifestLocations, defaultThemes); }, } } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 24480f59..7b803e24 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -164,6 +164,8 @@ 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 = {}; } async init() { @@ -178,9 +180,20 @@ 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) { @@ -316,7 +329,7 @@ export class Platform { } get themes() { - return Object.keys(this.config["themes"]); + return Object.keys(this._themeMapping); } async getActiveTheme() { @@ -337,7 +350,7 @@ export class Platform { } setTheme(themeName) { - const themeLocation = this.config["themes"][themeName]; + const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error(`Cannot find theme location for theme "${themeName}"!`); } From 213f87378b5b9bd7e3fa573cf4c4e599ea974dfd Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 12:46:12 +0530 Subject: [PATCH 14/33] Use t.if instead of t.map --- src/platform/web/ui/session/settings/SettingsView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index eef3bc64..a6b3e363 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,7 +97,7 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.map(vm => vm.activeTheme, (theme, t) => { + 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; }), ); From 2761789f452eed2457b257037534076986a69f0a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 14:58:14 +0530 Subject: [PATCH 15/33] Move theme code to separate file --- .../session/settings/SettingsViewModel.js | 7 +- src/platform/web/Platform.js | 55 ++------------ src/platform/web/ThemeLoader.ts | 76 +++++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 7 +- 4 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 src/platform/web/ThemeLoader.ts 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)); From c26dc04b520c16f1e0de3c23df2da983ce8e0525 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:03:32 +0530 Subject: [PATCH 16/33] Fix type --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 6c81a4d6..256aed54 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -24,7 +24,7 @@ export class ThemeLoader { this._platform = platform; } - async init(manifestLocations: Iterable>): Promise { + async init(manifestLocations: string[]): Promise { for (const manifestLocation of manifestLocations) { const { body } = await this._platform .request(manifestLocation, { From 174adc075573716d2c697b4ac8af55d144714c47 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:38:37 +0530 Subject: [PATCH 17/33] Move platform dependent code to Platform --- src/platform/web/Platform.js | 11 ++++++++++- src/platform/web/ThemeLoader.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 2481d256..e2628351 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,7 +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"; +import {ThemeLoader, COLOR_SCHEME_PREFERENCE} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -316,6 +316,15 @@ export class Platform { return this._themeLoader; } + get preferredColorScheme() { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return COLOR_SCHEME_PREFERENCE.DARK; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return COLOR_SCHEME_PREFERENCE.LIGHT; + } + return undefined; + } + replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 256aed54..713c7a62 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -16,6 +16,8 @@ limitations under the License. import type {Platform} from "./Platform.js"; +export enum COLOR_SCHEME_PREFERENCE { DARK, LIGHT, } + export class ThemeLoader { private _platform: Platform; private _themeMapping: Record = {}; @@ -64,12 +66,12 @@ export class ThemeLoader { return theme; } // return default theme - if (window.matchMedia) { - if (window.matchMedia("(prefers-color-scheme: dark)")) { + const preference = this._platform.preferredColorScheme; + switch (preference) { + case COLOR_SCHEME_PREFERENCE.DARK: return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { + case COLOR_SCHEME_PREFERENCE.LIGHT: return this._platform.config["defaultTheme"].light; - } } return undefined; } From cc88245933596f55c787b0bc028dbf33bb2518e4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:46:12 +0530 Subject: [PATCH 18/33] Create themeLoader only if not dev --- src/platform/web/Platform.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e2628351..ed117b3d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -165,7 +165,7 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; - this._themeLoader = new ThemeLoader(this); + this._themeLoader = import.meta.env.DEV? null: new ThemeLoader(this); } async init() { @@ -181,8 +181,8 @@ export class Platform { this._config.push ); const manifests = this.config["themeManifests"]; - await this._themeLoader.init(manifests); - await this._themeLoader.loadThemeFromSetting(); + await this._themeLoader?.init(manifests); + await this._themeLoader?.loadThemeFromSetting(); } _createLogger(isDevelopment) { From d5bc9f5d7d340c62d2fe7cf33d9fbb0f8219b57c Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Thu, 12 May 2022 12:48:34 +0530 Subject: [PATCH 19/33] Update src/platform/web/Platform.js Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index ed117b3d..e222691b 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -317,7 +317,7 @@ export class Platform { } get preferredColorScheme() { - if (window.matchMedia("(prefers-color-scheme: dark)")) { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return COLOR_SCHEME_PREFERENCE.DARK; } else if (window.matchMedia("(prefers-color-scheme: light)")) { return COLOR_SCHEME_PREFERENCE.LIGHT; From 4231037345708677a85fdc4d43425098910eb623 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Thu, 12 May 2022 12:48:41 +0530 Subject: [PATCH 20/33] Update src/platform/web/Platform.js Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e222691b..5a921aaf 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -319,7 +319,7 @@ export class Platform { get preferredColorScheme() { if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return COLOR_SCHEME_PREFERENCE.DARK; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { return COLOR_SCHEME_PREFERENCE.LIGHT; } return undefined; From b3063447399812d22e7eddeaaee61223f352c219 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 12:55:08 +0530 Subject: [PATCH 21/33] Add explaining comment --- src/platform/web/ThemeLoader.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 713c7a62..ab799b0a 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -35,6 +35,11 @@ export class ThemeLoader { cache: true, }) .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-asset` which is a mapping from the theme-name to the + location of the css file in build. + */ Object.assign(this._themeMapping, body["source"]["built-asset"]); } } From 654e83a5f98d18c5e5d1339be677e3b8e81fded2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:28:11 +0530 Subject: [PATCH 22/33] Remove method --- src/platform/web/Platform.js | 2 +- src/platform/web/ThemeLoader.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 5a921aaf..662525d8 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -182,7 +182,7 @@ export class Platform { ); const manifests = this.config["themeManifests"]; await this._themeLoader?.init(manifests); - await this._themeLoader?.loadThemeFromSetting(); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); } _createLogger(isDevelopment) { diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index ab799b0a..34b3c7d0 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -44,13 +44,6 @@ export class ThemeLoader { } } - 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) { From 0984aeb5708c3fe0cbbc925055ab8d2c42b0d3a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:39:57 +0530 Subject: [PATCH 23/33] Move code to ThemeLoader --- src/platform/web/Platform.js | 9 --------- src/platform/web/ThemeLoader.ts | 10 ++++------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 662525d8..a142ace7 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -316,15 +316,6 @@ export class Platform { return this._themeLoader; } - get preferredColorScheme() { - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return COLOR_SCHEME_PREFERENCE.DARK; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return COLOR_SCHEME_PREFERENCE.LIGHT; - } - return undefined; - } - replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 34b3c7d0..4c0ec6d3 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -64,12 +64,10 @@ export class ThemeLoader { return theme; } // return default theme - const preference = this._platform.preferredColorScheme; - switch (preference) { - case COLOR_SCHEME_PREFERENCE.DARK: - return this._platform.config["defaultTheme"].dark; - case COLOR_SCHEME_PREFERENCE.LIGHT: - return this._platform.config["defaultTheme"].light; + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; } return undefined; } From e63440527a99172b31b684e9f98551e171674342 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:43:19 +0530 Subject: [PATCH 24/33] Move condition to binding --- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 69827c33..dd7bbc03 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, (t, vm) => { - return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)): null; + t.if(vm => !import.meta.env.DEV && vm.activeTheme, (t, vm) => { + return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)); }), ); settingNodes.push( From 4ddfd3b5086d2fcc247bc14cae8386a8302ef6a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 14:31:28 +0530 Subject: [PATCH 25/33] built-asset --> built-assets --- scripts/build-plugins/rollup-plugin-build-themes.js | 6 +++--- src/platform/web/ThemeLoader.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index e9251224..c45c5aaa 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -253,17 +253,17 @@ module.exports = function buildThemes(options) { const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; - const builtAsset = {}; + const builtAssets = {}; /** * Generate a mapping from theme name to asset hashed location of said theme in build output. * This can be used to enumerate themes during runtime. */ for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; } manifest.source = { - "built-asset": builtAsset, + "built-assets": builtAssets, "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, "icon": icon diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 4c0ec6d3..0234a04a 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -37,10 +37,10 @@ export class ThemeLoader { .response(); /* After build has finished, the source section of each theme manifest - contains `built-asset` which is a mapping from the theme-name to the + contains `built-assets` which is a mapping from the theme-name to the location of the css file in build. */ - Object.assign(this._themeMapping, body["source"]["built-asset"]); + Object.assign(this._themeMapping, body["source"]["built-assets"]); } } From 9ba153439026535cf28b3301537fbf9c442e608d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 16:03:06 +0530 Subject: [PATCH 26/33] Remove unused import --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index a142ace7..02990ea0 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,7 +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, COLOR_SCHEME_PREFERENCE} from "./ThemeLoader"; +import {ThemeLoader} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { From 1b22a48b5415ddb8803c1d1200d2d201259231a4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 14:23:41 +0530 Subject: [PATCH 27/33] Treat theme-manifests the same way as config --- src/platform/web/sw.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index a9a92979..ce83e8c2 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -95,8 +95,8 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { - if (request.url.includes("config.json")) { - return handleConfigRequest(request); + if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { + return handleSpecialRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache @@ -123,9 +123,13 @@ async function handleRequest(request) { } } -async function handleConfigRequest(request) { +/** + * For some files (config.json and theme manifests) we satisfy the request from cache, + * but at the same time we refresh the cache with up-to-date content of the file + */ +async function handleSpecialRequest(request) { let response = await readCache(request); - const networkResponsePromise = fetchAndUpdateConfig(request); + const networkResponsePromise = fetchAndUpdateCache(request); if (response) { return response; } else { @@ -133,7 +137,7 @@ async function handleConfigRequest(request) { } } -async function fetchAndUpdateConfig(request) { +async function fetchAndUpdateCache(request) { const response = await fetch(request, { signal: pendingFetchAbortController.signal, headers: { From 660a08db3eb967b3adb9fc96549bef27ba989359 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 14:41:52 +0530 Subject: [PATCH 28/33] Give a better name --- src/platform/web/sw.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index ce83e8c2..6b07f42e 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -96,7 +96,7 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { - return handleSpecialRequest(request); + return handleStaleWhileRevalidateRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache @@ -124,10 +124,10 @@ async function handleRequest(request) { } /** - * For some files (config.json and theme manifests) we satisfy the request from cache, - * but at the same time we refresh the cache with up-to-date content of the file + * Stale-while-revalidate caching for certain files + * see https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate */ -async function handleSpecialRequest(request) { +async function handleStaleWhileRevalidateRequest(request) { let response = await readCache(request); const networkResponsePromise = fetchAndUpdateCache(request); if (response) { From 7426d17e33784baaab4daf219374163d37712f89 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 15:42:49 +0530 Subject: [PATCH 29/33] Precache config and theme manifest --- vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index a44d0917..362f8f81 100644 --- a/vite.config.js +++ b/vite.config.js @@ -37,7 +37,7 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", ["index.html"], { + injectServiceWorker("./src/platform/web/sw.js", ["index.html", "assets/config.json", "assets/theme-element.json"], { // placeholders to replace at end of build by chunk name index: { DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, From 7952a34d64d43cbd56ec789909ee76a1063efc51 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 16:09:09 +0530 Subject: [PATCH 30/33] Add logging --- src/platform/web/Platform.js | 28 +++++++------ src/platform/web/ThemeLoader.ts | 72 ++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 02990ea0..25439ad0 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,20 +169,22 @@ export class Platform { } async init() { - if (!this._config) { - if (!this._configURL) { - throw new Error("Neither config nor configURL was provided!"); + await this.logger.run("Platform init", async () => { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); + this._config = body; } - const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); - this._config = body; - } - this.notificationService = new NotificationService( - this._serviceWorkerHandler, - this._config.push - ); - const manifests = this.config["themeManifests"]; - await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + this.notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + const manifests = this.config["themeManifests"]; + await this._themeLoader?.init(manifests); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + }); } _createLogger(isDevelopment) { diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 0234a04a..5d93ad68 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -27,30 +27,34 @@ export class ThemeLoader { } async init(manifestLocations: string[]): Promise { - for (const manifestLocation of manifestLocations) { - const { body } = await this._platform - .request(manifestLocation, { - method: "GET", - format: "json", - cache: true, - }) - .response(); - /* - After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. - */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); - } + await this._platform.logger.run("ThemeLoader.init", async () => { + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-assets` which is a mapping from the theme-name to the + location of the css file in build. + */ + Object.assign(this._themeMapping, body["source"]["built-assets"]); + } + }); } 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); + this._platform.logger.run("ThemeLoader.setTheme", () => { + 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[] { @@ -58,17 +62,19 @@ export class ThemeLoader { } 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("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; - } - return undefined; + return await this._platform.logger.run("ThemeLoader.getActiveTheme", async () => { + // 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("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; + } + return undefined; + }); } } From 683ffa9ed376ae3d5197c6dda5789db2588b33dc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 17:31:17 +0530 Subject: [PATCH 31/33] injectServiceWorker plugin should accept callback --- scripts/build-plugins/service-worker.js | 3 ++- vite.config.js | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/build-plugins/service-worker.js b/scripts/build-plugins/service-worker.js index 805f6000..85619545 100644 --- a/scripts/build-plugins/service-worker.js +++ b/scripts/build-plugins/service-worker.js @@ -8,7 +8,7 @@ function contentHash(str) { return hasher.digest(); } -function injectServiceWorker(swFile, otherUnhashedFiles, placeholdersPerChunk) { +function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholdersPerChunk) { const swName = path.basename(swFile); let root; let version; @@ -31,6 +31,7 @@ function injectServiceWorker(swFile, otherUnhashedFiles, placeholdersPerChunk) { logger = config.logger; }, generateBundle: async function(options, bundle) { + const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle); const unhashedFilenames = [swName].concat(otherUnhashedFiles); const unhashedFileContentMap = unhashedFilenames.reduce((map, fileName) => { const chunkOrAsset = bundle[fileName]; diff --git a/vite.config.js b/vite.config.js index 362f8f81..53a7f452 100644 --- a/vite.config.js +++ b/vite.config.js @@ -37,7 +37,7 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", ["index.html", "assets/config.json", "assets/theme-element.json"], { + injectServiceWorker("./src/platform/web/sw.js", findUnhashedFileNamesFromBundle, { // placeholders to replace at end of build by chunk name index: { DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, @@ -48,3 +48,16 @@ export default defineConfig(({mode}) => { define: definePlaceholders, }); }); + +function findUnhashedFileNamesFromBundle(bundle) { + const names = ["index.html"]; + for (const fileName of Object.keys(bundle)) { + if (fileName.includes("config.json")) { + names.push(fileName); + } + if (/theme-.+\.json/.test(fileName)) { + names.push(fileName); + } + } + return names; +} From a550788788b16aed4a7c29dfeace5800355eb40c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 18:56:28 +0530 Subject: [PATCH 32/33] Remove some logging + use wrapOrRun --- src/platform/web/Platform.js | 4 +- src/platform/web/ThemeLoader.ts | 65 +++++++++++++++------------------ 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 25439ad0..2a691580 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,7 +169,7 @@ export class Platform { } async init() { - await this.logger.run("Platform init", async () => { + await this.logger.run("Platform init", async (log) => { if (!this._config) { if (!this._configURL) { throw new Error("Neither config nor configURL was provided!"); @@ -183,7 +183,7 @@ export class Platform { ); const manifests = this.config["themeManifests"]; await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); }); } diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 5d93ad68..72865279 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type {ILogItem} from "../../logging/types.js"; import type {Platform} from "./Platform.js"; -export enum COLOR_SCHEME_PREFERENCE { DARK, LIGHT, } - export class ThemeLoader { private _platform: Platform; private _themeMapping: Record = {}; @@ -27,27 +26,25 @@ export class ThemeLoader { } async init(manifestLocations: string[]): Promise { - await this._platform.logger.run("ThemeLoader.init", async () => { - for (const manifestLocation of manifestLocations) { - const { body } = await this._platform - .request(manifestLocation, { - method: "GET", - format: "json", - cache: true, - }) - .response(); - /* - After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. - */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); - } - }); + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-assets` which is a mapping from the theme-name to the + location of the css file in build. + */ + Object.assign(this._themeMapping, body["source"]["built-assets"]); + } } - setTheme(themeName: string) { - this._platform.logger.run("ThemeLoader.setTheme", () => { + setTheme(themeName: string, log?: ILogItem) { + this._platform.logger.wrapOrRun(log, "setTheme", () => { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); @@ -62,19 +59,17 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - return await this._platform.logger.run("ThemeLoader.getActiveTheme", async () => { - // 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("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; - } - return undefined; - }); + // 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("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; + } + return undefined; } } From 03ab1ee2c7a48721e763f8b301c162b6625940b8 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 17:48:03 +0200 Subject: [PATCH 33/33] log theme being loaded --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 72865279..d9aaabb6 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -44,7 +44,7 @@ export class ThemeLoader { } setTheme(themeName: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, "setTheme", () => { + this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`);