add filter-based navigation between guilds and channels

This commit is contained in:
hippoz 2022-02-03 02:43:11 +02:00
parent 9ad3b04da5
commit c85657f8be
Signed by: hippoz
GPG key ID: 7C52899193467641
6 changed files with 157 additions and 50 deletions

View file

@ -53,7 +53,7 @@ input, button, select, textarea {
padding: 0.4em; padding: 0.4em;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #ccc; border: none;
border-radius: 2px; border-radius: 2px;
} }
@ -132,3 +132,15 @@ button:focus {
border-radius: var(--button-border-radius); border-radius: var(--button-border-radius);
padding: 12px; padding: 12px;
} }
.main-panel-header {
font-size: 1.1em;
}
.error-text {
color: var(--color-red);
}
.grayed-text {
color: var(--color-red);
}

View file

@ -1,12 +1,15 @@
<script> <script>
import { apiClient } from "../api/common"; import { apiClient } from "../api/common";
import ChatView from "./ChatView.svelte"; import ChatView from "./ChatView.svelte";
import FuzzyView from "./FuzzyView.svelte";
let messageStore = {}; let messageStore = {};
let selectedGuild = null; let selectedGuild = null;
let selectedChannel = null; let selectedChannel = null;
let guilds = []; let guilds = [];
let user = null; let user = null;
let layer = { type: "CHAT" };
apiClient.getRequest("/users/@self") apiClient.getRequest("/users/@self")
.then((res) => { .then((res) => {
user = res.user; user = res.user;
@ -47,6 +50,24 @@
function onTextEntryMessage(event) { function onTextEntryMessage(event) {
const content = event.detail;
if (content.startsWith(":") && content.length > 1) {
switch (content[1]) {
case "g": {
layer = { type: "GUILD_FUZZY" };
break;
}
case "c": {
if (selectedGuild) {
layer = { type: "CHANNEL_FUZZY" };
}
break;
}
}
return;
}
if (!selectedGuild || !selectedChannel) { if (!selectedGuild || !selectedChannel) {
return false; return false;
} }
@ -82,11 +103,47 @@
messageStore[selectedGuild.id][selectedChannel.id][thisMessageIndex]._pendingStatus = "error"; messageStore[selectedGuild.id][selectedChannel.id][thisMessageIndex]._pendingStatus = "error";
}); });
} }
function fuzzySelectedGuild({ detail: id }) {
selectedGuild = guilds.find(e => e.id === id);
selectedChannel = null;
layer = { type: "CHAT" };
}
function fuzzySelectedChannel({ detail: id }) {
if (!selectedGuild)
return;
selectedChannel = selectedGuild.channels.find(e => e.id === id);
layer = { type: "CHAT" };
}
</script> </script>
<ChatView <style>
messageStore={messageStore} main {
selectedChannel={selectedChannel} box-sizing: border-box;
selectedGuild={selectedGuild} width: 820px;
on:entermessage={onTextEntryMessage} margin: 0 auto;
/> }
@media (max-width: 820px) {
main {
width: 100%;
}
}
</style>
<main>
{#if layer.type === "CHAT"}
<ChatView
messageStore={messageStore}
selectedChannel={selectedChannel}
selectedGuild={selectedGuild}
on:entermessage={onTextEntryMessage}
/>
{:else if layer.type === "GUILD_FUZZY"}
<FuzzyView on:selected={fuzzySelectedGuild} elements={guilds} title="Select a guild" />
{:else if layer.type === "CHANNEL_FUZZY"}
<FuzzyView on:selected={fuzzySelectedChannel} elements={selectedGuild ? selectedGuild.channels : []} title={`Select a channel in ${selectedGuild ? selectedGuild.name : "[unknown guild]"}`} />
{/if}
</main>

View file

@ -5,8 +5,11 @@
export let messageStore = {}; export let messageStore = {};
export let selectedChannel; export let selectedChannel;
export let selectedGuild; export let selectedGuild;
let warningMessage = null;
$: selectedChannelMessages = selectedGuild ? ((messageStore[selectedGuild.id] || [])[selectedChannel.id] || []) : []; // the mess below tries to get the messages for the selected channel from the message store
// if there are none or the channel isnt in the store, an empty array is returned
$: selectedChannelMessages = (selectedGuild && selectedChannel) ? ((messageStore[selectedGuild.id] || [])[selectedChannel.id] || []) : [];
</script> </script>
<style> <style>
@ -17,30 +20,16 @@
padding-left: 0; padding-left: 0;
margin-top: 4px; margin-top: 4px;
} }
.main-panel-header {
font-size: 1.1em;
}
main {
box-sizing: border-box;
width: 820px;
margin: 0 auto;
}
@media (max-width: 820px) {
main {
width: 100%;
}
}
</style> </style>
<main> <div class="card full-card">
<div class="card full-card"> <span class="main-panel-header">{(selectedGuild ? selectedGuild.name : "[no guild]")}{(selectedChannel ? selectedChannel.name : "[no channel]")}</span>
<span class="main-panel-header">{(selectedGuild ? selectedGuild.name : "[no guild]")}{(selectedChannel ? selectedChannel.name : "[no channel]")}</span> {#if selectedGuild && selectedChannel}
<div class="message-list-container"> <div class="message-list-container">
<MessageList messages={selectedChannelMessages} /> <MessageList messages={selectedChannelMessages} />
</div> </div>
</div> {:else}
<MessageEntry on:entermessage placeholder="Go on, type something interesting!" /> <span></span><span class="error-text">no channel selected</span>
</main> {/if}
</div>
<MessageEntry on:entermessage placeholder="Go on, type something interesting!" />

View file

@ -0,0 +1,55 @@
<script>
import { createEventDispatcher } from "svelte";
import MessageEntry from "./MessageEntry.svelte";
export let elements;
export let title;
let displayedElements = elements;
const dispatch = createEventDispatcher();
function elementClicked(id) {
dispatch("selected", id);
}
function onTextboxEnter({ detail: value }) {
// pressing enter will select the first element
if (displayedElements[0])
dispatch("selected", displayedElements[0].id);
}
function onInput(e) {
const value = e.target.value;
if (value.trim() === "") {
displayedElements = elements;
return;
}
displayedElements = elements.filter(e => e.name.toLowerCase().startsWith(value.toLowerCase()));
}
</script>
<style>
.option-button {
width: 100%;
padding: 16px;
margin: 0px;
box-sizing: border-box;
}
.center-text {
text-align: center;
}
</style>
<div class="card full-card option-card">
<span class="main-panel-header">{ title }</span>
{#each displayedElements as element}
<button class="button option-button" on:click={elementClicked(element.id)}>{ element.name }</button>
{/each}
{#if displayedElements.length < 1}
<div class="center-text">
<p>no results</p>
</div>
{/if}
</div>
<MessageEntry on:input={onInput} on:entermessage={onTextboxEnter} placeholder="Go on, type something interesting!" />

View file

@ -4,9 +4,9 @@
let textClass; let textClass;
$: if (message._pendingStatus === "waiting") { $: if (message._pendingStatus === "waiting") {
textClass = "content pending-waiting"; textClass = "content grayed-text";
} else if (message._pendingStatus === "error") { } else if (message._pendingStatus === "error") {
textClass = "content pending-error"; textClass = "content error-text";
} else { } else {
textClass = "content"; textClass = "content";
} }
@ -24,14 +24,6 @@
.content { .content {
margin-left: 4px; margin-left: 4px;
} }
.pending-waiting {
color: var(--grayed-text-color);
}
.pending-error {
color: var(--color-red);
}
</style> </style>
<div class="container"> <div class="container">

View file

@ -1,19 +1,21 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
export let placeholder; export let placeholder;
let text = ""; let text = "";
let inputRef;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function onKeydown(e) { function onKeydown(e) {
dispatch("keydown", e);
if (e.code === "Enter") { if (e.code === "Enter") {
if (text.trim() !== "") { dispatch("entermessage", text);
dispatch("entermessage", text); text = "";
text = "";
}
} }
} }
onMount(() => inputRef.focus());
</script> </script>
<input class="separated-card card full-card" placeholder={placeholder} on:keydown={onKeydown} bind:value={text} /> <input class="separated-card card full-card" placeholder={placeholder} bind:this={inputRef} on:input on:keydown={onKeydown} bind:value={text} />