improve settings page
This commit is contained in:
parent
232575974a
commit
59fde80edb
6 changed files with 466 additions and 299 deletions
59
frontend/src/components/ChipBar.svelte
Normal file
59
frontend/src/components/ChipBar.svelte
Normal 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>
|
23
frontend/src/components/StoredSwitch.svelte
Normal file
23
frontend/src/components/StoredSwitch.svelte
Normal 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>
|
46
frontend/src/components/Switch.svelte
Normal file
46
frontend/src/components/Switch.svelte
Normal 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>
|
|
@ -1,13 +1,17 @@
|
|||
<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 { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import request, { methods, remoteBlobUpload, remoteCall } from "../../request";
|
||||
import { apiRoute, getItem } from "../../storage";
|
||||
import UserView from "../UserView.svelte";
|
||||
import ChipBar from "../ChipBar.svelte";
|
||||
import Switch from "../Switch.svelte";
|
||||
import StoredSwitch from "../StoredSwitch.svelte";
|
||||
|
||||
export let close = () => {};
|
||||
let avatarFileInput;
|
||||
let selectedTab;
|
||||
|
||||
const doSuperuserPrompt = async () => {
|
||||
const { ok } = await request("POST", apiRoute("users/self/promote"), true);
|
||||
|
@ -72,6 +76,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
|
@ -90,6 +95,22 @@
|
|||
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 {
|
||||
border: 1px solid var(--background-color-2);
|
||||
}
|
||||
|
@ -105,19 +126,25 @@
|
|||
}
|
||||
|
||||
.large-settings {
|
||||
min-width: 540px;
|
||||
width: 600px;
|
||||
min-height: 425px;
|
||||
padding-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.account-buttons {
|
||||
.left-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.settings-modal {
|
||||
background-color: var(--background-color-1);
|
||||
}
|
||||
|
||||
.settings-modal .modal-header {
|
||||
padding-bottom: var(--space-xxs);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<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-header">
|
||||
|
@ -125,31 +152,58 @@
|
|||
</div>
|
||||
|
||||
<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">
|
||||
<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}>
|
||||
<div class="account-buttons">
|
||||
<div class="left-auto">
|
||||
<button class="button" on:click="{ openAvatarInput }">Update Avatar</button>
|
||||
<button class="button button-danger" on:click="{ doLogout }">Log Out</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator" />
|
||||
|
||||
{:else if selectedTab == "PRIVACY"}
|
||||
<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>
|
||||
<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="separator" />
|
||||
|
||||
<span class="input-label">Animations</span>
|
||||
<div class="horizontal-selections">
|
||||
<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 class="switch-option-card full-width">
|
||||
<div class="info">
|
||||
<span>Reduce animations</span>
|
||||
<span class="text-fg-3 text-small">Reduce the amount of animations and visual effects.</span>
|
||||
</div>
|
||||
<div class="option-switch">
|
||||
<StoredSwitch store={ doAnimations } inverted={ true } />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<span>Page not found: { selectedTab }</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -526,7 +526,7 @@ class TypingStore extends Store {
|
|||
}
|
||||
|
||||
async didInputKey() {
|
||||
if (!userInfoStore.value || !getItem("ui:online:sendTypingUpdates"))
|
||||
if (!userInfoStore.value || !sendTypingUpdatesItemStore.value)
|
||||
return;
|
||||
|
||||
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 showChannelView = new Store(true, "showChannelView");
|
||||
export const theme = new StorageItemStore("ui:theme");
|
||||
export const sendTypingUpdatesItemStore = new StorageItemStore("ui:online:sendTypingUpdates");
|
||||
export const doAnimations = new StorageItemStore("ui:doAnimations");
|
||||
export const communities = new CommunitiesStore();
|
||||
export const gatewayStatus = new GatewayStatusStore();
|
||||
|
|
Loading…
Reference in a new issue