greatly overhaul overlay system

This commit is contained in:
hippoz 2022-09-03 17:30:39 +03:00
parent d8552c0328
commit 4c9e321167
Signed by: hippoz
GPG key ID: 7C52899193467641
16 changed files with 117 additions and 117 deletions

View file

@ -1,15 +1,15 @@
import gateway, { GatewayEventType } from "./gateway";
import { removeItem, setItem } from "./storage";
import { overlayStore } from "./stores";
import { overlayStore, OverlayType } from "./stores";
export function useAuthHandlers() {
gateway.subscribe(GatewayEventType.Ready, () => {
overlayStore.close("login");
overlayStore.close("createAccount");
overlayStore.popType(OverlayType.Login);
overlayStore.popType(OverlayType.CreateAccount);
});
gateway.subscribe(GatewayEventType.BadAuth, () => {
overlayStore.open("login", {});
overlayStore.push(OverlayType.Login, {});
});
}

View file

@ -1,7 +1,7 @@
<script>
import { HashIcon, MenuIcon, UsersIcon } from "svelte-feather-icons";
import { getItem } from "../storage";
import { overlayStore, showPresenceSidebar, showSidebar } from "../stores";
import { overlayStore, OverlayType, showPresenceSidebar, showSidebar } from "../stores";
export let channel;
</script>
@ -24,7 +24,7 @@
</button>
{/if}
<HashIcon />
<span class="h5 top-bar-heading" on:click="{ () => overlayStore.open('editChannel', {channel}) }">{ channel.name }</span>
<span class="h5 top-bar-heading" on:click="{ () => overlayStore.push(OverlayType.EditChannel, {channel}) }">{ channel.name }</span>
<div class="right-buttons">
<button class="icon-button" on:click="{ () => showPresenceSidebar.set(!showPresenceSidebar.value) }" aria-label="Toggle user list">
<UsersIcon />

View file

@ -1,6 +1,6 @@
<script>
import { CornerUpLeftIcon, MoreVerticalIcon } from "svelte-feather-icons";
import { overlayStore, setMessageInputEvent, userInfoStore } from "../stores";
import { overlayStore, OverlayType, setMessageInputEvent, userInfoStore } from "../stores";
export let message;
@ -71,7 +71,7 @@
<CornerUpLeftIcon />
</button>
{#if userInfoStore.value && (message.author_id === userInfoStore.value.id || userInfoStore.value.is_superuser)}
<button class="icon-button edit-message" on:click="{ () => overlayStore.open('editMessage', { message }) }" aria-label="Edit Message">
<button class="icon-button edit-message" on:click="{ () => overlayStore.push(OverlayType.EditMessage, { message }) }" aria-label="Edit Message">
<MoreVerticalIcon />
</button>
{/if}

View file

@ -71,9 +71,7 @@
messages.deleteMessage({
id: optimisticMessageId
});
overlayStore.open("toast", {
message: "Couldn't send message"
});
overlayStore.toast("Couldn't send message");
}
};

View file

@ -2,7 +2,7 @@
import { HashIcon, PlusIcon, MoreVerticalIcon, SettingsIcon, CloudIcon } from "svelte-feather-icons";
import { quadInOut } from "svelte/easing";
import { maybeFly, maybeFlyIf } from "../animations";
import { channels, gatewayStatus, overlayStore, selectedChannel, showSidebar, smallViewport, userInfoStore, unreadStore } from "../stores";
import { channels, gatewayStatus, overlayStore, selectedChannel, showSidebar, smallViewport, userInfoStore, unreadStore, OverlayType } from "../stores";
import UserTopBar from "./UserTopBar.svelte";
const selectChannel = (channel) => {
@ -28,7 +28,7 @@
<div class="unread-indicator">{ $unreadStore.get(channel.id) }</div>
{/if}
{#if $userInfoStore && (channel.owner_id === $userInfoStore.id || $userInfoStore.is_superuser)}
<button class="icon-button" on:click|stopPropagation="{ () => overlayStore.open('editChannel', { channel }) }" aria-label="Edit Channel">
<button class="icon-button" on:click|stopPropagation="{ () => overlayStore.push(OverlayType.EditChannel, { channel }) }" aria-label="Edit Channel">
<MoreVerticalIcon />
</button>
{/if}
@ -36,14 +36,14 @@
</button>
{/each}
{#if $userInfoStore && $userInfoStore.permissions.create_channel}
<button on:click="{ () => overlayStore.open('createChannel') }" class="sidebar-button">
<button on:click="{ () => overlayStore.push(OverlayType.CreateChannel) }" class="sidebar-button">
<div class="sidebar-button-icon">
<PlusIcon />
</div>
<span>Create Channel</span>
</button>
{/if}
<button on:click="{ () => overlayStore.open('settings') }" class="sidebar-button">
<button on:click="{ () => overlayStore.push(OverlayType.Settings) }" class="sidebar-button">
<div class="sidebar-button-icon">
<SettingsIcon />
</div>

View file

@ -1,5 +1,5 @@
<script>
import { overlayStore } from "../../stores";
import { overlayStore, OverlayType } from "../../stores";
import request from "../../request";
import { apiRoute } from "../../storage";
import { maybeModalFly } from "../../animations";
@ -8,8 +8,8 @@
let password = "";
let buttonsEnabled = true;
let pendingOtherOpen = false;
export let close = () => {};
const close = () => overlayStore.close('createAccount');
const create = async () => {
buttonsEnabled = false;
const { ok } = await request("POST", apiRoute("users/register"), false, {
@ -17,14 +17,10 @@
password
});
if (ok) {
overlayStore.open("toast", {
message: "Account created"
});
overlayStore.toast("Account created");
loginInstead();
} else {
overlayStore.open("toast", {
message: "Couldn't create account"
});
overlayStore.toast("Couldn't create account");
buttonsEnabled = true;
return;
}
@ -35,7 +31,7 @@
}
const outroEnd = () => {
if (pendingOtherOpen) {
overlayStore.open("login", {});
overlayStore.push(OverlayType.Login);
}
};
const onKeydown = async (e) => {

View file

@ -6,17 +6,15 @@
let channelName = "";
let createButtonEnabled = true;
export let close = () => {};
const close = () => overlayStore.close('createChannel');
const create = async () => {
createButtonEnabled = false;
const { ok } = await request("POST", apiRoute("channels"), true, {
name: channelName
});
if (!ok) {
overlayStore.open("toast", {
message: "Couldn't create channel"
});
overlayStore.toast("Couldn't create channel");
}
close();
};

View file

@ -8,17 +8,15 @@
let channelName = channel.name;
let buttonsEnabled = true;
export let close = () => {};
const close = () => overlayStore.close('editChannel');
const save = async () => {
buttonsEnabled = false;
const { ok } = await request("PUT", apiRoute(`channels/${channel.id}`), true, {
name: channelName
});
if (!ok) {
overlayStore.open("toast", {
message: "Couldn't edit channel"
});
overlayStore.toast("Couldn't edit channel");
}
close();
};
@ -26,9 +24,7 @@
buttonsEnabled = false;
const { ok } = await request("DELETE", apiRoute(`channels/${channel.id}`), true);
if (!ok) {
overlayStore.open("toast", {
message: "Couldn't delete channel"
});
overlayStore.toast("Couldn't delete channel");
}
close();
};

View file

@ -1,5 +1,4 @@
<script>
import { quintInOut } from "svelte/easing";
import { overlayStore } from "../../stores";
import request from "../../request";
import { apiRoute } from "../../storage";
@ -9,17 +8,15 @@
let messageContent = message.content;
let buttonsEnabled = true;
export let close = () => {};
const close = () => overlayStore.close('editMessage');
const save = async () => {
buttonsEnabled = false;
const { ok } = await request("PUT", apiRoute(`messages/${message.id}`), true, {
content: messageContent
});
if (!ok) {
overlayStore.open("toast", {
message: "Couldn't edit message"
});
overlayStore.toast("Couldn't edit message");
}
close();
};
@ -27,9 +24,7 @@
buttonsEnabled = false;
const { ok } = await request("DELETE", apiRoute(`messages/${message.id}`), true);
if (!ok) {
overlayStore.open("toast", {
message: "Couldn't delete message"
});
overlayStore.toast("Couldn't delete message");
}
close();
};

View file

@ -1,5 +1,5 @@
<script>
import { overlayStore } from "../../stores";
import { overlayStore, OverlayType } from "../../stores";
import request from "../../request";
import { apiRoute } from "../../storage";
import { authWithToken } from "../../auth";
@ -9,8 +9,8 @@
let password = "";
let buttonsEnabled = true;
let pendingOtherOpen = false;
export let close = () => {};
const close = () => overlayStore.close('login');
const login = async () => {
buttonsEnabled = false;
const { ok, json } = await request("POST", apiRoute("users/login"), false, {
@ -21,13 +21,9 @@
authWithToken(json.token, true);
} else {
if (json && json.code && json.code === 6002) { // 6002 is the code for bad login
overlayStore.open("toast", {
message: "Invalid username or password"
});
overlayStore.toast("Invalid username or password");
} else {
overlayStore.open("toast", {
message: "Couldn't log in"
});
overlayStore.toast("Couldn't log in");
}
buttonsEnabled = true;
return;
@ -39,7 +35,7 @@
};
const outroEnd = () => {
if (pendingOtherOpen) {
overlayStore.open("createAccount", {});
overlayStore.push(OverlayType.CreateAccount);
}
};
const onKeydown = async (e) => {

View file

@ -1,5 +1,6 @@
<script>
import { overlayStore } from "../../stores";
import EditChannel from "./EditChannel.svelte";
import CreateChannel from "./CreateChannel.svelte";
import Toast from "./Toast.svelte";
@ -8,29 +9,19 @@
import EditMessage from "./EditMessage.svelte";
import Settings from "./Settings.svelte";
import Prompt from "./Prompt.svelte";
const OverlayComponent = {
0: CreateChannel,
1: EditChannel,
2: Toast,
3: Login,
4: CreateAccount,
5: EditMessage,
6: Settings,
7: Prompt,
};
</script>
{#if $overlayStore.createChannel}
<CreateChannel />
{/if}
{#if $overlayStore.editChannel}
<EditChannel { ...$overlayStore.editChannel } />
{/if}
{#if $overlayStore.toast}
<Toast { ...$overlayStore.toast } />
{/if}
{#if $overlayStore.login}
<Login />
{/if}
{#if $overlayStore.createAccount}
<CreateAccount />
{/if}
{#if $overlayStore.editMessage}
<EditMessage { ...$overlayStore.editMessage } />
{/if}
{#if $overlayStore.settings}
<Settings />
{/if}
{#if $overlayStore.prompt}
<Prompt { ...$overlayStore.prompt } />
{/if}
{#each $overlayStore as overlay (overlay.id)}
<svelte:component this={ OverlayComponent[overlay.type] } {...overlay.props} />
{/each}

View file

@ -1,6 +1,5 @@
<script>
import { maybeModalFade, maybeModalFly } from "../../animations";
import { overlayStore } from "../../stores";
export let onSubmit = async () => {};
export let onClose = async () => {};
@ -9,15 +8,16 @@
let userInput = "";
let buttonsEnabled = true;
export let close = () => {};
const close = async () => {
const closePrompt = async () => {
await onClose();
overlayStore.close("prompt");
close();
};
const save = async () => {
buttonsEnabled = false;
await onSubmit(userInput);
close();
closePrompt();
};
const onKeydown = async (e) => {
if (e.code !== "Enter")
@ -33,7 +33,7 @@
}
</style>
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
<div class="modal-backdrop" transition:maybeModalFade on:click="{ closePrompt }" on:keydown="{ onKeydown }">
<div class="modal" transition:maybeModalFly on:click|stopPropagation>
<div class="modal-header">
<span class="h4">{ heading }</span>
@ -45,7 +45,7 @@
</label>
<div class="modal-footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<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>

View file

@ -1,15 +1,15 @@
<script>
import { AtSignIcon } from "svelte-feather-icons";
import { overlayStore, userInfoStore, smallViewport, theme, doAnimations } from "../../stores";
import { overlayStore, userInfoStore, smallViewport, theme, doAnimations, OverlayType } from "../../stores";
import { logOut } from "../../auth";
import { maybeModalFade, maybeModalFly } from "../../animations";
import request from "../../request";
import { apiRoute, getItem } from "../../storage";
const close = () => overlayStore.close("settings");
export let close = () => {};
const doDeveloper = () => {
overlayStore.open("prompt", {
overlayStore.push(OverlayType.Prompt, {
heading: "",
valueName: "",
async onSubmit(value) {
@ -19,8 +19,7 @@
}
const respond = (value) => {
overlayStore.close("prompt");
overlayStore.open("toast", { message: value });
overlayStore.toast(value);
};
switch (parts[0]) {
@ -38,17 +37,15 @@
const doLogout = () => {
close();
logOut();
overlayStore.open("toast", {
message: "Logged out"
});
overlayStore.toast("Logged out");
};
const doSuperuserPrompt = async () => {
const { ok } = await request("POST", apiRoute("users/self/promote"), true);
if (ok) {
overlayStore.open("toast", { message: "You have been promoted to superuser" });
overlayStore.toast("You have been promoted to superuser");
} else {
overlayStore.open("toast", { message: "Failed to promote to superuser" });
overlayStore.toast("Failed to promote to superuser");
}
};
</script>

View file

@ -4,6 +4,7 @@
import { overlayStore } from "../../stores";
export let message;
export let close = () => {};
</script>
<style>
@ -25,7 +26,7 @@
{#key message}
<div class="toast" transition:maybeModalFly>
<span>{ message }</span>
<button class="icon-button icon-button-auto" on:click="{ () => overlayStore.close('toast') }">
<button class="icon-button icon-button-auto" on:click="{ close }">
<XIcon />
</button>
</div>

View file

@ -1,6 +1,6 @@
import { getItem } from "./storage";
// TODO: circular dependency
import { overlayStore } from "./stores";
import { overlayStore, OverlayType } from "./stores";
export function compatibleFetch(endpoint, options) {
if (window.fetch && typeof window.fetch === "function") {
@ -62,7 +62,7 @@ export default function doRequest(method, endpoint, auth=true, body=null, _keyEn
if (res.status === 403 && json.code && json.code === 6006 && !_keyEntryDepth) {
// This endpoint is password-protected
overlayStore.open("prompt", {
overlayStore.push(OverlayType.Prompt, {
heading: "Enter Key For Resource",
valueName: "Key",
async onSubmit(value) {

View file

@ -234,9 +234,7 @@ class MessageStore extends Store {
this.value = res.json.concat(this.value);
this.updated();
} else {
overlayStore.open("toast", {
message: "Messages failed to load"
});
overlayStore.toast("Messages failed to load");
}
}
@ -284,34 +282,68 @@ class MessagesStoreProvider {
}
}
export const OverlayType = {
CreateChannel: 0,
EditChannel: 1,
Toast: 2,
Login: 3,
CreateAccount: 4,
EditMessage: 5,
Settings: 6,
Prompt: 7,
};
class OverlayStore extends Store {
constructor() {
super({
createChannel: null,
editChannel: null,
toast: null,
login: null,
createAccount: null,
editMessage: null,
settings: null,
prompt: null,
}, "OverlayStore");
super([], "OverlayStore");
}
open(name, props={}) {
if (this.value[name] === undefined)
throw new Error(`OverlayStore.open: tried to open unknown overlay with name '${name}' (undefined in overlay map)`);
this.value[name] = props;
push(type, props={}) {
const id = Math.floor(Math.random() * 9999999);
props = {
...props,
close: () => {
this.popId(id);
}
}
this.value.push({
type,
props,
id
});
this.updated();
}
close(name) {
if (!this.value[name])
return;
this.value[name] = null;
pop() {
this.value.pop();
this.updated();
}
popType(type) {
for (let i = this.value.length - 1; i >= 0; i--) {
if (this.value[i].type === type) {
this.value.splice(i, 1);
this.updated();
return;
}
}
}
popId(id) {
for (let i = this.value.length - 1; i >= 0; i--) {
if (this.value[i].id === id) {
this.value.splice(i, 1);
this.updated();
return;
}
}
}
toast(message) {
this.push(OverlayType.Toast, { message });
}
}
class TypingStore extends Store {