2022-05-11 14:58:14 +05:30
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2022-07-19 16:00:58 +05:30
|
|
|
import type {ILogItem} from "../../../logging/types";
|
|
|
|
import type {Platform} from "../Platform.js";
|
2022-07-19 17:33:06 +05:30
|
|
|
import {RuntimeThemeParser} from "./parsers/RuntimeThemeParser";
|
|
|
|
import type {Variant, ThemeInformation} from "./parsers/types";
|
|
|
|
import {ColorSchemePreference} from "./parsers/types";
|
2022-07-20 15:36:02 +05:30
|
|
|
import {BuiltThemeParser} from "./parsers/BuiltThemeParser";
|
2022-06-02 15:05:43 +05:30
|
|
|
|
2022-05-11 14:58:14 +05:30
|
|
|
export class ThemeLoader {
|
|
|
|
private _platform: Platform;
|
2022-05-26 23:35:09 +05:30
|
|
|
private _themeMapping: Record<string, ThemeInformation>;
|
2022-07-19 17:33:06 +05:30
|
|
|
private _injectedVariables?: Record<string, string>;
|
2022-05-11 14:58:14 +05:30
|
|
|
|
|
|
|
constructor(platform: Platform) {
|
|
|
|
this._platform = platform;
|
|
|
|
}
|
|
|
|
|
2022-06-07 11:57:57 +05:30
|
|
|
async init(manifestLocations: string[], log?: ILogItem): Promise<void> {
|
|
|
|
await this._platform.logger.wrapOrRun(log, "ThemeLoader.init", async (log) => {
|
2022-06-12 17:05:31 +05:30
|
|
|
const results = await Promise.all(
|
2022-07-18 14:55:13 +05:30
|
|
|
manifestLocations.map(location => this._platform.request(location, { method: "GET", format: "json", cache: true, }).response())
|
2022-06-12 17:05:31 +05:30
|
|
|
);
|
2022-07-19 17:33:06 +05:30
|
|
|
const runtimeThemeParser = new RuntimeThemeParser(this._platform, this.preferredColorScheme);
|
|
|
|
const builtThemeParser = new BuiltThemeParser(this.preferredColorScheme);
|
2022-07-17 22:29:36 +05:30
|
|
|
const runtimeThemePromises: Promise<void>[] = [];
|
2022-07-15 15:05:50 +05:30
|
|
|
for (let i = 0; i < results.length; ++i) {
|
|
|
|
const { body } = results[i];
|
2022-07-17 17:35:29 +05:30
|
|
|
try {
|
|
|
|
if (body.extends) {
|
2022-07-18 14:55:13 +05:30
|
|
|
const indexOfBaseManifest = results.findIndex(manifest => manifest.body.id === body.extends);
|
|
|
|
if (indexOfBaseManifest === -1) {
|
|
|
|
throw new Error(`Base manifest for derived theme at ${manifestLocations[i]} not found!`);
|
|
|
|
}
|
|
|
|
const {body: baseManifest} = results[indexOfBaseManifest];
|
|
|
|
const baseManifestLocation = manifestLocations[indexOfBaseManifest];
|
2022-07-19 17:33:06 +05:30
|
|
|
const promise = runtimeThemeParser.parse(body, baseManifest, baseManifestLocation, log);
|
2022-07-17 22:29:36 +05:30
|
|
|
runtimeThemePromises.push(promise);
|
2022-07-17 17:35:29 +05:30
|
|
|
}
|
|
|
|
else {
|
2022-07-19 17:33:06 +05:30
|
|
|
builtThemeParser.parse(body, manifestLocations[i], log);
|
2022-07-17 17:35:29 +05:30
|
|
|
}
|
2022-06-28 12:28:19 +05:30
|
|
|
}
|
2022-07-17 17:35:29 +05:30
|
|
|
catch(e) {
|
|
|
|
console.error(e);
|
2022-06-28 12:28:19 +05:30
|
|
|
}
|
2022-07-15 15:05:50 +05:30
|
|
|
}
|
2022-07-17 22:29:36 +05:30
|
|
|
await Promise.all(runtimeThemePromises);
|
2022-07-19 17:33:06 +05:30
|
|
|
this._themeMapping = { ...builtThemeParser.themeMapping, ...runtimeThemeParser.themeMapping };
|
|
|
|
Object.assign(this._themeMapping, builtThemeParser.themeMapping, runtimeThemeParser.themeMapping);
|
|
|
|
this._addDefaultThemeToMapping(log);
|
2022-07-17 17:44:21 +05:30
|
|
|
log.log({ l: "Preferred colorscheme", scheme: this.preferredColorScheme === ColorSchemePreference.Dark ? "dark" : "light" });
|
|
|
|
log.log({ l: "Result", themeMapping: this._themeMapping });
|
2022-06-07 11:57:57 +05:30
|
|
|
});
|
2022-05-11 14:58:14 +05:30
|
|
|
}
|
|
|
|
|
2022-06-06 17:20:16 +05:30
|
|
|
setTheme(themeName: string, themeVariant?: "light" | "dark" | "default", log?: ILogItem) {
|
2022-06-05 20:49:05 +05:30
|
|
|
this._platform.logger.wrapOrRun(log, { l: "change theme", name: themeName, variant: themeVariant }, () => {
|
2022-06-28 12:28:19 +05:30
|
|
|
let cssLocation: string, variables: Record<string, string>;
|
2022-06-05 20:49:05 +05:30
|
|
|
let themeDetails = this._themeMapping[themeName];
|
|
|
|
if ("id" in themeDetails) {
|
|
|
|
cssLocation = themeDetails.cssLocation;
|
2022-06-28 12:28:19 +05:30
|
|
|
variables = themeDetails.variables;
|
2022-06-05 20:49:05 +05:30
|
|
|
}
|
|
|
|
else {
|
2022-06-06 17:20:16 +05:30
|
|
|
if (!themeVariant) {
|
|
|
|
throw new Error("themeVariant is undefined!");
|
|
|
|
}
|
|
|
|
cssLocation = themeDetails[themeVariant].cssLocation;
|
2022-06-28 12:28:19 +05:30
|
|
|
variables = themeDetails[themeVariant].variables;
|
2022-06-05 20:49:05 +05:30
|
|
|
}
|
|
|
|
this._platform.replaceStylesheet(cssLocation);
|
2022-06-28 12:28:19 +05:30
|
|
|
if (variables) {
|
2022-07-17 20:58:58 +05:30
|
|
|
log?.log({l: "Derived Theme", variables});
|
2022-07-19 17:33:06 +05:30
|
|
|
this._injectCSSVariables(variables);
|
2022-06-28 12:28:19 +05:30
|
|
|
}
|
2022-07-15 15:05:50 +05:30
|
|
|
else {
|
2022-07-19 17:33:06 +05:30
|
|
|
this._removePreviousCSSVariables();
|
2022-07-15 15:05:50 +05:30
|
|
|
}
|
2022-06-05 20:49:05 +05:30
|
|
|
this._platform.settingsStorage.setString("theme-name", themeName);
|
|
|
|
if (themeVariant) {
|
|
|
|
this._platform.settingsStorage.setString("theme-variant", themeVariant);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this._platform.settingsStorage.remove("theme-variant");
|
2022-05-18 16:09:09 +05:30
|
|
|
}
|
2022-06-02 15:05:43 +05:30
|
|
|
});
|
2022-05-11 14:58:14 +05:30
|
|
|
}
|
|
|
|
|
2022-07-19 17:33:06 +05:30
|
|
|
private _injectCSSVariables(variables: Record<string, string>): void {
|
|
|
|
const root = document.documentElement;
|
|
|
|
for (const [variable, value] of Object.entries(variables)) {
|
|
|
|
root.style.setProperty(`--${variable}`, value);
|
|
|
|
}
|
|
|
|
this._injectedVariables = variables;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _removePreviousCSSVariables(): void {
|
|
|
|
if (!this._injectedVariables) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const root = document.documentElement;
|
|
|
|
for (const variable of Object.keys(this._injectedVariables)) {
|
|
|
|
root.style.removeProperty(`--${variable}`);
|
|
|
|
}
|
|
|
|
this._injectedVariables = undefined;
|
|
|
|
}
|
|
|
|
|
2022-06-12 16:52:21 +05:30
|
|
|
/** Maps theme display name to theme information */
|
2022-05-26 23:35:09 +05:30
|
|
|
get themeMapping(): Record<string, ThemeInformation> {
|
|
|
|
return this._themeMapping;
|
2022-05-11 14:58:14 +05:30
|
|
|
}
|
|
|
|
|
2022-06-05 20:49:05 +05:30
|
|
|
async getActiveTheme(): Promise<{themeName: string, themeVariant?: string}> {
|
2022-06-06 12:20:06 +05:30
|
|
|
let themeName = await this._platform.settingsStorage.getString("theme-name");
|
|
|
|
let themeVariant = await this._platform.settingsStorage.getString("theme-variant");
|
|
|
|
if (!themeName || !this._themeMapping[themeName]) {
|
|
|
|
themeName = "Default" in this._themeMapping ? "Default" : Object.keys(this._themeMapping)[0];
|
|
|
|
if (!this._themeMapping[themeName][themeVariant]) {
|
|
|
|
themeVariant = "default" in this._themeMapping[themeName] ? "default" : undefined;
|
|
|
|
}
|
|
|
|
}
|
2022-06-05 20:49:05 +05:30
|
|
|
return { themeName, themeVariant };
|
2022-05-27 14:23:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
getDefaultTheme(): string | undefined {
|
2022-06-02 15:05:43 +05:30
|
|
|
switch (this.preferredColorScheme) {
|
|
|
|
case ColorSchemePreference.Dark:
|
2022-06-06 12:19:48 +05:30
|
|
|
return this._platform.config["defaultTheme"]?.dark;
|
2022-06-02 15:05:43 +05:30
|
|
|
case ColorSchemePreference.Light:
|
2022-06-06 12:19:48 +05:30
|
|
|
return this._platform.config["defaultTheme"]?.light;
|
2022-05-18 18:56:28 +05:30
|
|
|
}
|
2022-05-11 14:58:14 +05:30
|
|
|
}
|
2022-05-26 23:35:09 +05:30
|
|
|
|
2022-07-17 19:42:26 +05:30
|
|
|
private _findThemeDetailsFromId(themeId: string): {themeName: string, themeData: Partial<Variant>} | undefined {
|
2022-06-05 20:49:05 +05:30
|
|
|
for (const [themeName, themeData] of Object.entries(this._themeMapping)) {
|
2022-05-26 23:35:09 +05:30
|
|
|
if ("id" in themeData && themeData.id === themeId) {
|
2022-07-17 19:42:26 +05:30
|
|
|
return { themeName, themeData };
|
2022-05-26 23:35:09 +05:30
|
|
|
}
|
|
|
|
else if ("light" in themeData && themeData.light?.id === themeId) {
|
2022-07-17 19:42:26 +05:30
|
|
|
return { themeName, themeData: themeData.light };
|
2022-05-26 23:35:09 +05:30
|
|
|
}
|
|
|
|
else if ("dark" in themeData && themeData.dark?.id === themeId) {
|
2022-07-17 19:42:26 +05:30
|
|
|
return { themeName, themeData: themeData.dark };
|
2022-05-26 23:35:09 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-02 15:05:43 +05:30
|
|
|
|
2022-07-19 17:33:06 +05:30
|
|
|
private _addDefaultThemeToMapping(log: ILogItem) {
|
|
|
|
log.wrap("addDefaultThemeToMapping", l => {
|
|
|
|
const defaultThemeId = this.getDefaultTheme();
|
|
|
|
if (defaultThemeId) {
|
|
|
|
const themeDetails = this._findThemeDetailsFromId(defaultThemeId);
|
|
|
|
if (themeDetails) {
|
|
|
|
this._themeMapping["Default"] = { id: "default", cssLocation: themeDetails.themeData.cssLocation! };
|
|
|
|
const variables = themeDetails.themeData.variables;
|
|
|
|
if (variables) {
|
|
|
|
this._themeMapping["Default"].variables = variables;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
l.log({ l: "Default Theme", theme: defaultThemeId});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-14 19:27:18 +05:30
|
|
|
get preferredColorScheme(): ColorSchemePreference | undefined {
|
2022-06-02 15:05:43 +05:30
|
|
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
|
|
return ColorSchemePreference.Dark;
|
|
|
|
}
|
|
|
|
else if (window.matchMedia("(prefers-color-scheme: light)").matches) {
|
|
|
|
return ColorSchemePreference.Light;
|
|
|
|
}
|
|
|
|
}
|
2022-05-11 14:58:14 +05:30
|
|
|
}
|