From 4474458f4bf4d765f360390cf26a882662358fa5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 23 May 2022 12:45:32 +0530 Subject: [PATCH 01/35] getActiveTheme should never return undefined Instead it should throw an error. This is useful for when we do setTheme(await getActiveTheme()) because setTheme expects a string. --- src/platform/web/ThemeLoader.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index d9aaabb6..2aa79bb5 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -58,7 +58,7 @@ export class ThemeLoader { return Object.keys(this._themeMapping); } - async getActiveTheme(): Promise { + async getActiveTheme(): Promise { // check if theme is set via settings let theme = await this._platform.settingsStorage.getString("theme"); if (theme) { @@ -70,6 +70,6 @@ export class ThemeLoader { } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { return this._platform.config["defaultTheme"].light; } - return undefined; + throw new Error("Cannot find active theme!"); } } From 809c522571baf6a1d4b41221621893a1a89feef3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:13:31 +0530 Subject: [PATCH 02/35] Change the format of built-asset --- .../rollup-plugin-build-themes.js | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index da2db73b..4fddb8ed 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -254,13 +254,50 @@ module.exports = function buildThemes(options) { const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; 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. - */ + let defaultDarkVariant = {}, defaultLightVariant = {}; + const themeName = manifest.name; for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + const {name: variantName, default: isDefault, dark} = manifest.values.variants[variant]; + const themeId = `${name}-${variant}`; + const themeDisplayName = `${themeName} ${variantName}`; + if (isDefault) { + /** + * This is a default variant! + * We'll add these to the builtAssets keyed with just the + * theme-name (i.e "Element" instead of "Element Dark") + * so that we can distinguish them from other variants! + * + * This allows us to render a radio-button with "dark" and + * "light" options. + */ + const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; + defaultVariant.themeDisplayName = variantName; + defaultVariant.id = themeId + defaultVariant.cssLocation = assetMap.get(chunk.fileName).fileName; + continue; + } + // Non-default variants are keyed in builtAssets with "theme_name variant_name" + // eg: "Element Dark" + builtAssets[themeDisplayName] = { + cssLocation: assetMap.get(chunk.fileName).fileName, + id: themeId + }; + } + if (defaultDarkVariant.id && defaultLightVariant.id) { + /** + * As mentioned above, if there's both a default dark and a default light variant, + * add them to builtAsset separately. + */ + builtAssets[themeName] = { dark: defaultDarkVariant, light: defaultLightVariant }; + } + else { + /** + * If only one default variant is found (i.e only dark default or light default but not both), + * treat it like any other variant. + */ + const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; + builtAssets[`${themeName} ${variant.themeDisplayName}`] = { id: variant.id, cssLocation: variant.cssLocation }; } manifest.source = { "built-assets": builtAssets, From 3afbe1148e9fffa5837eb26c81a21b8c90047037 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:35:09 +0530 Subject: [PATCH 03/35] Use the new built-asset format in ThemeLoader --- src/platform/web/ThemeLoader.ts | 46 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 2aa79bb5..1f010fb2 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -17,15 +17,35 @@ limitations under the License. import type {ILogItem} from "../../logging/types.js"; import type {Platform} from "./Platform.js"; +type NormalVariant = { + id: string; + cssLocation: string; +}; + +type DefaultVariant = { + dark: { + id: string; + cssLocation: string; + themeDisplayName: string; + }; + light: { + id: string; + cssLocation: string; + themeDisplayName: string; + }; +} +type ThemeInformation = NormalVariant | DefaultVariant; + export class ThemeLoader { private _platform: Platform; - private _themeMapping: Record = {}; + private _themeMapping: Record; constructor(platform: Platform) { this._platform = platform; } async init(manifestLocations: string[]): Promise { + this._themeMapping = {}; for (const manifestLocation of manifestLocations) { const { body } = await this._platform .request(manifestLocation, { @@ -36,8 +56,8 @@ export class ThemeLoader { .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. + contains `built-assets` which is a mapping from the theme-name to theme + details which includes the location of the CSS file. */ Object.assign(this._themeMapping, body["source"]["built-assets"]); } @@ -45,7 +65,7 @@ export class ThemeLoader { setTheme(themeName: string, log?: ILogItem) { this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { - const themeLocation = this._themeMapping[themeName]; + const themeLocation = this._findThemeLocationFromId(themeName); if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); } @@ -54,8 +74,8 @@ export class ThemeLoader { }); } - get themes(): string[] { - return Object.keys(this._themeMapping); + get themeMapping(): Record { + return this._themeMapping; } async getActiveTheme(): Promise { @@ -72,4 +92,18 @@ export class ThemeLoader { } throw new Error("Cannot find active theme!"); } + + private _findThemeLocationFromId(themeId: string) { + for (const themeData of Object.values(this._themeMapping)) { + if ("id" in themeData && themeData.id === themeId) { + return themeData.cssLocation; + } + else if ("light" in themeData && themeData.light?.id === themeId) { + return themeData.light.cssLocation; + } + else if ("dark" in themeData && themeData.dark?.id === themeId) { + return themeData.dark.cssLocation; + } + } + } } From 0b98473e85ce31bf7a4beb80077d29f2002ee1e6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:36:37 +0530 Subject: [PATCH 04/35] Render a radio button for default variants --- .../session/settings/SettingsViewModel.js | 14 ++++++-- .../web/ui/session/settings/SettingsView.js | 34 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 5c89236f..bab72d3d 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -131,8 +131,8 @@ export class SettingsViewModel extends ViewModel { return this._formatBytes(this._estimate?.usage); } - get themes() { - return this.platform.themeLoader.themes; + get themeMapping() { + return this.platform.themeLoader.themeMapping; } get activeTheme() { @@ -185,5 +185,15 @@ export class SettingsViewModel extends ViewModel { this.emitChange("pushNotifications.serverError"); } } + + themeOptionChanged(themeId) { + if (themeId) { + this.setTheme(themeId); + } + // if there's no themeId, then the theme is going to be set via radio-buttons + // emit so that radio-buttons become displayed/hidden + this.emitChange("themeOption"); + } + } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index dd7bbc03..06f98577 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -142,9 +142,37 @@ export class SettingsView extends TemplateView { _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)); + let isDarkSelected = null, isLightSelected = null; + for (const [name, details] of Object.entries(vm.themeMapping)) { + let isSelected = null; + if (details.id === activeTheme) { + isSelected = true; + } + else if (details.dark?.id === activeTheme) { + isSelected = true; + isDarkSelected = true; + } + else if (details.light?.id === activeTheme) { + isSelected = true; + isLightSelected = true; + } + optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } - return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); + const select = t.select({ onChange: (e) => vm.themeOptionChanged(e.target.value) }, optionTags); + const radioButtons = t.form({ + className: { hidden: () => select.options[select.selectedIndex].value !== "" }, + onChange: (e) => { + const selectedThemeName = select.options[select.selectedIndex].text; + const themeId = vm.themeMapping[selectedThemeName][e.target.value].id; + vm.themeOptionChanged(themeId); + } + }, + [ + t.input({ type: "radio", name: "radio-chooser", value: "dark", id: "dark", checked: isDarkSelected }), + t.label({for: "dark"}, "dark"), + t.input({ type: "radio", name: "radio-chooser", value: "light", id: "light", checked: isLightSelected }), + t.label({for: "light"}, "light"), + ]); + return t.div({ className: "theme-chooser" }, [select, radioButtons]); } } From 1f00c8f6359d2570b0e5040c62d1b11891269619 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 26 May 2022 23:36:56 +0530 Subject: [PATCH 05/35] Add a temporary theme to test this PR --- .../web/ui/css/themes/element/manifest.json | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/platform/web/ui/css/themes/element/manifest.json b/src/platform/web/ui/css/themes/element/manifest.json index ec1852cb..0858a05b 100644 --- a/src/platform/web/ui/css/themes/element/manifest.json +++ b/src/platform/web/ui/css/themes/element/manifest.json @@ -1,6 +1,7 @@ { "version": 1, - "name": "element", + "name": "Element", + "id": "element", "values": { "font-faces": [ { @@ -9,37 +10,51 @@ } ], "variants": { - "light": { - "base": true, - "default": true, - "name": "Light", - "variables": { - "background-color-primary": "#fff", + "light": { + "base": true, + "default": true, + "name": "Light", + "variables": { + "background-color-primary": "#fff", "background-color-secondary": "#f6f6f6", - "text-color": "#2E2F32", + "text-color": "#2E2F32", "accent-color": "#03b381", "error-color": "#FF4B55", "fixed-white": "#fff", "room-badge": "#61708b", "link-color": "#238cf5" - } - }, - "dark": { - "dark": true, - "default": true, - "name": "Dark", - "variables": { - "background-color-primary": "#21262b", + } + }, + "dark": { + "dark": true, + "default": true, + "name": "Dark", + "variables": { + "background-color-primary": "#21262b", "background-color-secondary": "#2D3239", - "text-color": "#fff", + "text-color": "#fff", "accent-color": "#03B381", "error-color": "#FF4B55", "fixed-white": "#fff", "room-badge": "#61708b", "link-color": "#238cf5" - } - } - } + } + }, + "violet": { + "dark": true, + "name": "Violet", + "variables": { + "background-color-primary": "#21262b", + "background-color-secondary": "#2D3239", + "text-color": "#fff", + "accent-color": "#03B381", + "error-color": "#FF4B55", + "fixed-white": "#fff", + "room-badge": "#61708b", + "link-color": "#238cf5" + } + } + } } } From d4084da2998e0f011317f34d58bf988fbe3882f0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 14:23:38 +0530 Subject: [PATCH 06/35] Extract code into function --- src/platform/web/ThemeLoader.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 1f010fb2..2cc0c38d 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -79,18 +79,19 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - // check if theme is set via settings - let theme = await this._platform.settingsStorage.getString("theme"); + const theme = await this._platform.settingsStorage.getString("theme") ?? this.getDefaultTheme(); if (theme) { return theme; } - // return default theme + throw new Error("Cannot find active theme!"); + } + + getDefaultTheme(): string | undefined { 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; } - throw new Error("Cannot find active theme!"); } private _findThemeLocationFromId(themeId: string) { From bbec2effe5128470fbea855c73ccff0e3093cdc8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 14:24:19 +0530 Subject: [PATCH 07/35] Add typing --- 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 2cc0c38d..299c0e1d 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -94,7 +94,7 @@ export class ThemeLoader { } } - private _findThemeLocationFromId(themeId: string) { + private _findThemeLocationFromId(themeId: string): string | undefined { for (const themeData of Object.values(this._themeMapping)) { if ("id" in themeData && themeData.id === themeId) { return themeData.cssLocation; From f6cec938a7a4903d88f431544c8bab820d6b58f0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 14:24:44 +0530 Subject: [PATCH 08/35] Add default theme to mapping --- src/platform/web/ThemeLoader.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 299c0e1d..e9bdd479 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -60,6 +60,14 @@ export class ThemeLoader { details which includes the location of the CSS file. */ Object.assign(this._themeMapping, body["source"]["built-assets"]); + //Add the default-theme as an additional option to the mapping + const defaultThemeId = this.getDefaultTheme(); + if (defaultThemeId) { + const cssLocation = this._findThemeLocationFromId(defaultThemeId); + if (cssLocation) { + this._themeMapping["Default"] = { id: "default", cssLocation }; + } + } } } From cb03e97e78e308eeec82cfb6d5d1f43f73a51005 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 16:31:23 +0530 Subject: [PATCH 09/35] Use default theme intially --- 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 e9bdd479..2901cdf9 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -87,7 +87,7 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - const theme = await this._platform.settingsStorage.getString("theme") ?? this.getDefaultTheme(); + const theme = await this._platform.settingsStorage.getString("theme") ?? "default"; if (theme) { return theme; } From e8e4c33bae7435c33bb0c158a348452616f57a40 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:39:05 +0530 Subject: [PATCH 10/35] Rephrase comment --- scripts/build-plugins/rollup-plugin-build-themes.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 4fddb8ed..a3fd6c8f 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -264,11 +264,11 @@ module.exports = function buildThemes(options) { if (isDefault) { /** * This is a default variant! - * We'll add these to the builtAssets keyed with just the - * theme-name (i.e "Element" instead of "Element Dark") - * so that we can distinguish them from other variants! + * We'll add these to the builtAssets (separately) keyed with just the + * theme-name (i.e "Element" instead of "Element Dark"). + * We need to be able to distinguish them from other variants! * - * This allows us to render a radio-button with "dark" and + * This allows us to render radio-buttons with "dark" and * "light" options. */ const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; From 8ad0b8a7264f22de9c2ce382c60bbd077e689828 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:48:02 +0530 Subject: [PATCH 11/35] rename themeName --> variantName --- scripts/build-plugins/rollup-plugin-build-themes.js | 4 ++-- src/platform/web/ThemeLoader.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index a3fd6c8f..53258f16 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -272,7 +272,7 @@ module.exports = function buildThemes(options) { * "light" options. */ const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; - defaultVariant.themeDisplayName = variantName; + defaultVariant.variantName = variantName; defaultVariant.id = themeId defaultVariant.cssLocation = assetMap.get(chunk.fileName).fileName; continue; @@ -297,7 +297,7 @@ module.exports = function buildThemes(options) { * treat it like any other variant. */ const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; - builtAssets[`${themeName} ${variant.themeDisplayName}`] = { id: variant.id, cssLocation: variant.cssLocation }; + builtAssets[`${themeName} ${variant.variantName}`] = { id: variant.id, cssLocation: variant.cssLocation }; } manifest.source = { "built-assets": builtAssets, diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 2901cdf9..955d3b48 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -26,12 +26,12 @@ type DefaultVariant = { dark: { id: string; cssLocation: string; - themeDisplayName: string; + variantName: string; }; light: { id: string; cssLocation: string; - themeDisplayName: string; + variantName: string; }; } type ThemeInformation = NormalVariant | DefaultVariant; From 46d2792dac6c76425f32fbde2f9ea21c4e238d30 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:49:45 +0530 Subject: [PATCH 12/35] Modify comment --- src/platform/web/ThemeLoader.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 955d3b48..5bc38afa 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -34,6 +34,7 @@ type DefaultVariant = { variantName: string; }; } + type ThemeInformation = NormalVariant | DefaultVariant; export class ThemeLoader { @@ -57,7 +58,8 @@ export class ThemeLoader { /* After build has finished, the source section of each theme manifest contains `built-assets` which is a mapping from the theme-name to theme - details which includes the location of the CSS file. + information which includes the location of the CSS file. + (see type ThemeInformation above) */ Object.assign(this._themeMapping, body["source"]["built-assets"]); //Add the default-theme as an additional option to the mapping From e3235ea3eb79da19b93a74a75511339c3d861b1b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 11:58:17 +0530 Subject: [PATCH 13/35] Rename themeName --> themeId --- src/platform/web/ThemeLoader.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 5bc38afa..bd2d51b4 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -73,14 +73,14 @@ export class ThemeLoader { } } - setTheme(themeName: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { - const themeLocation = this._findThemeLocationFromId(themeName); + setTheme(themeId: string, log?: ILogItem) { + this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeId}, () => { + const themeLocation = this._findThemeLocationFromId(themeId); if (!themeLocation) { - throw new Error( `Cannot find theme location for theme "${themeName}"!`); + throw new Error( `Cannot find theme location for theme "${themeId}"!`); } this._platform.replaceStylesheet(themeLocation); - this._platform.settingsStorage.setString("theme", themeName); + this._platform.settingsStorage.setString("theme", themeId); }); } From efb1a674706973b9bbd93f9263d83f14e52eaddb Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 12:02:35 +0530 Subject: [PATCH 14/35] Make method name a verb --- src/domain/session/settings/SettingsViewModel.js | 2 +- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index bab72d3d..caaa2579 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -186,7 +186,7 @@ export class SettingsViewModel extends ViewModel { } } - themeOptionChanged(themeId) { + changeThemeOption(themeId) { if (themeId) { this.setTheme(themeId); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 06f98577..e77da911 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -158,13 +158,13 @@ export class SettingsView extends TemplateView { } optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } - const select = t.select({ onChange: (e) => vm.themeOptionChanged(e.target.value) }, optionTags); + const select = t.select({ onChange: (e) => vm.changeThemeOption(e.target.value) }, optionTags); const radioButtons = t.form({ className: { hidden: () => select.options[select.selectedIndex].value !== "" }, onChange: (e) => { const selectedThemeName = select.options[select.selectedIndex].text; const themeId = vm.themeMapping[selectedThemeName][e.target.value].id; - vm.themeOptionChanged(themeId); + vm.changeThemeOption(themeId); } }, [ From 9e79b632a8e405857f80d9e6a6a040659bae5288 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 30 May 2022 12:06:23 +0530 Subject: [PATCH 15/35] Extract variable --- src/platform/web/ui/session/settings/SettingsView.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index e77da911..14893aba 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -158,12 +158,18 @@ export class SettingsView extends TemplateView { } optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } - const select = t.select({ onChange: (e) => vm.changeThemeOption(e.target.value) }, optionTags); + const select = t.select({ + onChange: (e) => { + const themeId = e.target.value; + vm.changeThemeOption(themeId) + } + }, optionTags); const radioButtons = t.form({ className: { hidden: () => select.options[select.selectedIndex].value !== "" }, onChange: (e) => { const selectedThemeName = select.options[select.selectedIndex].text; - const themeId = vm.themeMapping[selectedThemeName][e.target.value].id; + const colorScheme = e.target.value; + const themeId = vm.themeMapping[selectedThemeName][colorScheme].id; vm.changeThemeOption(themeId); } }, From 12a8e9424325ced7568ad7be21b3e50d3be6e340 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 31 May 2022 20:21:18 +0530 Subject: [PATCH 16/35] Move code into ThemeLoader --- .../rollup-plugin-build-themes.js | 43 +--------------- src/platform/web/ThemeLoader.ts | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 53258f16..8d05f58a 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -254,50 +254,9 @@ module.exports = function buildThemes(options) { const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; const builtAssets = {}; - let defaultDarkVariant = {}, defaultLightVariant = {}; - const themeName = manifest.name; for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - const {name: variantName, default: isDefault, dark} = manifest.values.variants[variant]; - const themeId = `${name}-${variant}`; - const themeDisplayName = `${themeName} ${variantName}`; - if (isDefault) { - /** - * This is a default variant! - * We'll add these to the builtAssets (separately) keyed with just the - * theme-name (i.e "Element" instead of "Element Dark"). - * We need to be able to distinguish them from other variants! - * - * This allows us to render radio-buttons with "dark" and - * "light" options. - */ - const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; - defaultVariant.variantName = variantName; - defaultVariant.id = themeId - defaultVariant.cssLocation = assetMap.get(chunk.fileName).fileName; - continue; - } - // Non-default variants are keyed in builtAssets with "theme_name variant_name" - // eg: "Element Dark" - builtAssets[themeDisplayName] = { - cssLocation: assetMap.get(chunk.fileName).fileName, - id: themeId - }; - } - if (defaultDarkVariant.id && defaultLightVariant.id) { - /** - * As mentioned above, if there's both a default dark and a default light variant, - * add them to builtAsset separately. - */ - builtAssets[themeName] = { dark: defaultDarkVariant, light: defaultLightVariant }; - } - else { - /** - * If only one default variant is found (i.e only dark default or light default but not both), - * treat it like any other variant. - */ - const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; - builtAssets[`${themeName} ${variant.variantName}`] = { id: variant.id, cssLocation: variant.cssLocation }; + builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; } manifest.source = { "built-assets": builtAssets, diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index bd2d51b4..ca7ef641 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -55,13 +55,14 @@ export class ThemeLoader { cache: true, }) .response(); + this._populateThemeMap(body); /* After build has finished, the source section of each theme manifest contains `built-assets` which is a mapping from the theme-name to theme information which includes the location of the CSS file. (see type ThemeInformation above) */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); + // Object.assign(this._themeMapping, body["source"]["built-assets"]); //Add the default-theme as an additional option to the mapping const defaultThemeId = this.getDefaultTheme(); if (defaultThemeId) { @@ -73,6 +74,54 @@ export class ThemeLoader { } } + private _populateThemeMap(manifest) { + const builtAssets: Record = manifest.source?.["built-assets"]; + const themeName = manifest.name; + let defaultDarkVariant: any = {}, defaultLightVariant: any = {}; + for (const [themeId, cssLocation] of Object.entries(builtAssets)) { + const variant = themeId.match(/.+-(.+)/)?.[1]; + const { name: variantName, default: isDefault, dark } = manifest.values.variants[variant!]; + const themeDisplayName = `${themeName} ${variantName}`; + if (isDefault) { + /** + * This is a default variant! + * We'll add these to the themeMapping (separately) keyed with just the + * theme-name (i.e "Element" instead of "Element Dark"). + * We need to be able to distinguish them from other variants! + * + * This allows us to render radio-buttons with "dark" and + * "light" options. + */ + const defaultVariant = dark ? defaultDarkVariant : defaultLightVariant; + defaultVariant.variantName = variantName; + defaultVariant.id = themeId + defaultVariant.cssLocation = cssLocation; + continue; + } + // Non-default variants are keyed in themeMapping with "theme_name variant_name" + // eg: "Element Dark" + this._themeMapping[themeDisplayName] = { + cssLocation, + id: themeId + }; + } + if (defaultDarkVariant.id && defaultLightVariant.id) { + /** + * As mentioned above, if there's both a default dark and a default light variant, + * add them to themeMapping separately. + */ + this._themeMapping[themeName] = { dark: defaultDarkVariant, light: defaultLightVariant }; + } + else { + /** + * If only one default variant is found (i.e only dark default or light default but not both), + * treat it like any other variant. + */ + const variant = defaultDarkVariant.id ? defaultDarkVariant : defaultLightVariant; + this._themeMapping[`${themeName} ${variant.variantName}`] = { id: variant.id, cssLocation: variant.cssLocation }; + } + } + setTheme(themeId: string, log?: ILogItem) { this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeId}, () => { const themeLocation = this._findThemeLocationFromId(themeId); From dc2d1ce700de79d1eb89fa7293d88b232bf95d45 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 31 May 2022 20:21:36 +0530 Subject: [PATCH 17/35] Remove id --- src/platform/web/ui/css/themes/element/manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/web/ui/css/themes/element/manifest.json b/src/platform/web/ui/css/themes/element/manifest.json index 0858a05b..6c99e404 100644 --- a/src/platform/web/ui/css/themes/element/manifest.json +++ b/src/platform/web/ui/css/themes/element/manifest.json @@ -1,7 +1,6 @@ { "version": 1, "name": "Element", - "id": "element", "values": { "font-faces": [ { From 8de91291ddbeb42098f56ccef38f3ddca66fe448 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 2 Jun 2022 15:05:43 +0530 Subject: [PATCH 18/35] Add more methods to ThemeLoader --- src/platform/web/ThemeLoader.ts | 44 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index ca7ef641..f302bc0e 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -37,6 +37,11 @@ type DefaultVariant = { type ThemeInformation = NormalVariant | DefaultVariant; +export enum ColorSchemePreference { + Dark, + Light +}; + export class ThemeLoader { private _platform: Platform; private _themeMapping: Record; @@ -104,7 +109,7 @@ export class ThemeLoader { cssLocation, id: themeId }; - } + } if (defaultDarkVariant.id && defaultLightVariant.id) { /** * As mentioned above, if there's both a default dark and a default light variant, @@ -123,14 +128,14 @@ export class ThemeLoader { } setTheme(themeId: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeId}, () => { + this._platform.logger.wrapOrRun(log, { l: "change theme", id: themeId }, () => { const themeLocation = this._findThemeLocationFromId(themeId); if (!themeLocation) { - throw new Error( `Cannot find theme location for theme "${themeId}"!`); + throw new Error(`Cannot find theme location for theme "${themeId}"!`); } this._platform.replaceStylesheet(themeLocation); this._platform.settingsStorage.setString("theme", themeId); - }); + }); } get themeMapping(): Record { @@ -146,10 +151,11 @@ export class ThemeLoader { } getDefaultTheme(): string | undefined { - 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; + switch (this.preferredColorScheme) { + case ColorSchemePreference.Dark: + return this._platform.config["defaultTheme"].dark; + case ColorSchemePreference.Light: + return this._platform.config["defaultTheme"].light; } } @@ -166,4 +172,26 @@ export class ThemeLoader { } } } + + get preferredColorScheme(): ColorSchemePreference { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return ColorSchemePreference.Dark; + } + else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return ColorSchemePreference.Light; + } + throw new Error("Cannot find preferred colorscheme!"); + } + + async persistVariantToStorage(variant: string) { + await this._platform.settingsStorage.setString("theme-variant", variant); + } + + async getCurrentVariant(): Promise { + return await this._platform.settingsStorage.getString("theme-variant"); + } + + async removeVariantFromStorage(): Promise { + await this._platform.settingsStorage.remove("theme-variant"); + } } From b74f4b612bc8197b377c107149052cbd8bdffaa9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 2 Jun 2022 15:06:20 +0530 Subject: [PATCH 19/35] Change UI --- .../session/settings/SettingsViewModel.js | 17 ++++++ .../web/ui/session/settings/SettingsView.js | 56 +++++++++++-------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index caaa2579..aae83fb6 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -51,6 +51,7 @@ export class SettingsViewModel extends ViewModel { this.maxSentImageSizeLimit = 4000; this.pushNotifications = new PushNotificationStatus(); this._activeTheme = undefined; + this._activeVariant = undefined; } get _session() { @@ -79,6 +80,7 @@ export class SettingsViewModel extends ViewModel { this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); if (!import.meta.env.DEV) { this._activeTheme = await this.platform.themeLoader.getActiveTheme(); + this._activeVariant = await this.platform.themeLoader.getCurrentVariant(); } this.emitChange(""); } @@ -139,6 +141,10 @@ export class SettingsViewModel extends ViewModel { return this._activeTheme; } + get activeVariant() { + return this._activeVariant; + } + setTheme(name) { this.platform.themeLoader.setTheme(name); } @@ -195,5 +201,16 @@ export class SettingsViewModel extends ViewModel { this.emitChange("themeOption"); } + get preferredColorScheme() { + return this.platform.themeLoader.preferredColorScheme; + } + + persistVariantToStorage(variant) { + this.platform.themeLoader.persistVariantToStorage(variant); + } + + removeVariantFromStorage() { + this.platform.themeLoader.removeVariantFromStorage(); + } } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 14893aba..45178990 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -16,6 +16,7 @@ limitations under the License. import {TemplateView} from "../../general/TemplateView"; import {KeyBackupSettingsView} from "./KeyBackupSettingsView.js" +import {ColorSchemePreference} from "../../../ThemeLoader"; export class SettingsView extends TemplateView { render(t, vm) { @@ -142,38 +143,49 @@ export class SettingsView extends TemplateView { _themeOptions(t, vm) { const activeTheme = vm.activeTheme; const optionTags = []; - let isDarkSelected = null, isLightSelected = null; + const isDarkSelected = vm.activeVariant === "dark"; + const isLightSelected = vm.activeVariant === "light"; + // 1. render the dropdown containing the themes for (const [name, details] of Object.entries(vm.themeMapping)) { - let isSelected = null; - if (details.id === activeTheme) { - isSelected = true; - } - else if (details.dark?.id === activeTheme) { - isSelected = true; - isDarkSelected = true; - } - else if (details.light?.id === activeTheme) { - isSelected = true; - isLightSelected = true; - } + const isSelected = (details.id ?? details.dark?.id ?? details.light?.id) === activeTheme; optionTags.push( t.option({ value: details.id ?? "", selected: isSelected} , name)); } const select = t.select({ onChange: (e) => { const themeId = e.target.value; - vm.changeThemeOption(themeId) - } - }, optionTags); - const radioButtons = t.form({ - className: { hidden: () => select.options[select.selectedIndex].value !== "" }, - onChange: (e) => { - const selectedThemeName = select.options[select.selectedIndex].text; - const colorScheme = e.target.value; - const themeId = vm.themeMapping[selectedThemeName][colorScheme].id; + if (themeId) { + /* if the