improve settings page

This commit is contained in:
hippoz 2023-07-21 18:49:54 +03:00
parent 232575974a
commit 59fde80edb
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
6 changed files with 466 additions and 299 deletions

View file

@ -0,0 +1,59 @@
<script>
export let options = [];
export let selectedOptionId = null;
export let onSelect = (_) => {};
if (selectedOptionId) {
onSelect(selectedOptionId);
}
</script>
<style>
div {
display: flex;
flex-direction: row;
margin-bottom: var(--space-xs);
flex-wrap: wrap;
}
button {
display: flex;
justify-content: center;
align-items: center;
color: var(--foreground-color-2);
background-color: var(--background-color-2);
padding: 0.35em;
margin-top: var(--space-xs);
margin-right: var(--space-sm);
border-radius: 0.5em;
}
button:hover {
background-color: var(--background-color-3);
}
button.selected {
color: var(--background-color-2);
background-color: var(--foreground-color-2);
}
button.selected .material-icons-outlined {
color: var(--background-color-2);
}
.material-icons-outlined {
margin-right: var(--space-xxs);
}
</style>
<div>
{#each options as option (option.id)}
<button class="button" class:selected={ selectedOptionId === option.id } on:click={ () => { selectedOptionId = option.id; onSelect(selectedOptionId); } }>
{#if option.icon}
<span class="material-icons-outlined">{ option.icon }</span>
{/if}
{ option.text }
</button>
{/each}
</div>

View file

@ -0,0 +1,23 @@
<script>
import Switch from "./Switch.svelte";
export let store = null;
export let inverted = false;
const getValue = () => {
if (!store) return false;
if (inverted) {
return !store.value;
}
return !!store.value;
};
const setValue = (value) => {
if (!store) return;
store.set(inverted ? !value : !!value);
}
</script>
<Switch checked={ getValue() } onUpdated={setValue}></Switch>

View file

@ -0,0 +1,46 @@
<script>
export let onUpdated = (_) => {};
export let checked = false;
</script>
<style>
.switch {
width: 52px;
height: 26px;
border-radius: 999px;
margin: 4px;
background-color: var(--background-color-3);
transition: 0.15s;
contain: strict;
}
.switch-slider {
position: absolute;
margin: 3px;
width: 20px;
height: 20px;
border-radius: 100%;
background-color: var(--foreground-color-3);
transition: 0.15s;
contain: strict;
}
.switch-checkbox {
opacity: 0;
}
.switch.on {
background-color: var(--green-2);
}
.switch.on .switch-slider {
background-color: var(--foreground-color-2);
transform: translateX(26px);
}
</style>
<div class="switch" class:on={ checked } on:click={() => {checked = !checked; onUpdated(checked); }}>
<div class="switch-slider"></div>
<input class="switch-checkbox" type="checkbox" tabIndex="0" />
</div>

View file

@ -1,13 +1,17 @@
<script> <script>
import { overlayStore, userInfoStore, smallViewport, theme, doAnimations, OverlayType } from "../../stores"; import { overlayStore, userInfoStore, smallViewport, theme, doAnimations, OverlayType, sendTypingUpdatesItemStore } from "../../stores";
import { logOut } from "../../auth"; import { logOut } from "../../auth";
import { maybeModalFade, maybeModalScale } from "../../animations"; import { maybeModalFade, maybeModalScale } from "../../animations";
import request, { methods, remoteBlobUpload, remoteCall } from "../../request"; import request, { methods, remoteBlobUpload, remoteCall } from "../../request";
import { apiRoute, getItem } from "../../storage"; import { apiRoute, getItem } from "../../storage";
import UserView from "../UserView.svelte"; import UserView from "../UserView.svelte";
import ChipBar from "../ChipBar.svelte";
import Switch from "../Switch.svelte";
import StoredSwitch from "../StoredSwitch.svelte";
export let close = () => {}; export let close = () => {};
let avatarFileInput; let avatarFileInput;
let selectedTab;
const doSuperuserPrompt = async () => { const doSuperuserPrompt = async () => {
const { ok } = await request("POST", apiRoute("users/self/promote"), true); const { ok } = await request("POST", apiRoute("users/self/promote"), true);
@ -72,6 +76,7 @@
}; };
</script> </script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<style> <style>
.full-width { .full-width {
width: 100%; width: 100%;
@ -90,6 +95,22 @@
border: 1px solid var(--background-color-2); border: 1px solid var(--background-color-2);
} }
.switch-option-card {
display: flex;
justify-content: left;
flex-direction: row;
padding: var(--space-md);
}
.switch-option-card .info {
display: flex;
flex-direction: column;
}
.switch-option-card .option-switch {
padding-left: var(--space-xs);
margin-left: auto;
justify-self: flex-end;
}
.selection-option { .selection-option {
border: 1px solid var(--background-color-2); border: 1px solid var(--background-color-2);
} }
@ -105,19 +126,25 @@
} }
.large-settings { .large-settings {
min-width: 540px; width: 600px;
min-height: 425px;
padding-bottom: var(--space-xs); padding-bottom: var(--space-xs);
} }
.account-buttons { .left-auto {
margin-left: auto; margin-left: auto;
} }
.settings-modal { .settings-modal {
background-color: var(--background-color-1); background-color: var(--background-color-1);
} }
.settings-modal .modal-header {
padding-bottom: var(--space-xxs);
}
</style> </style>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }"> <div class="modal-backdrop" transition:maybeModalFade on:click="{ close }">
<div class="modal settings-modal" class:large-settings="{ !$smallViewport }" transition:maybeModalScale on:click|stopPropagation> <div class="modal settings-modal" class:large-settings="{ !$smallViewport }" transition:maybeModalScale on:click|stopPropagation>
<div class="modal-header"> <div class="modal-header">
@ -125,31 +152,58 @@
</div> </div>
<div class="modal-content"> <div class="modal-content">
<span class="input-label" on:click={ doDeveloper }>Account</span> <ChipBar selectedOptionId="ACCOUNT" onSelect={ (tab) => selectedTab = tab } options={[
{ id: "ACCOUNT", text: "Account", icon: "person" },
{ id: "PRIVACY", text: "Privacy", icon: "lock" },
{ id: "APPEARANCE", text: "Appearance", icon: "palette" },
]}></ChipBar>
<div class="separator" />
{#if selectedTab === "ACCOUNT"}
<div class="settings-card full-width"> <div class="settings-card full-width">
<UserView user={$userInfoStore}></UserView> <UserView user={$userInfoStore}></UserView>
<input type="file" style="display: none;" accept="image/png, image/jpeg, image/webp" name="avatar-upload" multiple={false} bind:this={avatarFileInput} on:change={onAvatarFileChange}> <input type="file" style="display: none;" accept="image/png, image/jpeg, image/webp" name="avatar-upload" multiple={false} bind:this={avatarFileInput} on:change={onAvatarFileChange}>
<div class="account-buttons"> <div class="left-auto">
<button class="button" on:click="{ openAvatarInput }">Update Avatar</button> <button class="button" on:click="{ openAvatarInput }">Update Avatar</button>
<button class="button button-danger" on:click="{ doLogout }">Log Out</button> <button class="button button-danger" on:click="{ doLogout }">Log Out</button>
</div> </div>
</div> </div>
{:else if selectedTab == "PRIVACY"}
<div class="separator" /> <div class="switch-option-card full-width">
<div class="info">
<span>Let others know when I'm typing</span>
<span class="text-fg-3 text-small">If this is enabled, other users will see an indicator while you're typing a message.</span>
</div>
<div class="option-switch">
<StoredSwitch store={ sendTypingUpdatesItemStore } />
</div>
</div>
<div class="switch-option-card full-width">
<div class="info">
<span>Make Waffle work</span>
<span class="text-fg-3 text-small">Waffle needs to store data such as your messages, created channels, your username, your profile picture and more in order to work. If you'd like to stop this, you can delete your account.</span>
</div>
</div>
{:else if selectedTab === "APPEARANCE"}
<span class="input-label">Theme</span> <span class="input-label">Theme</span>
<div class="horizontal-selections"> <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 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> <button class="button selection-option full-width" class:selected="{ $theme === "light" }" on:click="{ () => theme.set('light') }">Light</button>
</div> </div>
<div class="separator" /> <div class="switch-option-card full-width">
<div class="info">
<span class="input-label">Animations</span> <span>Reduce animations</span>
<div class="horizontal-selections"> <span class="text-fg-3 text-small">Reduce the amount of animations and visual effects.</span>
<button class="button selection-option full-width" class:selected="{ $doAnimations === true }" on:click="{ () => doAnimations.set(true) }">Enabled</button>
<button class="button selection-option full-width selected" class:selected="{ $doAnimations === false }" on:click="{ () => doAnimations.set(false) }">Disabled</button>
</div> </div>
<div class="option-switch">
<StoredSwitch store={ doAnimations } inverted={ true } />
</div>
</div>
{:else}
<span>Page not found: { selectedTab }</span>
{/if}
</div> </div>
</div> </div>
</div> </div>

View file

@ -526,7 +526,7 @@ class TypingStore extends Store {
} }
async didInputKey() { async didInputKey() {
if (!userInfoStore.value || !getItem("ui:online:sendTypingUpdates")) if (!userInfoStore.value || !sendTypingUpdatesItemStore.value)
return; return;
this.startedTyping(userInfoStore.value, selectedChannel.value.id, 6500); this.startedTyping(userInfoStore.value, selectedChannel.value.id, 6500);
@ -839,6 +839,7 @@ export const showPresenceSidebar = new Store(false, "showPresenceSidebar");
export const smallViewport = new Store(false, "smallViewport"); export const smallViewport = new Store(false, "smallViewport");
export const showChannelView = new Store(true, "showChannelView"); export const showChannelView = new Store(true, "showChannelView");
export const theme = new StorageItemStore("ui:theme"); export const theme = new StorageItemStore("ui:theme");
export const sendTypingUpdatesItemStore = new StorageItemStore("ui:online:sendTypingUpdates");
export const doAnimations = new StorageItemStore("ui:doAnimations"); export const doAnimations = new StorageItemStore("ui:doAnimations");
export const communities = new CommunitiesStore(); export const communities = new CommunitiesStore();
export const gatewayStatus = new GatewayStatusStore(); export const gatewayStatus = new GatewayStatusStore();

526
yarn.lock

File diff suppressed because it is too large Load diff