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 },
"showLineNumberGutter": {type: "boolean", default:true},
"showFoldGutter": {type: "boolean", default:true},
"autoUpdate": {type: "boolean", default: true},
"allowBetaVersions": {type: "boolean", default: false},
"enableGlobalHotkey": {type: "boolean", default: false},
"globalHotkey": {type: "string", default: "CmdOrCtrl+Shift+H"},
@ -49,6 +50,7 @@ const defaults = {
emacsMetaKey: "meta",
showLineNumberGutter: true,
showFoldGutter: true,
autoUpdate: true,
allowBetaVersions: false,
enableGlobalHotkey: false,
globalHotkey: "CmdOrCtrl+Shift+H",

View File

@ -21,7 +21,7 @@
languageAuto: true,
theme: window.heynote.themeMode.initial,
initialTheme: window.heynote.themeMode.initial,
systemTheme: 'system',
themeSetting: 'system',
development: window.location.href.indexOf("dev=1") !== -1,
showLanguageSelector: false,
showSettings: false,
@ -32,7 +32,7 @@
mounted() {
window.heynote.themeMode.get().then((mode) => {
this.theme = mode.computed
this.systemTheme = mode.theme
this.themeSetting = mode.theme
})
const onThemeChange = (theme) => {
this.theme = theme
@ -69,12 +69,12 @@
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")
newTheme = this.themeSetting === "system" ? "dark" : (this.themeSetting === "dark" ? "light" : "system")
} 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)
this.systemTheme = newTheme
this.themeSetting = newTheme
this.$refs.editor.focus()
},
@ -129,7 +129,8 @@
:language="language"
:languageAuto="languageAuto"
:theme="theme"
:systemTheme="systemTheme"
:themeSetting="themeSetting"
:autoUpdate="settings.autoUpdate"
:allowBetaVersions="settings.allowBetaVersions"
@toggleTheme="toggleTheme"
@openLanguageSelector="openLanguageSelector"

View File

@ -13,7 +13,8 @@
"language",
"languageAuto",
"theme",
"systemTheme",
"themeSetting",
"autoUpdate",
"allowBetaVersions",
],
@ -84,9 +85,13 @@
>
<span class="icon icon-format"></span>
</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">
<span :class="'icon ' + systemTheme"></span>
<span :class="'icon ' + themeSetting"></span>
</div>
</div>
</template>

View File

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

View File

@ -1,5 +1,7 @@
<script>
import KeyboardHotkey from "./KeyboardHotkey.vue"
import TabListItem from "./TabListItem.vue"
import TabContent from "./TabContent.vue"
export default {
props: {
@ -8,6 +10,8 @@
},
components: {
KeyboardHotkey,
TabListItem,
TabContent,
},
data() {
@ -24,6 +28,9 @@
allowBetaVersions: this.initialSettings.allowBetaVersions,
enableGlobalHotkey: this.initialSettings.enableGlobalHotkey,
globalHotkey: this.initialSettings.globalHotkey,
autoUpdate: this.initialSettings.autoUpdate,
activeTab: "general",
}
},
@ -51,8 +58,9 @@
allowBetaVersions: this.allowBetaVersions,
enableGlobalHotkey: this.enableGlobalHotkey,
globalHotkey: this.globalHotkey,
autoUpdate: this.autoUpdate,
})
}
},
}
}
</script>
@ -61,77 +69,122 @@
<div class="settings">
<div class="dialog">
<div class="dialog-content">
<h1>Settings</h1>
<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>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"
<nav class="sidebar">
<h1>Settings</h1>
<ul>
<TabListItem
name="General"
tab="general"
:activeTab="activeTab"
@click="activeTab = 'general'"
/>
</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>
<TabListItem
name="Appearance"
tab="appearance"
:activeTab="activeTab"
@click="activeTab = 'appearance'"
/>
<TabListItem
name="Updates"
tab="updates"
:activeTab="activeTab"
@click="activeTab = 'updates'"
/>
</ul>
</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>
@ -190,34 +243,46 @@
box-shadow: 0 0 25px rgba(0, 0, 0, 0.3)
.dialog-content
flex-grow: 1
padding: 40px
h1
font-size: 20px
font-weight: 600
margin-bottom: 20px
.row
display: flex
.entry
margin-bottom: 24px
margin-right: 20px
h2
font-weight: 600
margin-bottom: 10px
font-size: 14px
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
display: flex
.sidebar
box-sizing: border-box
width: 140px
border-right: 1px solid #eee
padding-top: 20px
+dark-mode
border-right: 1px solid #222
h1
font-size: 16px
font-weight: 700
margin-bottom: 20px
padding: 0 20px
margin-bottom: 20px
.settings-content
flex-grow: 1
padding: 40px
overflow-y: auto
.row
display: flex
.entry
margin-bottom: 24px
margin-right: 20px
h2
font-weight: 600
margin-bottom: 10px
font-size: 14px
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
border-radius: 0 0 5px 5px
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()
@media (prefers-color-scheme: dark)
@content
body[theme=dark] &
@content

View File

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