Read dark/light mode from system, automatically listen for changes, and add button for toggling the mode to the status bar

This commit is contained in:
Jonatan Heyman 2023-01-15 11:59:17 +01:00
parent fbac07d338
commit 2f842612fb
8 changed files with 105 additions and 16 deletions

View File

@ -54,7 +54,7 @@ async function createWindow() {
// Consider using contextBridge.exposeInMainWorld
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
nodeIntegration: true,
contextIsolation: false,
contextIsolation: true,
},
})
@ -121,3 +121,10 @@ ipcMain.handle('open-win', (_, arg) => {
childWindow.loadFile(indexHtml, { hash: arg })
}
})
ipcMain.handle('dark-mode:set', (event, mode) => {
nativeTheme.themeSource = mode
})
ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)

View File

@ -1,3 +1,32 @@
const { contextBridge, ipcRenderer } = require('electron')
const getComputedTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light"
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
let darkModeChangeListener = null
contextBridge.exposeInMainWorld('darkMode', {
set: (mode) => ipcRenderer.invoke('dark-mode:set', mode),
get: async () => {
const mode = await ipcRenderer.invoke('dark-mode:get')
return {
theme: mode,
computed: getComputedTheme(),
}
},
onChange: (callback) => {
darkModeChangeListener = (event) => {
callback(event.matches ? "dark" : "light")
}
mediaMatch.addEventListener('change', darkModeChangeListener)
return mediaMatch
},
removeListener() {
mediaMatch.removeEventListener('change', darkModeChangeListener)
},
initial: getComputedTheme(),
})
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {

BIN
public/icons/both-mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/icons/dark-mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/icons/light-mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -3,7 +3,6 @@
import StatusBar from './components/StatusBar.vue'
import Editor from './components/Editor.vue'
console.log("[App.vue]", `Hello world from Electron ${process.versions.electron}!`)
export default {
components: {
@ -18,11 +17,39 @@
column: 1,
language: "plaintext",
languageAuto: true,
theme: "dark",
theme: window.darkMode.initial,
initialTheme: window.darkMode.initial,
systemTheme: 'system',
}
},
mounted() {
window.darkMode.get().then((mode) => {
this.theme = mode.computed
this.systemTheme = mode.theme
})
window.darkMode.onChange((theme) => {
this.theme = theme
})
},
beforeUnmount() {
window.darkMode.removeListener()
},
methods: {
toggleTheme() {
let newTheme
// when the "system" theme is used, make sure that the first click always results in amn actual theme change
if (this.initialTheme === "light") {
newTheme = this.systemTheme === "system" ? "dark" : (this.systemTheme === "dark" ? "light" : "system")
} else {
newTheme = this.systemTheme === "system" ? "light" : (this.systemTheme === "light" ? "dark" : "system")
}
window.darkMode.set(newTheme)
this.systemTheme = newTheme
},
onCursorChange(e) {
//console.log("onCursorChange:", e)
this.line = e.cursorLine.line
@ -47,7 +74,8 @@
:language="language"
:languageAuto="languageAuto"
:theme="theme"
@toggleTheme="theme = theme === 'dark' ? 'light' : 'dark'"
:systemTheme="systemTheme"
@toggleTheme="toggleTheme"
class="status"
/>
</template>

View File

@ -19,6 +19,7 @@
"language",
"languageAuto",
"theme",
"systemTheme",
],
mounted() {
@ -44,11 +45,13 @@
Col <span class="num">{{ column }}</span>
</div>
<div class="spacer"></div>
<div class="status-block theme clickable" @click="$emit('toggleTheme')">{{ theme }}</div>
<div class="status-block lang clickable">
{{ languageName }}
<span v-if="languageAuto" class="auto">(auto)</span>
</div>
<div class="status-block theme clickable" @click="$emit('toggleTheme')">
<span :class="'icon ' + systemTheme"></span>
</div>
</div>
</template>
@ -76,23 +79,45 @@
color: rgba(255, 255, 255, 0.75)
.status-block.lang .auto
color: rgba(255, 255, 255, 0.55)
.theme .icon
opacity: 0.9
.spacer
flex-grow: 1
.status-block
padding: 2px 12px
padding: 2px 10px
cursor: default
&:first-child
padding-left: 12px
&:last-child
padding-right: 12px
&.clickable
cursor: pointer
&:hover
background: rgba(255,255,255, 0.1)
&.line-number
background-color: rgba(255,255,255, 0.1)
.line-number
color: rgba(255, 255, 255, 0.7)
.num
color: rgba(255, 255, 255, 1.0)
&.lang
.lang
.auto
color: rgba(255, 255, 255, 0.7)
.theme
padding-top: 0
padding-bottom: 0
.icon
display: block
width: 14px
height: 22px
background-size: 14px
background-repeat: no-repeat
background-position: center center
&.dark
background-image: url("/icons/dark-mode.png")
&.light
background-image: url("/icons/light-mode.png")
&.system
background-image: url("/icons/both-mode.png")
</style>

View File

@ -24,7 +24,7 @@ export class HeynoteEditor {
//minimalSetup,
customSetup,
this.theme.of("dark" ? heynoteDark : heynoteLight),
this.theme.of(theme === "dark" ? heynoteDark : heynoteLight),
heynoteBase,
indentUnit.of(" "),
EditorView.scrollMargins.of(f => {