Skip to content

Commit

Permalink
Merge pull request #43 from author-more/feat/theme-support
Browse files Browse the repository at this point in the history
feat: add themes support
  • Loading branch information
Belar authored Dec 21, 2024
2 parents e20aef0 + 83707a4 commit 2cad869
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 43 deletions.
12 changes: 8 additions & 4 deletions src/base/components/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ <h2>Instance</h2>
<input id="instance-save" type="button" value="Save" />
</div>
</div>
<!-- <div class="settings-section">
<div class="settings-section">
<div class="settings-section-header">
<h2>Theme</h2>
</div>
<div id="theme" class="settings-section-content">
<div onclick="localStorage.setItem('theme', 'light'); document.body.classList.add('theme-is-light'); document.body.classList.remove('theme-is-dark')" id="theme-select-light" class="theme-selection"><p>Light</p></div>
<div onclick="localStorage.setItem('theme', 'dark'); document.body.classList.add('theme-is-dark'); document.body.classList.remove('theme-is-light')" id="theme-select-dark" class="theme-selection"><p>Dark</p></div>
<select name="theme" id="theme-select">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
<option value="tab">Penpot</option>
</select>
</div>
</div> -->
</div>
<div class="settings-section">
<div class="settings-section-header">
<h2>Help</h2>
Expand Down
1 change: 1 addition & 0 deletions src/base/components/tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
border: none !important;
height: var(--tab-height);
width: calc(100% - var(--navBarWF));
filter: var(--theme-filter, none);

margin-left: 2.25rem;
}
Expand Down
2 changes: 1 addition & 1 deletion src/base/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<script src="./scripts/theme.js"></script>
<script src="./scripts/toggles.js"></script>
</head>
<body onload="ThemeCheck();">
<body>
<drag></drag>
<sl-include id="include-controls" src="./components/controls.html"></sl-include>
<sl-include src="./components/titlebar.html"></sl-include>
Expand Down
31 changes: 31 additions & 0 deletions src/base/scripts/electron-tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,47 @@ async function prepareTabReloadButton() {
function tabReadyHandler(tab) {
const webview = /** @type {WebviewTag} */ (tab.webview);

tab.once("webview-dom-ready", () => {
tab.on("active", () => requestTabTheme(tab));
});
tab.element.addEventListener("contextmenu", (event) => {
event.preventDefault();
window.api.send("openTabMenu", tab.id);
});
webview.addEventListener("ipc-message", (event) => {
const isThemeUpdate = event.channel === THEME_TAB_EVENTS.UPDATE;
if (isThemeUpdate) {
const [theme] = event.args;

handleInTabThemeUpdate(theme);
}
});
webview.addEventListener("page-title-updated", () => {
const newTitle = webview.getTitle();
tab.setTitle(newTitle);
});
}

/**
* Calls a tab and requests a theme update send-out.
* If no tab is provided, calls the active tab.
*
* @param {Tab =} tab
*/
async function requestTabTheme(tab) {
tab = tab || (await getActiveTab());

if (tab) {
const webview = /** @type {WebviewTag} */ (tab.webview);
webview?.send(THEME_TAB_EVENTS.REQUEST_UPDATE);
}
}

async function getActiveTab() {
const tabGroup = await getTabGroup();
return tabGroup?.getActiveTab();
}

async function getTabGroup() {
return /** @type {TabGroup | null} */ (
await getIncludedElement("tab-group", "#include-tabs")
Expand Down
115 changes: 110 additions & 5 deletions src/base/scripts/theme.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,112 @@
function ThemeCheck() {
if (localStorage.getItem('theme') === 'light') {
document.body.classList.add('theme-is-light')
/**
* @typedef {Parameters<typeof window.api.setTheme>[0]} ThemeId
* @typedef {ThemeId | "tab"} ThemeSetting
* @typedef {import("electron").IpcMessageEvent} IpcMessageEvent
*/

const THEME_STORE_KEY = "theme";
const THEME_TAB_EVENTS = Object.freeze({
REQUEST_UPDATE: "theme-request-update",
UPDATE: "theme-update",
});

/** @type {ThemeSetting | null} */
let currentThemeSetting = null;

window.addEventListener("DOMContentLoaded", () => {
currentThemeSetting = /** @type {ThemeSetting | null} */ (
localStorage.getItem(THEME_STORE_KEY)
);

if (currentThemeSetting) {
setTheme(currentThemeSetting);
}

prepareForm(currentThemeSetting);
});

/**
* @param {ThemeSetting | null} themeSetting
*/
async function prepareForm(themeSetting) {
const { themeSelect } = await getThemeSettingsForm();

if (themeSelect && themeSetting) {
themeSelect.value = themeSetting;
}

themeSelect?.addEventListener("change", (event) => {
const { target } = event;
const value = target instanceof HTMLSelectElement && target.value;

if (isThemeSetting(value)) {
const isTabTheme = value === "tab";

currentThemeSetting = value;
localStorage.setItem(THEME_STORE_KEY, value);

if (isTabTheme) {
requestTabTheme();
return;
}

setTheme(value);
} else {
document.body.classList.add('theme-is-dark')
currentThemeSetting = null;
localStorage.removeItem(THEME_STORE_KEY);
}
}
});
}

/**
* @param {string} themeId
*/
function setTheme(themeId) {
if (isThemeId(themeId)) {
window.api.setTheme(themeId);
}
}

async function getThemeSettingsForm() {
const themeSelect = await getIncludedElement(
"#theme-select",
"#include-settings",
HTMLSelectElement
);

return { themeSelect };
}

/**
* @param {string} inTabTheme
*/
function handleInTabThemeUpdate(inTabTheme) {
const shouldUseInTabTheme = currentThemeSetting === "tab";

if (shouldUseInTabTheme) {
setTheme(inTabTheme);
}
}

/**
*
* @param {unknown} value
* @returns {value is ThemeId}
*/
function isThemeId(value) {
return (
typeof value === "string" && ["light", "dark", "system"].includes(value)
);
}

/**
*
* @param {unknown} value
* @returns {value is ThemeSetting}
*/
function isThemeSetting(value) {
return (
typeof value === "string" &&
["light", "dark", "system", "tab"].includes(value)
);
}
40 changes: 39 additions & 1 deletion src/base/scripts/webviews/preload.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @ts-nocheck The file requires a review, before dedicating time to add quality types.

const { ipcRenderer } = require("electron");

// Set the title of the tab name
/// Instead of the tab name being "PROJECT_NAME - Penpot", this script will remove the " - Penpot" portion.
function SetTitleToDash() {
Expand Down Expand Up @@ -77,4 +79,40 @@ window.onload = function() {
}
};
}
};
};

// Theme
window.addEventListener("DOMContentLoaded", () =>
onClassChange(document.body, () => dispatchThemeUpdate())
);

ipcRenderer.on("theme-request-update", () => dispatchThemeUpdate());

/**
* Observes a node and executes a callback on a class change.
*
* @param {Parameters<MutationObserver["observe"]>[0]} node
* @param {function} callback
*/
function onClassChange(node, callback) {
const observer = new MutationObserver((mutations) => {
const hasClassChanged = mutations.some(
({ type, attributeName }) =>
type === "attributes" && attributeName === "class"
);

if (hasClassChanged) {
callback();
}
});

observer.observe(node, { attributes: true });
}

/**
* Sends an event, with a currently set theme, to the webview's host.
*/
function dispatchThemeUpdate() {
const isLightTheme = document.body.classList.contains("light");
ipcRenderer.sendToHost("theme-update", isLightTheme ? "light" : "dark");
}
61 changes: 31 additions & 30 deletions src/base/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,20 @@
border: 0;
}

.theme-is-light {
background: #ffffff;
@media (prefers-color-scheme: light) {
:root {
--theme-filter: invert(1);
}

body,
#settings {
background: #ffffff;
}

.titlebar {
filter: invert(1);
.titlebar,
.controls,
#settings > * {
filter: var(--theme-filter, none);
}

#theme-select-light {
Expand All @@ -24,8 +33,10 @@
}
}

.theme-is-dark {
background: #18181a;
@media (prefers-color-scheme: dark) {
body {
background: #18181a;
}

#theme-select-dark {
border-color: #00ff89;
Expand Down Expand Up @@ -134,7 +145,6 @@ drag {
margin: 6px;
min-width: 250px;
width: 300px;
transition: 1s all;

div:nth-child(1) > div.settings-section-header {
border-radius: 4px 4px 0px 0px;
Expand All @@ -154,13 +164,26 @@ drag {
display: flex;
margin: 12px;

input {
input,
select {
padding: 6px 12px;
border-radius: 4px;
border: 1px #656565 solid;
margin-right: 6px;
}

select {
width: 100%;
background: rgb(18, 18, 18);

@media (prefers-color-scheme: light) {
option {
background: rgb(237, 237, 237);
color: black;
}
}
}

input#instance-save {
background: #575151;
color: white;
Expand All @@ -177,28 +200,6 @@ div#theme {
gap: 12px;
}

.theme-selection {
border: 2px #7d7d7d solid;
border-radius: 10px;
padding: 12px 32px;
width: 100%;
text-align: center;

p {
margin: 0px;
}
}

div#theme-select-light {
color: black;
background: white;
}

div#theme-select-dark {
color: white;
background: #232222;
}

.settings-section-content a {
background: #3c413e;
color: white;
Expand Down
5 changes: 4 additions & 1 deletion src/process/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
type Electron = import("electron");

declare var api: {
send: (channel: string, data: unknown) => void;
setTheme: (themeId: Electron.NativeTheme["themeSource"]) => void;
onOpenTab: (callback: (href: string) => void) => void;
onTabMenuAction: (
callback: ({ command: string, tabId: number }) => void
) => void;
};

declare var mainWindow: import("electron").BrowserWindow;
declare var mainWindow: Electron.BrowserWindow;

declare var transparent: boolean;
declare var AppIcon: string;
3 changes: 3 additions & 0 deletions src/process/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ contextBridge.exposeInMainWorld(
ipcRenderer.send(channel, data);
}
},
setTheme: (themeId) => {
ipcRenderer.send("set-theme", themeId);
},
onOpenTab: (callback) =>
ipcRenderer.on("open-tab", (_event, value) => callback(value)),
onTabMenuAction: (callback) =>
Expand Down
Loading

0 comments on commit 2cad869

Please sign in to comment.