Browse Source

docs: update macos-dark-mode fiddle and guide content (#29198)

* update macos dark mode docs for Electron v12

* pr review fixes

* more pr review fixes

* reorg paragraphs for better flow

* Update docs/tutorial/dark-mode.md

Co-authored-by: Erick Zhao <[email protected]>

* pr fixes

Co-authored-by: Erick Zhao <[email protected]>
Ethan Arrowood 3 years ago
parent
commit
5656493676

+ 11 - 8
docs/fiddles/features/macos-dark-mode/main.js

@@ -1,11 +1,12 @@
 const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
+const path = require('path')
 
 function createWindow () {
   const win = new BrowserWindow({
     width: 800,
     height: 600,
     webPreferences: {
-      nodeIntegration: true
+      preload: path.join(__dirname, 'preload.js')
     }
   })
 
@@ -25,16 +26,18 @@ function createWindow () {
   })
 }
 
-app.whenReady().then(createWindow)
+app.whenReady().then(() => {
+  createWindow()
+
+  app.on('activate', () => {
+    if (BrowserWindow.getAllWindows().length === 0) {
+      createWindow()
+    }
+  })
+})
 
 app.on('window-all-closed', () => {
   if (process.platform !== 'darwin') {
     app.quit()
   }
 })
-
-app.on('activate', () => {
-  if (BrowserWindow.getAllWindows().length === 0) {
-    createWindow()
-  }
-})

+ 6 - 0
docs/fiddles/features/macos-dark-mode/preload.js

@@ -0,0 +1,6 @@
+const { contextBridge, ipcRenderer } = require('electron')
+
+contextBridge.exposeInMainWorld('darkMode', {
+  toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
+  system: () => ipcRenderer.invoke('dark-mode:system')
+})

+ 2 - 4
docs/fiddles/features/macos-dark-mode/renderer.js

@@ -1,11 +1,9 @@
-const { ipcRenderer } = require('electron')
-
 document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
-  const isDarkMode = await ipcRenderer.invoke('dark-mode:toggle')
+  const isDarkMode = await window.darkMode.toggle()
   document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
 })
 
 document.getElementById('reset-to-system').addEventListener('click', async () => {
-  await ipcRenderer.invoke('dark-mode:system')
+  await window.darkMode.system()
   document.getElementById('theme-source').innerHTML = 'System'
 })

+ 74 - 71
docs/tutorial/dark-mode.md

@@ -47,18 +47,18 @@ of this theming, due to the use of the macOS 10.14 SDK.
 
 ## Example
 
-We'll start with a working application from the
-[Quick Start Guide](quick-start.md) and add functionality gradually.
+This example demonstrates an Electron application that derives its theme colors from the
+`nativeTheme`. Additionally, it provides theme toggle and reset controls using IPC channels.
 
-First, let's edit our interface so users can toggle between light and dark
-modes.  This basic UI contains buttons to change the `nativeTheme.themeSource`
-setting and a text element indicating which `themeSource` value is selected.
-By default, Electron follows the system's dark mode preference, so we
-will hardcode the theme source as "System".
+```javascript fiddle='docs/fiddles/features/macos-dark-mode'
 
-Add the following lines to the `index.html` file:
+```
+
+### How does this work?
 
-```html
+Starting with the `index.html` file:
+
+```html title='index.html'
 <!DOCTYPE html>
 <html>
 <head>
@@ -80,65 +80,70 @@ Add the following lines to the `index.html` file:
 </html>
 ```
 
-Next, add [event listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
-that listen for `click` events on the toggle buttons. Because the `nativeTheme`
-module only exposed in the Main process, you need to set up each listener's
-callback to use IPC to send messages to and handle responses from the Main
-process:
+And the `style.css` file:
+
+```css title='style.css'
+@media (prefers-color-scheme: dark) {
+  body { background: #333; color: white; }
+}
 
-* when the "Toggle Dark Mode" button is clicked, we send the
-`dark-mode:toggle` message (event) to tell the Main process to trigger a theme
-change, and update the "Current Theme Source" label in the UI based on the
-response from the Main process.
-* when the "Reset to System Theme" button is clicked, we send the
-`dark-mode:system` message (event) to tell the Main process to use the system
-color scheme, and update the "Current Theme Source" label to `System`.
+@media (prefers-color-scheme: light) {
+  body { background: #ddd; color: black; }
+}
+```
+
+The example renders an HTML page with a couple elements. The `<strong id="theme-source">`
+ element shows which theme is currently selected, and the two `<button>` elements are the
+ controls. The CSS file uses the [`prefers-color-scheme`][prefers-color-scheme] media query
+ to set the `<body>` element background and text colors.
+
+The `preload.js` script adds a new API to the `window` object called `darkMode`. This API
+ exposes two IPC channels to the renderer process, `'dark-mode:toggle'` and `'dark-mode:system'`.
+ It also assigns two methods, `toggle` and `system`, which pass messages from the renderer to the
+ main process.
+
+```js title='preload.js'
+const { contextBridge, ipcRenderer } = require('electron')
+
+contextBridge.exposeInMainWorld('darkMode', {
+  toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
+  system: () => ipcRenderer.invoke('dark-mode:system')
+})
+```
 
-To add listeners and handlers, add the following lines to the `renderer.js` file:
+Now the renderer process can communicate with the main process securely and perform the necessary
+ mutations to the `nativeTheme` object.
 
-```javascript
-const { ipcRenderer } = require('electron')
+The `renderer.js` file is responsible for controlling the `<button>` functionality.
 
+```js title='renderer.js'
 document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
-  const isDarkMode = await ipcRenderer.invoke('dark-mode:toggle')
+  const isDarkMode = await window.darkMode.toggle()
   document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
 })
 
 document.getElementById('reset-to-system').addEventListener('click', async () => {
-  await ipcRenderer.invoke('dark-mode:system')
+  await window.darkMode.system()
   document.getElementById('theme-source').innerHTML = 'System'
 })
 ```
 
-If you run your code at this point, you'll see that your buttons don't do
-anything just yet, and your Main process will output an error like this when
-you click on your buttons:
-`Error occurred in handler for 'dark-mode:toggle': No handler registered for 'dark-mode:toggle'`
-This is expected — we haven't actually touched any `nativeTheme` code yet.
+Using `addEventListener`, the `renderer.js` file adds `'click'` [event listeners][event-listeners]
+ to each button element. Each event listener handler makes calls to the respective `window.darkMode`
+ API methods.
 
-Now that we're done wiring the IPC from the Renderer's side, the next step
-is to update the `main.js` file to handle events from the Renderer process.
+Finally, the `main.js` file represents the main process and contains the actual `nativeTheme` API.
 
-Depending on the received event, we update the
-[`nativeTheme.themeSource`](../api/native-theme.md#nativethemethemesource)
-property to apply the desired theme on the system's native UI elements
-(e.g. context menus) and propagate the preferred color scheme to the Renderer
-process:
-
-* Upon receiving `dark-mode:toggle`, we check if the dark theme is currently
-active using the `nativeTheme.shouldUseDarkColors` property, and set the
-`themeSource` to the opposite theme.
-* Upon receiving `dark-mode:system`, we reset the `themeSource` to `system`.
-
-```javascript
+```js
 const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
+const path = require('path')
 
 function createWindow () {
   const win = new BrowserWindow({
     width: 800,
     height: 600,
     webPreferences: {
-      nodeIntegration: true
+      preload: path.join(__dirname, 'preload.js')
     }
   })
 
@@ -154,44 +159,41 @@ function createWindow () {
   })
 
   ipcMain.handle('dark-mode:system', () => {
-    nativeTheme.themeSource = 'system'
+    nativeTheme.themeSouce = 'system'
   })
 }
 
-app.whenReady().then(createWindow)
+app.whenReady().then(() => {
+  createWindow()
+
+  app.on('activate', () => {
+    if (BrowserWindow.getAllWindows().length === 0) {
+      createWindow()
+    }
+  })
+})
 
 app.on('window-all-closed', () => {
   if (process.platform !== 'darwin') {
     app.quit()
   }
 })
-
-app.on('activate', () => {
-  if (BrowserWindow.getAllWindows().length === 0) {
-    createWindow()
-  }
-})
 ```
 
-The final step is to add a bit of styling to enable dark mode for the web parts
-of the UI by leveraging the [`prefers-color-scheme`][prefer-color-scheme] CSS
-attribute. The value of `prefers-color-scheme` will follow your
-`nativeTheme.themeSource` setting.
-
-Create a `styles.css` file and add the following lines:
+The `ipcMain.handle` methods are how the main process responds to the click events from the buttons
+ on the HTML page.
 
-```css fiddle='docs/fiddles/features/macos-dark-mode'
-@media (prefers-color-scheme: dark) {
-  body { background:  #333; color: white; }
-}
+The `'dark-mode:toggle'` IPC channel handler method checks the `shouldUseDarkColors` boolean property,
+ sets the corresponding `themeSource`, and then returns the current `shouldUseDarkColors` property.
+ Looking back on the renderer process event listener for this IPC channel, the return value from this
+ handler is utilized to assign the correct text to the `<strong id='theme-source'>` element.
 
-@media (prefers-color-scheme: light) {
-  body { background:  #ddd; color: black; }
-}
-```
+The `'dark-mode:system'` IPC channel handler method assigns the string `'system'` to the `themeSource`
+ and returns nothing. This also corresponds with the relative renderer process event listener as the
+ method is awaited with no return value expected.
 
-After launching the Electron application, you can change modes or reset the
-theme to system default by clicking corresponding buttons:
+Run the example using Electron Fiddle and then click the "Toggle Dark Mode" button; the app should
+ start alternating between a light and dark background color.
 
 ![Dark Mode](../images/dark_mode.gif)
 
@@ -199,4 +201,5 @@ theme to system default by clicking corresponding buttons:
 [electron-forge]: https://www.electronforge.io/
 [electron-packager]: https://github.com/electron/electron-packager
 [packager-darwindarkmode-api]: https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html#darwindarkmodesupport
-[prefer-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
+[prefers-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
+[event-listeners]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener