greatly overhaul overlay system
This commit is contained in:
parent
d8552c0328
commit
4c9e321167
16 changed files with 117 additions and 117 deletions
|
@ -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, {});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -71,9 +71,7 @@
|
|||
messages.deleteMessage({
|
||||
id: optimisticMessageId
|
||||
});
|
||||
overlayStore.open("toast", {
|
||||
message: "Couldn't send message"
|
||||
});
|
||||
overlayStore.toast("Couldn't send message");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue