resonatecoop / beam

A resonate audio beam
GNU General Public License v3.0
17 stars 6 forks source link

Dark mode #48

Closed simonv3 closed 2 years ago

simonv3 commented 2 years ago

this could be a toggle on the /profile page, but could also inherit from the user's settings on their computer

https://www.electronjs.org/docs/v14-x-y/tutorial/dark-mode

peterklingelhofer commented 2 years ago

I'm getting these errors:

ERROR in src/components/Profile.tsx:58:37
TS2339: Property 'darkMode' does not exist on type 'Window & typeof globalThis'.
    56 |   }, [fetchStats]);
    57 |   const toggleDarkMode = async () => {
  > 58 |     const isDarkMode = await window.darkMode.toggle()
       |                                     ^^^^^^^^
    59 |     if (
    60 |       document
    61 |       && document.getElementById('theme-source') !== null

ERROR in src/components/Profile.tsx:62:25
TS2531: Object is possibly 'null'.
    60 |       document
    61 |       && document.getElementById('theme-source') !== null
  > 62 |       && 'innerHTML' in document.getElementById('theme-source')
       |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    63 |     ) {
    64 |       document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
    65 |     }

ERROR in src/components/Profile.tsx:64:7
TS2531: Object is possibly 'null'.
    62 |       && 'innerHTML' in document.getElementById('theme-source')
    63 |     ) {
  > 64 |       document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
       |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    65 |     }
    66 |   }
    67 |   

ERROR in src/components/Profile.tsx:69:18
TS2339: Property 'darkMode' does not exist on type 'Window & typeof globalThis'.
    67 |   
    68 |   const resetToSystem = async () => {
  > 69 |     await window.darkMode.system()
       |                  ^^^^^^^^
    70 |     if (
    71 |       document
    72 |       && document.getElementById('theme-source') !== null

ERROR in src/components/Profile.tsx:73:25
TS2531: Object is possibly 'null'.
    71 |       document
    72 |       && document.getElementById('theme-source') !== null
  > 73 |       && 'innerHTML' in document.getElementById('theme-source')
       |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    74 |     ) {
    75 |       document.getElementById('theme-source').innerHTML = 'System'
    76 |     }

ERROR in src/components/Profile.tsx:75:7
TS2531: Object is possibly 'null'.
    73 |       && 'innerHTML' in document.getElementById('theme-source')
    74 |     ) {
  > 75 |       document.getElementById('theme-source').innerHTML = 'System'
       |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    76 |     }
    77 |   }
    78 |

With this diff:

diff --git a/public/electron.js b/public/electron.js
index c6d5274..276bbf5 100644
--- a/public/electron.js
+++ b/public/electron.js
@@ -1,5 +1,5 @@
 // Module to control the application lifecycle and the native browser window.
-const { app, BrowserWindow, protocol, shell } = require("electron");
+const { app, BrowserWindow, nativeTheme, ipcMain, protocol, shell } = require("electron");
 const path = require("path");

 // Create the native browser window.
@@ -28,6 +28,20 @@ function createWindow() {
   if (!app.isPackaged) {
     mainWindow.webContents.openDevTools();
   }
+
+  // Dark mode
+  ipcMain.handle('dark-mode:toggle', () => {
+    if (nativeTheme.shouldUseDarkColors) {
+      nativeTheme.themeSource = 'light'
+    } else {
+      nativeTheme.themeSource = 'dark'
+    }
+    return nativeTheme.shouldUseDarkColors
+  })
+
+  ipcMain.handle('dark-mode:system', () => {
+    nativeTheme.themeSource = 'system'
+  })
 }

 // Setup a local proxy to adjust the paths of requested files when loading
diff --git a/public/preload.js b/public/preload.js
index bd020c8..a2ef178 100644
--- a/public/preload.js
+++ b/public/preload.js
@@ -1,10 +1,15 @@
 // All of the Node.js APIs are available in the preload process.
 // It has the same sandbox as a Chrome extension.
-const { contextBridge } = require("electron");
+const { contextBridge, ipcRenderer } = require("electron");

 // As an example, here we use the exposeInMainWorld API to expose the browsers
 // and node versions to the main window.
 // They'll be accessible at "window.versions".
 process.once("loaded", () => {
   contextBridge.exposeInMainWorld("versions", process.versions);
+
+  contextBridge.exposeInMainWorld('darkMode', {
+    toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
+    system: () => ipcRenderer.invoke('dark-mode:system')
+  })
 });
diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx
index 65907d0..f224d08 100644
--- a/src/components/Profile.tsx
+++ b/src/components/Profile.tsx
@@ -54,6 +54,28 @@ const Profile: React.FC = () => {
   React.useEffect(() => {
     fetchStats();
   }, [fetchStats]);
+  const toggleDarkMode = async () => {
+    const isDarkMode = await window.darkMode.toggle()
+    if (
+      document
+      && document.getElementById('theme-source') !== null
+      && 'innerHTML' in document.getElementById('theme-source')
+    ) {
+      document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
+    }
+  }
+  
+  const resetToSystem = async () => {
+    await window.darkMode.system()
+    if (
+      document
+      && document.getElementById('theme-source') !== null
+      && 'innerHTML' in document.getElementById('theme-source')
+    ) {
+      document.getElementById('theme-source').innerHTML = 'System'
+    }
+  }
+

   return (
     <div
@@ -62,6 +84,8 @@ const Profile: React.FC = () => {
         max-width: 820px;
       `}
     >
+      <Button onClick={toggleDarkMode}>Toggle Dark Mode</Button>
+      <Button onClick={resetToSystem}>Reset to System Theme</Button>
       {user && (
         <div
           className={css`
diff --git a/src/index.css b/src/index.css
index ec2585e..322a402 100644
--- a/src/index.css
+++ b/src/index.css
@@ -11,3 +11,11 @@ code {
   font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
     monospace;
 }
+
+@media (prefers-color-scheme: dark) {
+  body { background: #333; color: white; }
+}
+
+@media (prefers-color-scheme: light) {
+  body { background: #ddd; color: black; }
+}

Seems like public/preload.js isn't making darkMode available on the main window, the way I have this set up in the above diff.

simonv3 commented 2 years ago

@peterklingelhofer do you have a branch I could test on? Are these errors happening when running the app inside electron with yarn electron:start or just with yarn start

peterklingelhofer commented 2 years ago

@simonv3 here is the branch

These errors are happening with good ol' yarn start

simonv3 commented 2 years ago

Ah that makes sense, I don't think electron library is available like that in the normal web view, which is what yarn start does (it's just the normal react-script start reference). If beam were to be deployed on a server, we'd have to figure out a way to do this without referencing the electron package. I actually wonder, is there no way for the browser to detect this without having electron supplying it?

peterklingelhofer commented 2 years ago

Ack, yeah that's tricky in case we want this to potentially eventually replace the web player as well.

Also, that's silly that pulling from upstream from the GitHub website defaults to merging, should definitely be a rebase... I'll fix this branch later to cleanup that commit history. EDIT: Done!