Skip to content

Commit 21cfea1

Browse files
authored
Merge pull request #49 from author-more/build/es-modules
build: switch to es modules
2 parents 2cad869 + fcfc4b7 commit 21cfea1

24 files changed

+198
-186
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ It delivers a desktop-like experience for Penpot users with the addition of inte
2626
1. Run `npm ci` to install packages.
2727
*Other package managers such as Yarn, PNPM, or Bun should work as well.*
2828
1. (Optional) Run `npm run dev` to start the application in development mode. This will open a new window with the application running.
29+
30+
> Note: Penpot Desktop is using ES Modules. Make sure to read the [ES Modules (ESM) in Electron guide](https://www.electronjs.org/docs/latest/tutorial/esm).
31+
2932
1. Run `npm run build` to build the application. By default, it will build for the current OS and architecture, but you can pass flags to build for other platforms. See the [Electron Builder documentation](https://www.electron.build/cli) for more information.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"url": "https://github.com/author-more/penpot-desktop/issues",
1919
"email": "penpotdesktop@authormore.com"
2020
},
21+
"type":"module",
2122
"scripts": {
2223
"start": "npm run dev",
2324
"build": "electron-builder --config build/electron-builder.yml",

src/base/components/titlebar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="titlebar">
22
<!-- Controls -->
33
<sl-tooltip content="Settings" style="--sl-tooltip-arrow-size: 0;">
4-
<button onclick="ToggleSettings()"><?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M19.6224 10.3954L18.5247 7.7448L20 6L18 4L16.2647 5.48295L13.5578 4.36974L12.9353 2H10.981L10.3491 4.40113L7.70441 5.51596L6 4L4 6L5.45337 7.78885L4.3725 10.4463L2 11V13L4.40111 13.6555L5.51575 16.2997L4 18L6 20L7.79116 18.5403L10.397 19.6123L11 22H13L13.6045 19.6132L16.2551 18.5155C16.6969 18.8313 18 20 18 20L20 18L18.5159 16.2494L19.6139 13.598L21.9999 12.9772L22 11L19.6224 10.3954Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></button>
4+
<button id="toggle-settings"><?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M19.6224 10.3954L18.5247 7.7448L20 6L18 4L16.2647 5.48295L13.5578 4.36974L12.9353 2H10.981L10.3491 4.40113L7.70441 5.51596L6 4L4 6L5.45337 7.78885L4.3725 10.4463L2 11V13L4.40111 13.6555L5.51575 16.2997L4 18L6 20L7.79116 18.5403L10.397 19.6123L11 22H13L13.6045 19.6132L16.2551 18.5155C16.6969 18.8313 18 20 18 20L20 18L18.5159 16.2494L19.6139 13.598L21.9999 12.9772L22 11L19.6224 10.3954Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></button>
55
</sl-tooltip>
66

77
<!-- Titlebar Buttons -->

src/base/global.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type Ipc = import("../types/ipc.ts");
2+
3+
declare var api: Ipc.Api

src/base/index.html

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,12 @@
33
<title>Penpot Desktop</title>
44
<link rel="stylesheet" href="./styles/index.css"/>
55
<link rel="stylesheet" href="../../node_modules/@shoelace-style/shoelace/cdn/themes/dark.css" onload="document.documentElement.classList.add('sl-theme-dark')"/>
6-
<script type="module" src="../../node_modules/@shoelace-style/shoelace/cdn/shoelace.js"></script>
7-
<script src="../../node_modules/electron-tabs/dist/electron-tabs.js"></script>
8-
<script src="./scripts/dom.js"></script>
9-
<script src="./scripts/electron-tabs.js"></script>
10-
<script src="./scripts/instance.js"></script>
11-
<script src="./scripts/theme.js"></script>
12-
<script src="./scripts/toggles.js"></script>
6+
<script src="./scripts/main.js" type="module"></script>
137
</head>
148
<body>
159
<drag></drag>
1610
<sl-include id="include-controls" src="./components/controls.html"></sl-include>
17-
<sl-include src="./components/titlebar.html"></sl-include>
11+
<sl-include id="include-titlebar" src="./components/titlebar.html"></sl-include>
1812
<sl-include src="./components/splash.html"></sl-include>
1913
<sl-include id="include-tabs" src="./components/tabs.html"></sl-include>
2014
<sl-include id="include-settings" src="./components/settings.html"></sl-include>
@@ -23,7 +17,7 @@
2317
<img src="./assets/penpot-logo/logo-white.png"/>
2418
<h2>No tabs are opened</h2>
2519
<p>Add a new tab to start making awesome things.</p>
26-
<button onclick='document.querySelector("body > #include-tabs > tab-group").shadowRoot.querySelector("div > nav > div.buttons > button").click(); ATWC()'>Create a tab</button>
20+
<button id="open-tab">Create a tab</button>
2721
</div>
2822
</body>
2923
</html>

src/base/scripts/dom.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* @param {string} includeSelector - The CSS selector of the sl-include element.
2626
* @param {Class<E> =} type - The expected type of the element.
2727
*/
28-
function getIncludedElement(selector, includeSelector, type) {
28+
export function getIncludedElement(selector, includeSelector, type) {
2929
return new Promise((resolve) => {
3030
const includeElement = document.querySelector(includeSelector);
3131
if (!includeElement) {
@@ -59,7 +59,7 @@ function getIncludedElement(selector, includeSelector, type) {
5959
* @param {ParentNode | null | undefined} parent
6060
* @return {E | null}
6161
*/
62-
function typedQuerySelector(selector, type, parent = document) {
62+
export function typedQuerySelector(selector, type, parent = document) {
6363
const element = parent?.querySelector(selector);
6464
if (element instanceof type) {
6565
return element;

src/base/scripts/electron-tabs.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { getIncludedElement, typedQuerySelector } from "./dom.js";
2+
import { handleInTabThemeUpdate, THEME_TAB_EVENTS } from "./theme.js";
3+
14
/**
25
* @typedef {import("electron-tabs").TabGroup} TabGroup
36
* @typedef {import("electron-tabs").Tab} Tab
47
* @typedef {import("electron").WebviewTag} WebviewTag
58
*/
69

710
const DEFAULT_INSTANCE = "https://design.penpot.app/";
8-
const PRELOAD_PATH = "./scripts/webviews/preload.js";
11+
const PRELOAD_PATH = "./scripts/webviews/preload.mjs";
912
const DEFAULT_TAB_OPTIONS = Object.freeze({
1013
src: DEFAULT_INSTANCE,
1114
active: true,
@@ -16,23 +19,23 @@ const DEFAULT_TAB_OPTIONS = Object.freeze({
1619
ready: tabReadyHandler,
1720
});
1821

19-
window.addEventListener("DOMContentLoaded", async () => {
22+
export async function initTabs() {
2023
const tabGroup = await getTabGroup();
2124

2225
tabGroup?.on("tab-removed", () => {
23-
ATWC();
26+
handleNoTabs();
2427
});
2528
tabGroup?.on("tab-added", () => {
26-
ATWC();
29+
handleNoTabs();
2730
});
2831

2932
prepareTabReloadButton();
30-
});
3133

32-
window.api.onOpenTab(openTab);
33-
window.api.onTabMenuAction(handleTabMenuAction);
34+
window.api.onOpenTab(openTab);
35+
window.api.onTabMenuAction(handleTabMenuAction);
36+
}
3437

35-
async function resetTabs() {
38+
export async function resetTabs() {
3639
const tabGroup = await getTabGroup();
3740
tabGroup?.eachTab((tab) => tab.close(false));
3841
openTab();
@@ -41,7 +44,7 @@ async function resetTabs() {
4144
/**
4245
* @param {string =} href
4346
*/
44-
async function setDefaultTab(href) {
47+
export async function setDefaultTab(href) {
4548
const tabGroup = await getTabGroup();
4649

4750
tabGroup?.setDefaultTab({
@@ -53,7 +56,7 @@ async function setDefaultTab(href) {
5356
/**
5457
* @param {string =} href
5558
*/
56-
async function openTab(href) {
59+
export async function openTab(href) {
5760
const tabGroup = await getTabGroup();
5861

5962
tabGroup?.addTab(
@@ -107,12 +110,12 @@ function tabReadyHandler(tab) {
107110
}
108111

109112
/**
110-
* Calls a tab and requests a theme update send-out.
113+
* Calls a tab and requests a theme update send-out.
111114
* If no tab is provided, calls the active tab.
112-
*
115+
*
113116
* @param {Tab =} tab
114117
*/
115-
async function requestTabTheme(tab) {
118+
export async function requestTabTheme(tab) {
116119
tab = tab || (await getActiveTab());
117120

118121
if (tab) {
@@ -126,7 +129,18 @@ async function getActiveTab() {
126129
return tabGroup?.getActiveTab();
127130
}
128131

129-
async function getTabGroup() {
132+
async function handleNoTabs() {
133+
const tabGroup = await getTabGroup();
134+
const tabs = tabGroup?.getTabs();
135+
const hasTabs = !!tabs?.length;
136+
137+
const noTabsExistPage = typedQuerySelector(".no-tabs-exist", HTMLElement);
138+
if (noTabsExistPage) {
139+
noTabsExistPage.style.display = hasTabs ? "none" : "inherit";
140+
}
141+
}
142+
143+
export async function getTabGroup() {
130144
return /** @type {TabGroup | null} */ (
131145
await getIncludedElement("tab-group", "#include-tabs")
132146
);

src/base/scripts/instance.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
import { getIncludedElement } from "./dom.js";
2+
import { openTab, resetTabs, setDefaultTab } from "./electron-tabs.js";
3+
14
const INSTANCE_STORE_KEY = "Instance";
25
const INSTANCE_EVENTS = Object.freeze({
36
REGISTER: "registerInstance",
47
REMOVE: "removeInstance",
58
});
69

7-
window.addEventListener("DOMContentLoaded", async () => {
10+
export async function initInstance() {
811
const savedInstance = await registerSavedInstance();
912

1013
await setDefaultTab(savedInstance);
1114
openTab(savedInstance);
1215
prepareSaveButton();
13-
});
16+
}
1417

1518
async function prepareSaveButton() {
1619
const { instanceSaveButton } = await getInstanceSettingsForm();
@@ -36,11 +39,11 @@ async function saveInstance(trigger) {
3639
await setDefaultTab(instance);
3740
} else {
3841
const savedInstance = localStorage.getItem(INSTANCE_STORE_KEY);
39-
42+
4043
if (savedInstance) {
4144
window.api.send(INSTANCE_EVENTS.REMOVE, savedInstance);
4245
}
43-
46+
4447
localStorage.removeItem(INSTANCE_STORE_KEY);
4548
await setDefaultTab();
4649
}

src/base/scripts/main.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import "../../../node_modules/@shoelace-style/shoelace/cdn/shoelace.js";
2+
import "../../../node_modules/electron-tabs/dist/electron-tabs.js";
3+
4+
import { initTabs } from "./electron-tabs.js";
5+
import { initInstance } from "./instance.js";
6+
import { initTheme } from "./theme.js";
7+
import { initToggles } from "./toggles.js";
8+
9+
window.addEventListener("DOMContentLoaded", () => {
10+
initTabs();
11+
initInstance();
12+
initTheme();
13+
initToggles();
14+
});

src/base/scripts/theme.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44
* @typedef {import("electron").IpcMessageEvent} IpcMessageEvent
55
*/
66

7+
import { getIncludedElement } from "./dom.js";
8+
import { requestTabTheme } from "./electron-tabs.js";
9+
710
const THEME_STORE_KEY = "theme";
8-
const THEME_TAB_EVENTS = Object.freeze({
11+
export const THEME_TAB_EVENTS = Object.freeze({
912
REQUEST_UPDATE: "theme-request-update",
1013
UPDATE: "theme-update",
1114
});
1215

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

16-
window.addEventListener("DOMContentLoaded", () => {
19+
export function initTheme() {
1720
currentThemeSetting = /** @type {ThemeSetting | null} */ (
1821
localStorage.getItem(THEME_STORE_KEY)
1922
);
@@ -23,7 +26,7 @@ window.addEventListener("DOMContentLoaded", () => {
2326
}
2427

2528
prepareForm(currentThemeSetting);
26-
});
29+
}
2730

2831
/**
2932
* @param {ThemeSetting | null} themeSetting
@@ -80,7 +83,7 @@ async function getThemeSettingsForm() {
8083
/**
8184
* @param {string} inTabTheme
8285
*/
83-
function handleInTabThemeUpdate(inTabTheme) {
86+
export function handleInTabThemeUpdate(inTabTheme) {
8487
const shouldUseInTabTheme = currentThemeSetting === "tab";
8588

8689
if (shouldUseInTabTheme) {

src/base/scripts/toggles.js

Lines changed: 17 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,29 @@
1-
/**
2-
* @typedef {import('@shoelace-style/shoelace').SlAlert} SlAlert
3-
*/
1+
import { getIncludedElement, typedQuerySelector } from "./dom.js";
2+
import { openTab } from "./electron-tabs.js";
43

5-
// Settings
6-
function ToggleSettings() {
4+
export async function initToggles() {
5+
const { toggleSettingsButton, openTabButton } = await getToggles();
6+
7+
toggleSettingsButton?.addEventListener("click", () => toggleSettings());
8+
openTabButton?.addEventListener("click", () => openTab());
9+
}
10+
11+
function toggleSettings() {
712
const settingsUi = typedQuerySelector("#settings", HTMLElement);
813
if (settingsUi) {
914
const isVisible = settingsUi.style.display === "flex";
1015
settingsUi.style.display = isVisible ? "none" : "flex";
1116
}
1217
}
1318

14-
// Detect if there are no tabs
15-
setTimeout(async () => {
16-
const tabGroup = await getTabGroup();
17-
const tabClose = tabGroup?.shadowRoot?.querySelector(
18-
"div > nav > div.tabs > div > span.tab-close"
19+
async function getToggles() {
20+
const toggleSettingsButton = await getIncludedElement(
21+
"#toggle-settings",
22+
"#include-titlebar",
23+
HTMLButtonElement
1924
);
20-
const button = tabGroup?.shadowRoot?.querySelector(
21-
"div > nav > div.buttons > button"
22-
);
23-
tabClose?.addEventListener("click", () => ATWC());
24-
button?.addEventListener("click", () => ATWC());
25-
}, 2000);
26-
27-
async function ATWC() {
28-
const tabGroup = await getTabGroup();
29-
const tabs = tabGroup?.shadowRoot?.querySelector("div > nav > div.tabs > *");
30-
const hasTabs = typeof tabs != "undefined" && tabs != null;
31-
32-
const noTabsExistPage = typedQuerySelector(".no-tabs-exist", HTMLElement);
33-
const tabClose = tabGroup?.shadowRoot?.querySelector(
34-
"div > nav > div.tabs > div > span.tab-close"
35-
);
36-
37-
if (noTabsExistPage) {
38-
noTabsExistPage.style.display = hasTabs ? "none" : "inherit";
39-
}
40-
tabClose?.addEventListener("click", () => ATWC());
41-
}
4225

43-
// Alerts
44-
/// Docs: https://shoelace.style/getting-started/usage#methods
45-
/// No Instance
46-
function ShowNoInstance() {
47-
const noInstanceAlert = getNoInstanceAlert();
48-
if (noInstanceAlert) {
49-
noInstanceAlert.show();
50-
}
51-
}
26+
const openTabButton = typedQuerySelector("#open-tab", HTMLButtonElement);
5227

53-
function HideNoInstance() {
54-
const noInstanceAlert = getNoInstanceAlert();
55-
if (noInstanceAlert) {
56-
noInstanceAlert.hide();
57-
}
28+
return { toggleSettingsButton, openTabButton };
5829
}
59-
60-
function getNoInstanceAlert() {
61-
// Type guard would require a type import. As a compromise, we use a type guard for a HTMLElement and assume the type as SlAlert.
62-
return /** @type {SlAlert | null}*/ (
63-
typedQuerySelector("#noinstance", HTMLElement)
64-
);
65-
}
66-
67-
setTimeout(() => {
68-
const instanceField = typedQuerySelector("#InstanceField", HTMLInputElement);
69-
const isInstanceFieldEmpty = instanceField?.value === "";
70-
if (isInstanceFieldEmpty) {
71-
ShowNoInstance();
72-
}
73-
}, 1000);

src/base/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
5+
},
6+
"include": ["**/*", "../types"]
7+
}

src/process/global.d.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,2 @@
1-
type Electron = import("electron");
2-
3-
declare var api: {
4-
send: (channel: string, data: unknown) => void;
5-
setTheme: (themeId: Electron.NativeTheme["themeSource"]) => void;
6-
onOpenTab: (callback: (href: string) => void) => void;
7-
onTabMenuAction: (
8-
callback: ({ command: string, tabId: number }) => void
9-
) => void;
10-
};
11-
12-
declare var mainWindow: Electron.BrowserWindow;
13-
141
declare var transparent: boolean;
152
declare var AppIcon: string;

0 commit comments

Comments
 (0)