Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/images/dark-theme-working-reference.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/default-theme-low-contrast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/main/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export class Storage {
...DEFAULT_SETTINGS.app,
...this.settings.app,
},
theme: {
...DEFAULT_SETTINGS.theme,
...this.settings.theme,
},
};

this.writeSync(this.settings);
Expand Down
11 changes: 8 additions & 3 deletions src/main/Ui/ThemeManager/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { app, ipcMain, IpcMainEvent } from "electron";
import { app, ipcMain, IpcMainEvent, nativeTheme } from "electron";
import Azip from "adm-zip";
import * as fs from "fs";
import * as path from "path";
Expand All @@ -7,7 +7,7 @@ import { storage } from "Main/Storage";
import { logger } from "Main/Logger";

import { DEFAULT_THEME, TEST_THEME_ID, DOWNLOAD_ZIP_URI, DOWNLOAD_ZIP_PATH } from "Const";
import { keysToCamelCase, keysToKebabCase } from "Utils/Common";
import { getThemeColorScheme, keysToCamelCase, keysToKebabCase } from "Utils/Common";
import { mkPath, access, downloadFile } from "Utils/Main";
import ThemeValidator from "./ThemeValidator";

Expand Down Expand Up @@ -51,12 +51,17 @@ export default class ThemeManager {
const currentThemeId = storage.settings.theme.currentTheme;
const disableThemes = storage.settings.app.disableThemes;

let currentTheme = this.themes.get(currentThemeId) || this.creatorThemes.get(currentThemeId);
let currentTheme =
this.themes.get(currentThemeId) || this.creatorThemes.get(currentThemeId) || DEFAULT_THEME;

if (currentThemeId === DEFAULT_THEME.id || disableThemes) {
currentTheme = DEFAULT_THEME;
}

if (!disableThemes) {
nativeTheme.themeSource = getThemeColorScheme(currentTheme.palette);
}

app.emit("loadCurrentTheme", currentTheme);
}

Expand Down
45 changes: 44 additions & 1 deletion src/main/Ui/WindowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ import {
IpcMainEvent,
WebContents,
IpcMainInvokeEvent,
nativeTheme,
} from "electron";

import Window from "./Window";
import MenuManager from "./MenuManager";
import { storage } from "Main/Storage";
import { logger } from "Main/Logger";
import { dialogs } from "Main/Dialogs";
import { CHROME_GPU, DEFAULT_WIN_OPTIONS, HOMEPAGE, NEW_FILE_TAB_TITLE, RECENT_FILES } from "Const";
import { normalizeUrl, isAppAuthGrandLink, isAppAuthRedeem, parseURL } from "Utils/Common";
import {
getThemeColorScheme,
normalizeUrl,
isAppAuthGrandLink,
isAppAuthRedeem,
parseURL,
} from "Utils/Common";
import { mkPath } from "Utils/Main";

export default class WindowManager {
Expand All @@ -28,6 +36,12 @@ export default class WindowManager {
constructor() {
this.menuManager = new MenuManager();

const themePreference = this.applyThemePreference(storage.settings.theme.preference);
if (themePreference !== storage.settings.theme.preference) {
storage.settings.theme.preference = themePreference;
storage.save();
}

this.restoreData();
this.registerEvents();
}
Expand Down Expand Up @@ -263,6 +277,30 @@ export default class WindowManager {

window.setUserId(data.userId);
}
private setThemePreference(_: IpcMainEvent, data: WebApi.SetThemePreference) {
const preference = this.resolveThemePreference(data?.themePreference);

storage.settings.theme.preference = preference;
storage.save();

if (storage.settings.app.disableThemes) {
nativeTheme.themeSource = preference;
}
}
private resolveThemePreference(preference: unknown): WebApi.ThemePreference {
if (preference === "system" || preference === "light" || preference === "dark") {
return preference;
}

logger.warn(`Invalid theme preference "${preference}", falling back to system theme`);
return "system";
}
private applyThemePreference(preference: unknown): WebApi.ThemePreference {
const resolvedPreference = this.resolveThemePreference(preference);
nativeTheme.themeSource = resolvedPreference;

return resolvedPreference;
}
private startAppAuth(event: IpcMainEvent, data: { grantPath: string }) {
if (isAppAuthGrandLink(data.grantPath)) {
const url = `${HOMEPAGE}${data.grantPath}?desktop_protocol=figma`;
Expand Down Expand Up @@ -600,6 +638,10 @@ export default class WindowManager {
}

private changeTheme(event: IpcMainEvent, theme: Themes.Theme) {
if (!storage.settings.app.disableThemes) {
nativeTheme.themeSource = getThemeColorScheme(theme.palette);
}

for (const [_, window] of this.windows) {
window.changeTheme(event, theme);
}
Expand Down Expand Up @@ -655,6 +697,7 @@ export default class WindowManager {
ipcMain.handle("writeFiles", this.writeFiles.bind(this));

ipcMain.on("setInitialOptions", this.setInitialOptions.bind(this));
ipcMain.on("setThemePreference", this.setThemePreference.bind(this));
ipcMain.on("setUser", this.setUser.bind(this));
ipcMain.on("openDevTools", this.openDevTools.bind(this));
ipcMain.on("startAppAuth", this.startAppAuth.bind(this));
Expand Down
19 changes: 18 additions & 1 deletion src/renderer/DesktopAPI/ThemesApplier.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as E from "electron";
import { DEFAULT_THEME, SELECTORS_TO_IGNORE, PROPS_WITH_COLOR, CHROME_GPU } from "Const";
import { getColorsMap, variablesColorsMap } from "Utils/Common";
import { getColorsMap, getThemeColorScheme, variablesColorsMap } from "Utils/Common";

export class ThemesApplier {
private enabled = false;
private currentTheme: Themes.Theme;
private targetElements: Set<HTMLElement | CSSStyleRule> = new Set();

Expand All @@ -14,6 +15,7 @@ export class ThemesApplier {
return;
}

this.enabled = true;
this.registerEvents();
});
}
Expand All @@ -28,6 +30,15 @@ export class ThemesApplier {
this.applyPalette(palette);
});
}
public syncColorScheme() {
if (!this.enabled) {
return;
}

const colorScheme = getThemeColorScheme(this.currentTheme.palette);
document.body.dataset.preferredTheme = colorScheme;
document.body.style.colorScheme = colorScheme;
}

private changePalette(theme: Themes.Theme) {
this.currentTheme = theme;
Expand Down Expand Up @@ -69,6 +80,12 @@ export class ThemesApplier {
private applyPalette(palette: Themes.Palette, el: HTMLElement | CSSStyleRule = document.body) {
const keys = Object.keys(palette);

if (el instanceof HTMLElement) {
const colorScheme = getThemeColorScheme(palette);
el.dataset.preferredTheme = colorScheme;
el.style.colorScheme = colorScheme;
}

for (const key of keys) {
const value = palette[key];
el.style.setProperty(`--${key}`, value);
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/DesktopAPI/webBinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface IntiApiOptions {
}

const API_VERSION = 111;
const APP_VERSION = '999.0.0';
const APP_VERSION = "999.0.0";
let webPort: MessagePort;
const mainProcessCancelCallbacks: Map<number, () => void> = new Map();

Expand Down Expand Up @@ -308,10 +308,10 @@ const publicAPI: any = {
setInitialOptions(args: WebApi.SetInitOptions) {
sendMsgToMain("setInitialOptions", args);
},
// setTheme(args: any) {
// console.log("isTabOpen, args: ", args);
// // n.send("setTheme", e.getString("theme", "dark"));
// },
setThemePreference(args: WebApi.SetThemePreference) {
themes.syncColorScheme();
sendMsgToMain("setThemePreference", args);
},

setFeatureFlags(args: any) {
sendMsgToMain("setFeatureFlags", args);
Expand Down
1 change: 1 addition & 0 deletions src/types/Common/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ declare namespace Types {
};
theme: {
currentTheme: string;
preference: WebApi.ThemePreference;
};
[path: string]: any;
}
Expand Down
4 changes: 4 additions & 0 deletions src/types/Renderer/webApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ declare namespace WebApi {
orgId: string | null;
navigationConfig: NavigationConfig;
}
type ThemePreference = "system" | "light" | "dark";
interface SetThemePreference {
themePreference: ThemePreference;
}
interface WriteNewExtensionDirectoryToDiskFile {
name: string;
content: string;
Expand Down
5 changes: 5 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ declare namespace Electron {
channel: "setInitialOptions",
listener: (event: IpcMainInvokeEvent, data: WebApi.SetInitOptions) => void,
): this;
on(
channel: "setThemePreference",
listener: (event: IpcMainInvokeEvent, data: WebApi.SetThemePreference) => void,
): this;
on(
channel: "finishAppAuth",
listener: (event: IpcMainInvokeEvent, auth: { redirectURL: string }) => void,
Expand Down Expand Up @@ -474,6 +478,7 @@ declare namespace Electron {
send(channed: "windowClose", tabs: Types.TabFront[]): this;
send(channed: "toggleThemeCreatorPreviewMask"): this;
send(channed: "setInitialOptions", data: WebApi.SetInitOptions): this;
send(channed: "setThemePreference", data: WebApi.SetThemePreference): this;
send(channed: "setUser", userId: string): this;
send(channed: "toggleCurrentWindowFullscreen"): this;
send(
Expand Down
18 changes: 17 additions & 1 deletion src/utils/Common/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,23 @@ export const variablesColorsMap: Themes.ColorsMap = {
"rgb(27, 196, 125)": "var(--bg-beta-label)",
};

export const getColorsMap = (palette: Themes.Palette, currentPalette?: Themes.ColorsMap): Themes.ColorsMap => {
export const getThemeColorScheme = (palette: Themes.Palette): "light" | "dark" => {
const background = palette["bg-panel"];
const channels = [1, 3, 5].map(
(offset) => Number.parseInt(background.slice(offset, offset + 2), 16) / 255,
);
const [red, green, blue] = channels.map((channel) =>
channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4),
);
const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;

return luminance > 0.179 ? "light" : "dark";
};

export const getColorsMap = (
palette: Themes.Palette,
currentPalette?: Themes.ColorsMap,
): Themes.ColorsMap => {
const defaultColorsMap: Themes.ColorsMap = {
"rgb(255, 255, 255)": palette["bg-panel"],
"rgb(252, 252, 252)": palette["bg-panel"],
Expand Down
1 change: 1 addition & 0 deletions src/utils/Main/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const DEFAULT_SETTINGS: Types.SettingsInterface = {
},
theme: {
currentTheme: "0",
preference: "system",
},
ui: {
scalePanel: 1,
Expand Down
1 change: 1 addition & 0 deletions src/utils/Render/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const DEFAULT_SETTINGS: Types.SettingsInterface = {
},
theme: {
currentTheme: "0",
preference: "system",
},
ui: {
scalePanel: 1,
Expand Down