Save theme settings to local storage and improve slider styles
This commit is contained in:
parent
d879b52ebf
commit
229ada66dc
|
@ -1,11 +1,13 @@
|
|||
import { getStore } from "./store"
|
||||
import { getBackendUiStore } from "./store/backend"
|
||||
import { getAutomationStore } from "./store/automation/"
|
||||
import { getThemeStore } from "./store/theme"
|
||||
import analytics from "analytics"
|
||||
|
||||
export const store = getStore()
|
||||
export const backendUiStore = getBackendUiStore()
|
||||
export const automationStore = getAutomationStore()
|
||||
export const themeStore = getThemeStore()
|
||||
|
||||
export const initialise = async () => {
|
||||
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>
|
||||
import { themeStore } from "builderStore"
|
||||
import { Label, DropdownMenu, Toggle, Button } from "@budibase/bbui"
|
||||
|
||||
let anchor
|
||||
let popover
|
||||
let dark = false
|
||||
let hue = 208
|
||||
let saturation = 9
|
||||
let lightness = 16
|
||||
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>
|
||||
|
||||
<div class="topnavitemright" on:click={popover.show} bind:this={anchor}>
|
||||
|
@ -33,28 +15,36 @@
|
|||
<div class="content">
|
||||
<div>
|
||||
<Label extraSmall grey>Theme</Label>
|
||||
<Toggle thin text="Dark theme" bind:checked={dark} />
|
||||
<Toggle thin text="Dark theme" bind:checked={$themeStore.darkMode} />
|
||||
</div>
|
||||
{#if dark && !showAdvanced}
|
||||
{#if $themeStore.darkMode && !showAdvanced}
|
||||
<div class="button">
|
||||
<Button text on:click={() => (showAdvanced = true)}>Customise</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if dark && showAdvanced}
|
||||
{#if $themeStore.darkMode && showAdvanced}
|
||||
<div>
|
||||
<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>
|
||||
<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>
|
||||
<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 class="button">
|
||||
<Button text on:click={reset}>Reset</Button>
|
||||
<Button text on:click={themeStore.reset}>Reset</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -104,4 +94,48 @@
|
|||
.button {
|
||||
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>
|
||||
|
|
Loading…
Reference in New Issue