Save theme settings to local storage and improve slider styles
This commit is contained in:
parent
cb5bce660b
commit
1f5b1cfd10
|
@ -1,11 +1,13 @@
|
||||||
import { getStore } from "./store"
|
import { getStore } from "./store"
|
||||||
import { getBackendUiStore } from "./store/backend"
|
import { getBackendUiStore } from "./store/backend"
|
||||||
import { getAutomationStore } from "./store/automation/"
|
import { getAutomationStore } from "./store/automation/"
|
||||||
|
import { getThemeStore } from "./store/theme"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
export const store = getStore()
|
export const store = getStore()
|
||||||
export const backendUiStore = getBackendUiStore()
|
export const backendUiStore = getBackendUiStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
|
export const themeStore = getThemeStore()
|
||||||
|
|
||||||
export const initialise = async () => {
|
export const initialise = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { get, writable } from "svelte/store"
|
||||||
|
|
||||||
|
export const localStorageStore = (localStorageKey, initialValue) => {
|
||||||
|
const store = writable(initialValue, () => {
|
||||||
|
// Hydrate from local storage when we get a new subscriber
|
||||||
|
hydrate()
|
||||||
|
|
||||||
|
// Listen for local storage changes and keep store in sync
|
||||||
|
const storageListener = ({ key }) => key === localStorageKey && hydrate()
|
||||||
|
window.addEventListener("storage", storageListener)
|
||||||
|
return () => window.removeEventListener("storage", storageListener)
|
||||||
|
})
|
||||||
|
|
||||||
|
// New store setter which updates the store and localstorage
|
||||||
|
const set = value => {
|
||||||
|
store.set(value)
|
||||||
|
localStorage.setItem(localStorageKey, JSON.stringify(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// New store updater which updates the store and localstorage
|
||||||
|
const update = updaterFn => set(updaterFn(get(store)))
|
||||||
|
|
||||||
|
// Hydrates the store from localstorage
|
||||||
|
const hydrate = () => {
|
||||||
|
const localValue = localStorage.getItem(localStorageKey)
|
||||||
|
if (localValue == null) {
|
||||||
|
set(initialValue)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
store.set(JSON.parse(localValue))
|
||||||
|
} catch {
|
||||||
|
set(initialValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch the default svelte store functions with our overrides
|
||||||
|
return {
|
||||||
|
...store,
|
||||||
|
set,
|
||||||
|
update,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { localStorageStore } from "./localStorage"
|
||||||
|
|
||||||
|
export const getThemeStore = () => {
|
||||||
|
const themeElement = document.documentElement
|
||||||
|
const initialValue = {
|
||||||
|
darkMode: false,
|
||||||
|
hue: 208,
|
||||||
|
saturation: 9,
|
||||||
|
lightness: 16,
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = localStorageStore("bb-theme", initialValue)
|
||||||
|
|
||||||
|
// Sets a CSS variable on the root document element
|
||||||
|
function setCSSVariable(prop, value) {
|
||||||
|
themeElement.style.setProperty(prop, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets the custom theme to the default dark theme.
|
||||||
|
// The reset option is only available when dark theme is on, which is why it
|
||||||
|
// sets dark mode to true here
|
||||||
|
store.reset = () => {
|
||||||
|
store.set({
|
||||||
|
...initialValue,
|
||||||
|
darkMode: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update theme when store changes
|
||||||
|
store.subscribe(theme => {
|
||||||
|
themeElement.classList[theme.darkMode ? "add" : "remove"]("dark")
|
||||||
|
setCSSVariable("--theme-hue", Math.round(theme.hue))
|
||||||
|
setCSSVariable("--theme-saturation", `${Math.round(theme.saturation)}%`)
|
||||||
|
setCSSVariable("--theme-brightness", `${Math.round(theme.lightness)}%`)
|
||||||
|
})
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
|
@ -1,28 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { themeStore } from "builderStore"
|
||||||
import { Label, DropdownMenu, Toggle, Button } from "@budibase/bbui"
|
import { Label, DropdownMenu, Toggle, Button } from "@budibase/bbui"
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let popover
|
let popover
|
||||||
let dark = false
|
|
||||||
let hue = 208
|
|
||||||
let saturation = 9
|
|
||||||
let lightness = 16
|
|
||||||
let showAdvanced = false
|
let showAdvanced = false
|
||||||
|
|
||||||
function setCSSVariable(prop, value) {
|
|
||||||
document.documentElement.style.setProperty(prop, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
hue = 208
|
|
||||||
saturation = 9
|
|
||||||
lightness = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
$: document.documentElement.classList[dark ? "add" : "remove"]("dark")
|
|
||||||
$: setCSSVariable("--theme-hue", Math.round(hue))
|
|
||||||
$: setCSSVariable("--theme-saturation", `${Math.round(saturation)}%`)
|
|
||||||
$: setCSSVariable("--theme-brightness", `${Math.round(lightness)}%`)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="topnavitemright" on:click={popover.show} bind:this={anchor}>
|
<div class="topnavitemright" on:click={popover.show} bind:this={anchor}>
|
||||||
|
@ -33,28 +15,36 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div>
|
<div>
|
||||||
<Label extraSmall grey>Theme</Label>
|
<Label extraSmall grey>Theme</Label>
|
||||||
<Toggle thin text="Dark theme" bind:checked={dark} />
|
<Toggle thin text="Dark theme" bind:checked={$themeStore.darkMode} />
|
||||||
</div>
|
</div>
|
||||||
{#if dark && !showAdvanced}
|
{#if $themeStore.darkMode && !showAdvanced}
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<Button text on:click={() => (showAdvanced = true)}>Customise</Button>
|
<Button text on:click={() => (showAdvanced = true)}>Customise</Button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if dark && showAdvanced}
|
{#if $themeStore.darkMode && showAdvanced}
|
||||||
<div>
|
<div>
|
||||||
<Label extraSmall grey>Hue</Label>
|
<Label extraSmall grey>Hue</Label>
|
||||||
<input type="range" bind:value={hue} min="0" max="360" />
|
<input type="range" bind:value={$themeStore.hue} min="0" max="360" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label extraSmall grey>Saturation</Label>
|
<Label extraSmall grey>Saturation</Label>
|
||||||
<input type="range" bind:value={saturation} min="0" max="100" />
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={$themeStore.saturation}
|
||||||
|
min="0"
|
||||||
|
max="100" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label extraSmall grey>Lightness</Label>
|
<Label extraSmall grey>Lightness</Label>
|
||||||
<input type="range" bind:value={lightness} min="0" max="32" />
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={$themeStore.lightness}
|
||||||
|
min="0"
|
||||||
|
max="32" />
|
||||||
</div>
|
</div>
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<Button text on:click={reset}>Reset</Button>
|
<Button text on:click={themeStore.reset}>Reset</Button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,4 +94,48 @@
|
||||||
.button {
|
.button {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
input[type="range"]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
background: var(--grey-4);
|
||||||
|
border-radius: 9px;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border: none;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-moz-range-track {
|
||||||
|
background: var(--grey-4);
|
||||||
|
border-radius: 9px;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue