Settings improvements (#88)

* Organize Settings in different tabs

* Add setting for turning off/on auto updates.

* Remove debug log

* Use a better variable name (systemTheme -> themeSetting)

* Store theme setting for Heynote webapp in local storage
This commit is contained in:
Jonatan Heyman 2023-12-28 16:48:56 +01:00 committed by GitHub
parent 77ed45cfa2
commit 6022356478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 277 additions and 136 deletions

View File

@ -25,6 +25,7 @@ const schema = {
"emacsMetaKey": { "enum": [null, "alt", "meta"], default: null }, "emacsMetaKey": { "enum": [null, "alt", "meta"], default: null },
"showLineNumberGutter": {type: "boolean", default:true}, "showLineNumberGutter": {type: "boolean", default:true},
"showFoldGutter": {type: "boolean", default:true}, "showFoldGutter": {type: "boolean", default:true},
"autoUpdate": {type: "boolean", default: true},
"allowBetaVersions": {type: "boolean", default: false}, "allowBetaVersions": {type: "boolean", default: false},
"enableGlobalHotkey": {type: "boolean", default: false}, "enableGlobalHotkey": {type: "boolean", default: false},
"globalHotkey": {type: "string", default: "CmdOrCtrl+Shift+H"}, "globalHotkey": {type: "string", default: "CmdOrCtrl+Shift+H"},
@ -49,6 +50,7 @@ const defaults = {
emacsMetaKey: "meta", emacsMetaKey: "meta",
showLineNumberGutter: true, showLineNumberGutter: true,
showFoldGutter: true, showFoldGutter: true,
autoUpdate: true,
allowBetaVersions: false, allowBetaVersions: false,
enableGlobalHotkey: false, enableGlobalHotkey: false,
globalHotkey: "CmdOrCtrl+Shift+H", globalHotkey: "CmdOrCtrl+Shift+H",

View File

@ -21,7 +21,7 @@
languageAuto: true, languageAuto: true,
theme: window.heynote.themeMode.initial, theme: window.heynote.themeMode.initial,
initialTheme: window.heynote.themeMode.initial, initialTheme: window.heynote.themeMode.initial,
systemTheme: 'system', themeSetting: 'system',
development: window.location.href.indexOf("dev=1") !== -1, development: window.location.href.indexOf("dev=1") !== -1,
showLanguageSelector: false, showLanguageSelector: false,
showSettings: false, showSettings: false,
@ -32,7 +32,7 @@
mounted() { mounted() {
window.heynote.themeMode.get().then((mode) => { window.heynote.themeMode.get().then((mode) => {
this.theme = mode.computed this.theme = mode.computed
this.systemTheme = mode.theme this.themeSetting = mode.theme
}) })
const onThemeChange = (theme) => { const onThemeChange = (theme) => {
this.theme = theme this.theme = theme
@ -69,12 +69,12 @@
let newTheme let newTheme
// when the "system" theme is used, make sure that the first click always results in amn actual theme change // when the "system" theme is used, make sure that the first click always results in amn actual theme change
if (this.initialTheme === "light") { if (this.initialTheme === "light") {
newTheme = this.systemTheme === "system" ? "dark" : (this.systemTheme === "dark" ? "light" : "system") newTheme = this.themeSetting === "system" ? "dark" : (this.themeSetting === "dark" ? "light" : "system")
} else { } else {
newTheme = this.systemTheme === "system" ? "light" : (this.systemTheme === "light" ? "dark" : "system") newTheme = this.themeSetting === "system" ? "light" : (this.themeSetting === "light" ? "dark" : "system")
} }
window.heynote.themeMode.set(newTheme) window.heynote.themeMode.set(newTheme)
this.systemTheme = newTheme this.themeSetting = newTheme
this.$refs.editor.focus() this.$refs.editor.focus()
}, },
@ -129,7 +129,8 @@
:language="language" :language="language"
:languageAuto="languageAuto" :languageAuto="languageAuto"
:theme="theme" :theme="theme"
:systemTheme="systemTheme" :themeSetting="themeSetting"
:autoUpdate="settings.autoUpdate"
:allowBetaVersions="settings.allowBetaVersions" :allowBetaVersions="settings.allowBetaVersions"
@toggleTheme="toggleTheme" @toggleTheme="toggleTheme"
@openLanguageSelector="openLanguageSelector" @openLanguageSelector="openLanguageSelector"

View File

@ -13,7 +13,8 @@
"language", "language",
"languageAuto", "languageAuto",
"theme", "theme",
"systemTheme", "themeSetting",
"autoUpdate",
"allowBetaVersions", "allowBetaVersions",
], ],
@ -84,9 +85,13 @@
> >
<span class="icon icon-format"></span> <span class="icon icon-format"></span>
</div> </div>
<UpdateStatusItem v-if="updatesEnabled" :allowBetaVersions="allowBetaVersions" /> <UpdateStatusItem
v-if="updatesEnabled"
:autoUpdate="autoUpdate"
:allowBetaVersions="allowBetaVersions"
/>
<div class="status-block theme clickable" @click="$emit('toggleTheme')" title="Toggle dark/light mode"> <div class="status-block theme clickable" @click="$emit('toggleTheme')" title="Toggle dark/light mode">
<span :class="'icon ' + systemTheme"></span> <span :class="'icon ' + themeSetting"></span>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,6 +1,7 @@
<script> <script>
export default { export default {
props: [ props: [
"autoUpdate",
"allowBetaVersions", "allowBetaVersions",
], ],
@ -16,7 +17,6 @@
total: 0.0, total: 0.0,
bytesPerSecond: 0.0, bytesPerSecond: 0.0,
}, },
checkForUpdateIntervalId: null, checkForUpdateIntervalId: null,
} }
}, },
@ -50,14 +50,6 @@
this.updateProgress = progress this.updateProgress = progress
} }
}) })
// check for update now
this.checkForUpdate()
// check for updates every 8 hours
this.checkForUpdateIntervalId = setInterval(() => {
this.checkForUpdate()
}, 1000 * 3600 * 8)
}, },
beforeUnmount() { beforeUnmount() {
@ -67,11 +59,27 @@
}, },
watch: { watch: {
allowBetaVersions: { autoUpdate: {
handler: function (newValue) { immediate: true,
this.checkForUpdate() handler(autoUpdate) {
if (this.checkForUpdateIntervalId) {
clearInterval(this.checkForUpdateIntervalId)
}
if (autoUpdate) {
// check for update now
this.checkForUpdate()
// check for updates every 8 hours
this.checkForUpdateIntervalId = setInterval(() => {
this.checkForUpdate()
}, 1000 * 3600 * 8)
}
}, },
} },
allowBetaVersions(newValue) {
this.checkForUpdate()
},
}, },
computed: { computed: {

View File

@ -1,5 +1,7 @@
<script> <script>
import KeyboardHotkey from "./KeyboardHotkey.vue" import KeyboardHotkey from "./KeyboardHotkey.vue"
import TabListItem from "./TabListItem.vue"
import TabContent from "./TabContent.vue"
export default { export default {
props: { props: {
@ -8,6 +10,8 @@
}, },
components: { components: {
KeyboardHotkey, KeyboardHotkey,
TabListItem,
TabContent,
}, },
data() { data() {
@ -24,6 +28,9 @@
allowBetaVersions: this.initialSettings.allowBetaVersions, allowBetaVersions: this.initialSettings.allowBetaVersions,
enableGlobalHotkey: this.initialSettings.enableGlobalHotkey, enableGlobalHotkey: this.initialSettings.enableGlobalHotkey,
globalHotkey: this.initialSettings.globalHotkey, globalHotkey: this.initialSettings.globalHotkey,
autoUpdate: this.initialSettings.autoUpdate,
activeTab: "general",
} }
}, },
@ -51,8 +58,9 @@
allowBetaVersions: this.allowBetaVersions, allowBetaVersions: this.allowBetaVersions,
enableGlobalHotkey: this.enableGlobalHotkey, enableGlobalHotkey: this.enableGlobalHotkey,
globalHotkey: this.globalHotkey, globalHotkey: this.globalHotkey,
autoUpdate: this.autoUpdate,
}) })
} },
} }
} }
</script> </script>
@ -61,77 +69,122 @@
<div class="settings"> <div class="settings">
<div class="dialog"> <div class="dialog">
<div class="dialog-content"> <div class="dialog-content">
<h1>Settings</h1> <nav class="sidebar">
<div class="row"> <h1>Settings</h1>
<div class="entry"> <ul>
<h2>Keymap</h2> <TabListItem
<select ref="keymapSelector" v-model="keymap" @change="updateSettings"> name="General"
<template v-for="km in keymaps" :key="km.value"> tab="general"
<option :selected="km.value === keymap" :value="km.value">{{ km.name }}</option> :activeTab="activeTab"
</template> @click="activeTab = 'general'"
</select>
</div>
<div class="entry" v-if="keymap === 'emacs' && isMac">
<h2>Meta Key</h2>
<select v-model="metaKey" @change="updateSettings">
<option :selected="metaKey === 'meta'" value="meta">Command</option>
<option :selected="metaKey === 'alt'" value="alt">Option</option>
</select>
</div>
</div>
<div class="row">
<div class="entry">
<h2>Gutters</h2>
<label>
<input
type="checkbox"
v-model="showLineNumberGutter"
@change="updateSettings"
/>
Show line numbers
</label>
<label>
<input
type="checkbox"
v-model="showFoldGutter"
@change="updateSettings"
/>
Show fold gutter
</label>
</div>
</div>
<div class="row">
<div class="entry">
<h2>Global Keyboard Shortcut</h2>
<label class="keyboard-shortcut-label">
<input
type="checkbox"
v-model="enableGlobalHotkey"
@change="updateSettings"
/>
Enable Global Hotkey
</label>
<KeyboardHotkey
:disabled="!enableGlobalHotkey"
v-model="globalHotkey"
@change="updateSettings"
/> />
</div> <TabListItem
</div> name="Appearance"
<div class="row"> tab="appearance"
<div class="entry"> :activeTab="activeTab"
<h2>Beta Versions</h2> @click="activeTab = 'appearance'"
<label> />
<input <TabListItem
type="checkbox" name="Updates"
v-model="allowBetaVersions" tab="updates"
@change="updateSettings" :activeTab="activeTab"
/> @click="activeTab = 'updates'"
Use beta versions of Heynote />
</label> </ul>
</div> </nav>
<div class="settings-content">
<TabContent tab="general" :activeTab="activeTab">
<div class="row">
<div class="entry">
<h2>Keymap</h2>
<select ref="keymapSelector" v-model="keymap" @change="updateSettings">
<template v-for="km in keymaps" :key="km.value">
<option :selected="km.value === keymap" :value="km.value">{{ km.name }}</option>
</template>
</select>
</div>
<div class="entry" v-if="keymap === 'emacs' && isMac">
<h2>Meta Key</h2>
<select v-model="metaKey" @change="updateSettings">
<option :selected="metaKey === 'meta'" value="meta">Command</option>
<option :selected="metaKey === 'alt'" value="alt">Option</option>
</select>
</div>
</div>
<div class="row">
<div class="entry">
<h2>Global Keyboard Shortcut</h2>
<label class="keyboard-shortcut-label">
<input
type="checkbox"
v-model="enableGlobalHotkey"
@change="updateSettings"
/>
Enable Global Hotkey
</label>
<KeyboardHotkey
:disabled="!enableGlobalHotkey"
v-model="globalHotkey"
@change="updateSettings"
/>
</div>
</div>
</TabContent>
<TabContent tab="appearance" :activeTab="activeTab">
<div class="row">
<div class="entry">
<h2>Gutters</h2>
<label>
<input
type="checkbox"
v-model="showLineNumberGutter"
@change="updateSettings"
/>
Show line numbers
</label>
<label>
<input
type="checkbox"
v-model="showFoldGutter"
@change="updateSettings"
/>
Show fold gutter
</label>
</div>
</div>
</TabContent>
<TabContent tab="updates" :activeTab="activeTab">
<div class="row">
<div class="entry">
<h2>Auto Update</h2>
<label>
<input
type="checkbox"
v-model="autoUpdate"
@change="updateSettings"
/>
Periodically check for new updates
</label>
</div>
</div>
<div class="row">
<div class="entry">
<h2>Beta Versions</h2>
<label>
<input
type="checkbox"
v-model="allowBetaVersions"
@change="updateSettings"
/>
Use beta versions of Heynote
</label>
</div>
</div>
</TabContent>
</div> </div>
</div> </div>
@ -190,34 +243,46 @@
box-shadow: 0 0 25px rgba(0, 0, 0, 0.3) box-shadow: 0 0 25px rgba(0, 0, 0, 0.3)
.dialog-content .dialog-content
flex-grow: 1 flex-grow: 1
padding: 40px display: flex
h1 .sidebar
font-size: 20px box-sizing: border-box
font-weight: 600 width: 140px
margin-bottom: 20px border-right: 1px solid #eee
padding-top: 20px
.row +dark-mode
display: flex border-right: 1px solid #222
.entry h1
margin-bottom: 24px font-size: 16px
margin-right: 20px font-weight: 700
h2 margin-bottom: 20px
font-weight: 600 padding: 0 20px
margin-bottom: 10px margin-bottom: 20px
font-size: 14px .settings-content
select flex-grow: 1
width: 200px padding: 40px
&:focus overflow-y: auto
outline: none .row
label display: flex
display: block .entry
user-select: none margin-bottom: 24px
&.keyboard-shortcut-label margin-right: 20px
margin-bottom: 14px h2
> input[type=checkbox] font-weight: 600
position: relative margin-bottom: 10px
top: 2px font-size: 14px
left: -3px select
width: 200px
&:focus
outline: none
label
display: block
user-select: none
&.keyboard-shortcut-label
margin-bottom: 14px
> input[type=checkbox]
position: relative
top: 2px
left: -3px
.bottom-bar .bottom-bar
border-radius: 0 0 5px 5px border-radius: 0 0 5px 5px
background: #eee background: #eee

View File

@ -0,0 +1,24 @@
<script>
export default {
props: ["tab", "activeTab"],
computed: {
className() {
return "tab-content " + (this.tab === this.activeTab ? "active" : "")
}
}
}
</script>
<template>
<div :class="className">
<slot></slot>
</div>
</template>
<style scoped lang="sass">
.tab-content
display: none
&.active
display: block
</style>

View File

@ -0,0 +1,34 @@
<script>
export default {
props: ["tab", "activeTab", "name"],
computed: {
tabClass() {
return this.tab === this.activeTab ? "active" : ""
}
}
}
</script>
<template>
<li :class="tabClass">{{ name }}</li>
</template>
<style scoped lang="sass">
li
padding: 9px 20px
font-size: 13px
user-select: none
cursor: pointer
&:hover
background: #f1f1f1
+dark-mode
background: #292929
&.active
background: #48b57e
color: #fff
cursor: default
+dark-mode
background: #1b6540
</style>

View File

@ -1,5 +1,3 @@
=dark-mode() =dark-mode()
@media (prefers-color-scheme: dark)
@content
body[theme=dark] & body[theme=dark] &
@content @content

View File

@ -1,11 +1,15 @@
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)') const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
let themeModeChangeListener = null
let autoUpdateCallbacks = null
let themeCallback = null let themeCallback = null
mediaMatch.addEventListener("change", async (event) => {
if (themeCallback) {
themeCallback((await Heynote.themeMode.get()).computed)
}
})
let autoUpdateCallbacks = null
let currencyData = null let currencyData = null
export default { const Heynote = {
platform: { platform: {
isMac: true, isMac: true,
isWindows: false, isWindows: false,
@ -41,27 +45,25 @@ export default {
themeMode: { themeMode: {
set: (mode) => { set: (mode) => {
localStorage.setItem("theme", mode)
themeCallback(mode) themeCallback(mode)
document.body.setAttribute("theme", mode === "dark" ? "dark" : "light") console.log("set theme to", mode)
}, },
get: async () => { get: async () => {
const theme = localStorage.getItem("theme") || "system"
const systemTheme = mediaMatch.matches ? "dark" : "light"
return { return {
theme: "light", theme: theme,
computed: "light", computed: theme === "system" ? systemTheme : theme,
} }
}, },
onChange: (callback) => { onChange: (callback) => {
themeCallback = callback themeCallback = callback
themeModeChangeListener = (event) => {
callback(event.matches ? "dark" : "light")
}
mediaMatch.addEventListener('change', themeModeChangeListener)
return mediaMatch
}, },
removeListener() { removeListener() {
mediaMatch.removeEventListener('change', themeModeChangeListener) themeCallback = null
}, },
initial: "light", initial: localStorage.getItem("theme") || "system",
}, },
settings: { settings: {
@ -77,3 +79,5 @@ export default {
return currencyData return currencyData
}, },
} }
export default Heynote