add filter-based navigation between guilds and channels
This commit is contained in:
parent
9ad3b04da5
commit
c85657f8be
6 changed files with 157 additions and 50 deletions
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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!" />
|
||||||
|
|
55
frontend/src/components/FuzzyView.svelte
Normal file
55
frontend/src/components/FuzzyView.svelte
Normal 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!" />
|
|
@ -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">
|
||||||
|
|
|
@ -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} />
|
Loading…
Reference in a new issue