Compare commits

..

No commits in common. "72c9650f7142945e367dd916aa55a7303e30cfa3" and "94e94deb912a88b220e6093adc2adc99257e0d0c" have entirely different histories.

11 changed files with 299 additions and 264 deletions

View file

@ -1,22 +1,8 @@
import { fade, fly, scale } from "svelte/transition";
import { cubicInOut, linear } from "svelte/easing";
import { cubicInOut } from "svelte/easing";
import { getItem } from "./storage";
import { smallViewport } from "./stores";
// Function specific for the Login and CreateAccount modals, where the transition duration is relied upon
export function maybeModalFadeIf(...e) {
if (e[1] && e[1]._condition)
return maybeModalFade(e[0]);
else
return {
delay: 0,
duration: e[1].duration,
easing: e[1].easing,
css: (_t) => ""
};
}
export function maybeModalFade(node) {
return maybeFade(node, { duration: 175, easing: cubicInOut });
}

View file

@ -1,7 +1,7 @@
<script>
import { overlayStore } from "../../stores";
import { methods, remoteSignal } from "../../request";
import Modal from "./Modal.svelte";
import { maybeModalFade, maybeModalScale } from "../../animations";
let communityName = "";
let createButtonEnabled = true;
@ -15,20 +15,36 @@
}
close();
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await create();
};
</script>
<Modal {close} enter={create}>
<span class="h4" slot="header">Create Community</span>
<style>
.full-width {
width: 100%;
}
</style>
<svelte:fragment slot="content">
<label class="input-label">
Community Name
<input class="input full-width" minlength="1" maxlength="32" bind:value={ communityName } />
</label>
</svelte:fragment>
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
<div class="modal-header">
<span class="h4">Create Community</span>
</div>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !createButtonEnabled }">Create</button>
</svelte:fragment>
</Modal>
<div class="modal-content">
<label class="input-label">
Community Name
<input class="input full-width" minlength="1" maxlength="32" bind:value={ communityName } />
</label>
</div>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !createButtonEnabled }">Create</button>
</div>
</div>
</div>

View file

@ -1,7 +1,7 @@
<script>
import { overlayStore, OverlayType } from "../../stores";
import { methods, remoteCall } from "../../request";
import Modal from "./Modal.svelte";
import { maybeModalScale } from "../../animations";
let username = "";
let password = "";
@ -30,33 +30,47 @@
overlayStore.push(OverlayType.Login);
}
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await create();
};
</script>
<style>
.full-width {
width: 100%;
}
.separator {
margin-bottom: var(--space-md);
}
</style>
<Modal opaque close={loginInstead} {outroEnd} enter={create}>
<span class="h4" slot="header">Create an Account</span>
<div class="modal-backdrop modal-backdrop-opaque" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation on:outroend="{ outroEnd }">
<div class="modal-header">
<span class="h4">Create an Account</span>
</div>
<svelte:fragment slot="content">
<label class="input-label">
Username
<input class="input full-width" minlength="1" maxlength="32" bind:value={ username } />
</label>
<div class="modal-content">
<label class="input-label">
Username
<input class="input full-width" minlength="1" maxlength="32" bind:value={ username } />
</label>
<div class="separator" />
<label class="input-label">
Password
<input class="input full-width" minlength="8" type="password" bind:value={ password } />
</label>
</div>
<div class="separator" />
<label class="input-label">
Password
<input class="input full-width" minlength="8" type="password" bind:value={ password } />
</label>
</svelte:fragment>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ loginInstead }">Log in instead</button>
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !buttonsEnabled }">Create</button>
</svelte:fragment>
</Modal>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ loginInstead }">Log in instead</button>
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !buttonsEnabled }">Create</button>
</div>
</div>
</div>

View file

@ -1,7 +1,7 @@
<script>
import { overlayStore } from "../../stores";
import { methods, remoteSignal } from "../../request";
import Modal from "./Modal.svelte";
import { methods, remoteCall, remoteSignal } from "../../request";
import { maybeModalFade, maybeModalScale } from "../../animations";
let channelName = "";
let createButtonEnabled = true;
@ -16,25 +16,39 @@
}
close();
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await create();
};
</script>
<Modal {close} enter={create}>
<svelte:fragment slot="header">
<span class="h4">Create Channel</span>
{#if community.id !== -1}
<span class="text-fg-3 text-small">in <span class="text-fg-2">{ community.name }</span></span>
{/if}
</svelte:fragment>
<style>
.full-width {
width: 100%;
}
</style>
<svelte:fragment slot="content">
<label class="input-label">
Channel Name
<input class="input full-width" minlength="1" maxlength="32" bind:value={ channelName } />
</label>
</svelte:fragment>
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
<div class="modal-header">
<span class="h4">Create Channel</span>
{#if community.id !== -1}
<span class="text-fg-3 text-small">in <span class="text-fg-2">{ community.name }</span></span>
{/if}
</div>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !createButtonEnabled }">Create</button>
</svelte:fragment>
</Modal>
<div class="modal-content">
<label class="input-label">
Channel Name
<input class="input full-width" minlength="1" maxlength="32" bind:value={ channelName } />
</label>
</div>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !createButtonEnabled }">Create</button>
</div>
</div>
</div>

View file

@ -1,7 +1,7 @@
<script>
import { maybeModalFade, maybeModalScale } from "../../animations";
import { overlayStore } from "../../stores";
import { methods, remoteSignal } from "../../request";
import Modal from "./Modal.svelte";
import { methods, remoteCall, remoteSignal } from "../../request";
export let channel;
@ -25,27 +25,41 @@
}
close();
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await save();
};
</script>
<style>
.full-width {
width: 100%;
}
.delete-button {
color: var(--red-2);
}
</style>
<Modal {close} enter={save}>
<span class="h4" slot="header">Edit Channel</span>
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
<div class="modal-header">
<span class="h4">Edit Channel</span>
</div>
<svelte:fragment slot="content">
<label class="input-label">
Channel Name
<input class="input full-width" minlength="1" maxlength="32" bind:value={ channelName } />
</label>
</svelte:fragment>
<div class="modal-content">
<label class="input-label">
Channel Name
<input class="input full-width" minlength="1" maxlength="32" bind:value={ channelName } />
</label>
</div>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button modal-secondary-action delete-button" on:click="{ deleteChannel }" disabled="{ !buttonsEnabled }">Delete</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
</svelte:fragment>
</Modal>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button modal-secondary-action delete-button" on:click="{ deleteChannel }" disabled="{ !buttonsEnabled }">Delete</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
</div>
</div>
</div>

View file

@ -1,7 +1,7 @@
<script>
import { overlayStore } from "../../stores";
import { methods, remoteSignal } from "../../request";
import Modal from "./Modal.svelte";
import { methods, remoteCall, remoteSignal } from "../../request";
import { maybeModalFade, maybeModalScale } from "../../animations";
export let message;
@ -25,27 +25,41 @@
}
close();
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await save();
};
</script>
<style>
.full-width {
width: 100%;
}
.delete-button {
color: var(--red-2);
}
</style>
<Modal {close} enter={save}>
<span class="h4" slot="header">Edit Message</span>
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
<div class="modal-header">
<span class="h4">Edit Message</span>
</div>
<svelte:fragment slot="content">
<label class="input-label">
Content
<input class="input full-width" minlength="1" bind:value={ messageContent } />
</label>
</svelte:fragment>
<div class="modal-content">
<label class="input-label">
Content
<input class="input full-width" minlength="1" bind:value={ messageContent } />
</label>
</div>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button modal-secondary-action delete-button" on:click="{ deleteMessage }" disabled="{ !buttonsEnabled }">Delete</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
</svelte:fragment>
</Modal>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button modal-secondary-action delete-button" on:click="{ deleteMessage }" disabled="{ !buttonsEnabled }">Delete</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
</div>
</div>
</div>

View file

@ -2,8 +2,8 @@
import { overlayStore, OverlayType } from "../../stores";
import { remoteCall } from "../../request";
import { authWithToken } from "../../auth";
import { maybeModalScale } from "../../animations";
import { methods } from "../../request";
import Modal from "./Modal.svelte";
let username = "";
let password = "";
@ -35,33 +35,47 @@
overlayStore.push(OverlayType.CreateAccount);
}
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await login();
};
</script>
<style>
.full-width {
width: 100%;
}
.separator {
margin-bottom: var(--space-md);
}
</style>
<Modal opaque close={createAccountInstead} {outroEnd} enter={login}>
<span class="h4" slot="header">Welcome back!</span>
<div class="modal-backdrop modal-backdrop-opaque" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation on:outroend="{ outroEnd }">
<div class="modal-header">
<span class="h4">Welcome back!</span>
</div>
<svelte:fragment slot="content">
<label class="input-label">
Username
<input class="input full-width" minlength="1" maxlength="32" bind:value={ username } />
</label>
<div class="modal-content">
<label class="input-label">
Username
<input class="input full-width" minlength="1" maxlength="32" bind:value={ username } />
</label>
<div class="separator" />
<div class="separator" />
<label class="input-label">
Password
<input class="input full-width" minlength="8" type="password" bind:value={ password } />
</label>
</svelte:fragment>
<label class="input-label">
Password
<input class="input full-width" minlength="8" type="password" bind:value={ password } />
</label>
</div>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ createAccountInstead }">Create an account instead</button>
<button class="button button-accent modal-primary-action" on:click="{ login }" disabled="{ !buttonsEnabled }">Log In</button>
</svelte:fragment>
</Modal>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ createAccountInstead }">Create an account instead</button>
<button class="button button-accent modal-primary-action" on:click="{ login }" disabled="{ !buttonsEnabled }">Log In</button>
</div>
</div>
</div>

View file

@ -1,57 +0,0 @@
<script>
import { onMount } from "svelte";
import { maybeModalFade, maybeModalFadeIf, maybeModalScale } from "../../animations";
export let close = () => {};
export let enter = () => {};
export let outroEnd = () => {};
export let className = "";
export let opaque = false;
let modal;
let blur = false;
const onKeydown = ({ code }) => {
if (code === "Enter") {
enter();
} else if (code === "Escape") {
close();
}
};
const backdropIntroEnd = () => {
blur = true;
};
const backdropOutroStart = () => {
blur = false;
};
onMount(() => {
modal.focus();
});
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<div class="modal-backdrop" class:modal-backdrop-opaque={opaque} class:blur={blur} transition:maybeModalFadeIf="{{ _condition: !opaque }}" on:click="{ close }" on:keydown="{ onKeydown }" on:introend={backdropIntroEnd} on:outrostart={backdropOutroStart}>
<div bind:this={modal} role="alertdialog" tabindex="-1" aria-modal="true" class={className + " modal"} transition:maybeModalScale on:click|stopPropagation on:outroend={outroEnd}>
{#if $$slots.header}
<div class="modal-header">
<slot name="header" />
</div>
{/if}
{#if $$slots.content}
<div class="modal-content">
<slot name="content" />
</div>
{/if}
{#if $$slots.footer}
<div class="modal-footer">
<slot name="footer" />
</div>
{/if}
</div>
</div>

View file

@ -1,5 +1,5 @@
<script>
import Modal from "./Modal.svelte";
import { maybeModalFade, maybeModalScale } from "../../animations";
export let onSubmit = async () => {};
export let onClose = async () => {};
@ -19,18 +19,36 @@
await onSubmit(userInput);
closePrompt();
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
return;
await save();
};
</script>
<Modal {closePrompt} enter={save}>
<span class="h4" slot="header">{ heading }</span>
<style>
.full-width {
width: 100%;
}
</style>
<label class="input-label" slot="content">
{ valueName }
<input class="input full-width" bind:value={ userInput } />
</label>
<div class="modal-backdrop" transition:maybeModalFade on:click="{ closePrompt }" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
<div class="modal-header">
<span class="h4">{ heading }</span>
</div>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ closePrompt }">Cancel</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Submit</button>
</svelte:fragment>
</Modal>
<div class="modal-content">
<label class="input-label">
{ valueName }
<input class="input full-width" bind:value={ userInput } />
</label>
</div>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ closePrompt }">Cancel</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Submit</button>
</div>
</div>
</div>

View file

@ -2,12 +2,12 @@
import { overlayStore, userInfoStore, smallViewport, theme, doAnimations, OverlayType, sendTypingUpdatesItemStore } from "../../stores";
import { logOut } from "../../auth";
import { maybeModalFade, maybeModalScale } from "../../animations";
import request, { methods, remoteBlobUpload } from "../../request";
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";
import Modal from "./Modal.svelte";
export let close = () => {};
let avatarFileInput;
@ -76,7 +76,12 @@
};
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<style>
.full-width {
width: 100%;
}
.separator {
margin-bottom: var(--space-sm);
}
@ -123,7 +128,7 @@
grid-template-columns: repeat(2, 1fr);
}
:global(.large-settings) {
.large-settings {
width: 600px;
min-height: 425px;
padding-bottom: var(--space-xs);
@ -133,72 +138,77 @@
margin-left: auto;
}
:global(.settings-modal) {
background-color: var(--background-color-1) !important;
.settings-modal {
background-color: var(--background-color-1);
}
:global(.settings-modal .modal-header) {
.settings-modal .modal-header {
padding-bottom: var(--space-xxs);
}
</style>
<Modal {close} className={`settings-modal ${$smallViewport ? "" : "large-settings"}`}>
<span class="h4" slot="header">Settings</span>
<!-- 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">
<span class="h4">Settings</span>
</div>
<svelte:fragment slot="content">
<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="left-auto">
<button class="button" on:click="{ openAvatarInput }">Update Avatar</button>
<button class="button button-danger" on:click="{ doLogout }">Log Out</button>
</div>
</div>
{:else if selectedTab == "PRIVACY"}
<div class="switch-option-card full-width">
<div class="info">
<span class="info-heading">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 class="info-heading">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="modal-content">
<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" />
<div class="switch-option-card full-width">
<div class="info">
<span class="info-heading">Reduce animations</span>
<span class="text-fg-3 text-small">Reduce the amount of animations and visual effects.</span>
{#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="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="option-switch">
<StoredSwitch store={ doAnimations } inverted={ true } />
{:else if selectedTab == "PRIVACY"}
<div class="switch-option-card full-width">
<div class="info">
<span class="info-heading">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>
{:else}
<span>Page not found: { selectedTab }</span>
{/if}
</svelte:fragment>
</Modal>
<div class="switch-option-card full-width">
<div class="info">
<span class="info-heading">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" />
<div class="switch-option-card full-width">
<div class="info">
<span class="info-heading">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>

View file

@ -54,13 +54,13 @@
/* top-level */
:root {
--purple-1: hsl(266, 63%, 64%);
--purple-1: hsl(273, 67%, 53%);
--blue-1: hsl(200, 78%, 50%);
--green-1: hsl(140, 78%, 50%);
--yellow-1: hsl(50, 78%, 50%);
--red-1: hsl(2, 78%, 65%);
--purple-2: hsl(266, 62%, 58%);
--purple-2: hsl(273, 64%, 48%);
--blue-2: hsl(200, 78%, 45%);
--green-2: hsl(140, 78%, 40%);
--yellow-2: hsl(50, 78%, 60%);
@ -222,11 +222,8 @@ body {
bottom: 0;
z-index: 15;
background-color: rgba(0, 0, 0, 0.4);
contain: strict;
}
.modal-backdrop.blur {
backdrop-filter: blur(1.5px);
contain: strict;
}
.modal-backdrop-opaque {
@ -287,6 +284,7 @@ body {
.modal-backdrop {
align-items: flex-end;
justify-content: center;
backdrop-filter: unset;
}
.modal-backdrop-opaque {
@ -329,10 +327,8 @@ body {
background: none;
text-align: center;
border: none;
padding: 0.85em;
padding-top: 0.65em;
padding-bottom: 0.65em;
border-radius: 9999px;
padding: 0.7em;
border-radius: 1em;
font: inherit;
user-select: none;
font-weight: 550;
@ -592,10 +588,6 @@ body {
border-bottom-left-radius: var(--radius-mdplus);
}
.full-width {
width: 100%;
}
/*! the tweaks below are heavily based on modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */