frontend: add theme switching

This commit is contained in:
hippoz 2022-05-07 03:27:41 +03:00
parent c8604515d6
commit 704b35ae2b
Signed by: hippoz
GPG key ID: 7C52899193467641
6 changed files with 77 additions and 21 deletions

View file

@ -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%;

View file

@ -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%;

View file

@ -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;

View file

@ -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>

View file

@ -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
}; };

View file

@ -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);
});