frontend: add theme switching
This commit is contained in:
parent
c8604515d6
commit
704b35ae2b
6 changed files with 77 additions and 21 deletions
|
@ -59,15 +59,6 @@
|
||||||
--radius-xxl: calc(5.25 * var(--sradius-unit));
|
--radius-xxl: calc(5.25 * var(--sradius-unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-light {
|
|
||||||
--foreground-color-1: hsl(180, 11%, 7%);
|
|
||||||
--foreground-color-2: hsl(180, 11%, 12%);
|
|
||||||
--foreground-color-3: hsl(180, 11%, 17%);
|
|
||||||
--background-color-1: rgb(253, 254, 255);
|
|
||||||
--background-color-2: rgb(218, 219, 220);
|
|
||||||
--background-color-3: rgb(153, 154, 155);
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,11 +1,34 @@
|
||||||
<script>
|
<script>
|
||||||
import { CloudIcon } from "svelte-feather-icons";
|
import { CloudIcon } from "svelte-feather-icons";
|
||||||
import { gatewayStatus, showSidebar, selectedChannel, smallViewport, showChannelView } from "../stores";
|
import { gatewayStatus, showSidebar, selectedChannel, smallViewport, showChannelView, theme } from "../stores";
|
||||||
import ChannelView from "./ChannelView.svelte";
|
import ChannelView from "./ChannelView.svelte";
|
||||||
import OverlayProvider from "./overlays/OverlayProvider.svelte";
|
import OverlayProvider from "./overlays/OverlayProvider.svelte";
|
||||||
import Sidebar from "./Sidebar.svelte";
|
import Sidebar from "./Sidebar.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
{#if $theme === "light"}
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
--foreground-color-1: hsl(180, 11%, 7%);
|
||||||
|
--foreground-color-2: hsl(180, 11%, 12%);
|
||||||
|
--foreground-color-3: hsl(180, 11%, 17%);
|
||||||
|
--background-color-1: hsl(210, 100%, 100%);
|
||||||
|
--background-color-2: hsl(210, 3%, 90%);
|
||||||
|
--background-color-3: hsl(210, 1%, 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
color: var(--foreground-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-red, .button-accent {
|
||||||
|
color: var(--background-color-1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{/if}
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.flex-container {
|
.flex-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -71,6 +71,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background-color: var(--background-color-1);
|
background-color: var(--background-color-1);
|
||||||
border-right: 1px solid var(--background-color-2);
|
border-right: 1px solid var(--background-color-2);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -90,9 +92,12 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: var(--space-xs);
|
padding: var(--space-xs);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-button {
|
.sidebar-button {
|
||||||
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { fade, fly } from "svelte/transition";
|
|
||||||
import { quintInOut } from "svelte/easing";
|
import { quintInOut } from "svelte/easing";
|
||||||
import { AtSignIcon } from "svelte-feather-icons";
|
import { AtSignIcon } from "svelte-feather-icons";
|
||||||
import { overlayStore, userInfoStore, smallViewport } from "../../stores";
|
import { overlayStore, userInfoStore, smallViewport, theme } from "../../stores";
|
||||||
import { logOut } from "../../auth";
|
import { logOut } from "../../auth";
|
||||||
import { maybeFade, maybeFly } from "../../animations";
|
import { maybeFade, maybeFly } from "../../animations";
|
||||||
|
|
||||||
const close = () => overlayStore.close("settings");
|
const close = () => overlayStore.close("settings");
|
||||||
|
|
||||||
|
@ -22,13 +21,11 @@ import { maybeFade, maybeFly } from "../../animations";
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
.separator {
|
.separator {
|
||||||
margin-bottom: var(--space-md);
|
margin-bottom: var(--space-sm);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
.user-account-card {
|
.settings-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
|
@ -36,7 +33,21 @@ import { maybeFade, maybeFly } from "../../animations";
|
||||||
border-radius: var(--radius-norm);
|
border-radius: var(--radius-norm);
|
||||||
background-color: var(--background-color-1);
|
background-color: var(--background-color-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selection-option {
|
||||||
|
background-color: var(--background-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-option.selected, .selection-option:hover {
|
||||||
|
background-color: var(--background-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-selections {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-md);
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.large-settings {
|
.large-settings {
|
||||||
min-width: 540px;
|
min-width: 540px;
|
||||||
min-height: 420px;
|
min-height: 420px;
|
||||||
|
@ -50,11 +61,19 @@ import { maybeFade, maybeFly } from "../../animations";
|
||||||
<div class="modal-backdrop" transition:maybeFade="{{ duration: 300, easing: quintInOut }}" on:click="{ close }">
|
<div class="modal-backdrop" transition:maybeFade="{{ duration: 300, easing: quintInOut }}" on:click="{ close }">
|
||||||
<div class="modal" class:large-settings="{ !$smallViewport }" transition:maybeFly="{{ duration: 300, easing: quintInOut, y: 10 }}" on:click|stopPropagation>
|
<div class="modal" class:large-settings="{ !$smallViewport }" transition:maybeFly="{{ duration: 300, easing: quintInOut, y: 10 }}" on:click|stopPropagation>
|
||||||
<span class="input-label">Account</span>
|
<span class="input-label">Account</span>
|
||||||
<div class="user-account-card full-width">
|
<div class="settings-card full-width">
|
||||||
<AtSignIcon />
|
<AtSignIcon />
|
||||||
<span class="h5 top-bar-heading">{ $userInfoStore ? $userInfoStore.username : "" }</span>
|
<span class="h5 top-bar-heading">{ $userInfoStore ? $userInfoStore.username : "" }</span>
|
||||||
<button class="button button-red inner-logout-button" on:click="{ doLogout }">Log Out</button>
|
<button class="button button-red inner-logout-button" on:click="{ doLogout }">Log Out</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="separator" />
|
||||||
|
|
||||||
|
<span class="input-label">Theme</span>
|
||||||
|
<div class="horizontal-selections">
|
||||||
|
<button class="button selection-option full-width selected" class:selected="{ $theme === "dark" }" on:click="{ () => theme.set('dark') }">Dark</button>
|
||||||
|
<button class="button selection-option full-width" class:selected="{ $theme === "light" }" on:click="{ () => theme.set('light') }">Light</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="button modal-secondary-action" on:click="{ close }">Close</button>
|
<button class="button modal-secondary-action" on:click="{ close }">Close</button>
|
||||||
|
|
|
@ -4,6 +4,7 @@ const defaults = {
|
||||||
"auth:token": "",
|
"auth:token": "",
|
||||||
"app:behavior:doAnimations": true,
|
"app:behavior:doAnimations": true,
|
||||||
"app:cache:openChannelId": -1,
|
"app:cache:openChannelId": -1,
|
||||||
|
"app:visual:theme": "dark",
|
||||||
"loggingSink:Gateway": false,
|
"loggingSink:Gateway": false,
|
||||||
"loggingSink:Store": false
|
"loggingSink:Store": false
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,10 +12,20 @@ class Store {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// like subscribe, but without initially calling it
|
||||||
|
watch(handler) {
|
||||||
|
const newLength = this._handlers.push(handler);
|
||||||
|
const handlerIndex = newLength - 1;
|
||||||
|
storeLog(`(${this.name}) Calling handler (watch/initial)`, this.value);
|
||||||
|
return () => {
|
||||||
|
this._handlers.splice(handlerIndex, 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
subscribe(handler) {
|
subscribe(handler) {
|
||||||
const newLength = this._handlers.push(handler);
|
const newLength = this._handlers.push(handler);
|
||||||
const handlerIndex = newLength - 1;
|
const handlerIndex = newLength - 1;
|
||||||
storeLog(`(${this.name}) Calling handler (initial)`, this.value);
|
storeLog(`(${this.name}) Calling handler (subscribe/initial)`, this.value);
|
||||||
handler(this.value);
|
handler(this.value);
|
||||||
return () => {
|
return () => {
|
||||||
this._handlers.splice(handlerIndex, 1);
|
this._handlers.splice(handlerIndex, 1);
|
||||||
|
@ -299,16 +309,19 @@ export const selectedChannel = new Store({ id: getItem("app:cache:openChannelId"
|
||||||
export const showSidebar = new Store(false, "showSidebar");
|
export const showSidebar = new Store(false, "showSidebar");
|
||||||
export const showChannelView = new Store(true, "showChannelView");
|
export const showChannelView = new Store(true, "showChannelView");
|
||||||
export const smallViewport = new Store(false, "smallViewport");
|
export const smallViewport = new Store(false, "smallViewport");
|
||||||
|
export const theme = new Store(getItem("app:visual:theme"), "theme");
|
||||||
export const channels = new ChannelsStore();
|
export const channels = new ChannelsStore();
|
||||||
export const gatewayStatus = new GatewayStatusStore();
|
export const gatewayStatus = new GatewayStatusStore();
|
||||||
export const messagesStoreProvider = new MessagesStoreProvider();
|
export const messagesStoreProvider = new MessagesStoreProvider();
|
||||||
export const userInfoStore = new UserInfoStore();
|
export const userInfoStore = new UserInfoStore();
|
||||||
export const overlayStore = new OverlayStore();
|
export const overlayStore = new OverlayStore();
|
||||||
|
|
||||||
export const allStores = {
|
export const allStores = {
|
||||||
selectedChannel,
|
selectedChannel,
|
||||||
showSidebar,
|
showSidebar,
|
||||||
showChannelView,
|
showChannelView,
|
||||||
smallViewport,
|
smallViewport,
|
||||||
|
theme,
|
||||||
channels,
|
channels,
|
||||||
gatewayStatus,
|
gatewayStatus,
|
||||||
messagesStoreProvider,
|
messagesStoreProvider,
|
||||||
|
@ -316,6 +329,10 @@ export const allStores = {
|
||||||
overlayStore,
|
overlayStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
selectedChannel.subscribe((newSelectedChannel) => {
|
selectedChannel.watch((newSelectedChannel) => {
|
||||||
setItem("app:cache:openChannelId", newSelectedChannel.id);
|
setItem("app:cache:openChannelId", newSelectedChannel.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
theme.watch((newTheme) => {
|
||||||
|
setItem("app:visual:theme", newTheme);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue