Compare commits
3 Commits
a1bd44ffb3
...
e0fe175566
Author | SHA1 | Date | |
---|---|---|---|
e0fe175566 | |||
7d581afcaf | |||
38f926b32f |
8
package-lock.json
generated
8
package-lock.json
generated
@ -13,7 +13,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/svelte-fontawesome": "^0.2.0",
|
"@fortawesome/svelte-fontawesome": "^0.2.0",
|
||||||
"detect-it": "^4.0.1",
|
"detect-it": "^4.0.1",
|
||||||
"fedwave-chat-client": "^0.0.1",
|
"fedwave-chat-client": "^0.0.2",
|
||||||
"jose": "^4.11.2",
|
"jose": "^4.11.2",
|
||||||
"svrollbar": "^0.12.0"
|
"svrollbar": "^0.12.0"
|
||||||
},
|
},
|
||||||
@ -1605,9 +1605,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fedwave-chat-client": {
|
"node_modules/fedwave-chat-client": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/fedwave-chat-client/-/fedwave-chat-client-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fedwave-chat-client/-/fedwave-chat-client-0.0.2.tgz",
|
||||||
"integrity": "sha512-VP7OJELCx1V0c26QbgF+mLoMq4EyPLbrjIylAvS12hv2XVlmGJRqnrh6kCVHqSr6ZwirjlM/eLSDgJ2jWXMiyw==",
|
"integrity": "sha512-r4rvOGVEvb09LrIrWOF4D1yEd/9duxameFlnJltOZxXagPkzS5RhJagIOKO0L+37moUHOVv/cMu3Gg+YFDA0Pg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io-client": "^3.1.3"
|
"socket.io-client": "^3.1.3"
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/svelte-fontawesome": "^0.2.0",
|
"@fortawesome/svelte-fontawesome": "^0.2.0",
|
||||||
"detect-it": "^4.0.1",
|
"detect-it": "^4.0.1",
|
||||||
"fedwave-chat-client": "^0.0.1",
|
"fedwave-chat-client": "^0.0.2",
|
||||||
"jose": "^4.11.2",
|
"jose": "^4.11.2",
|
||||||
"svrollbar": "^0.12.0"
|
"svrollbar": "^0.12.0"
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
export let size: string = 'var(--avatar-size)';
|
export let size: string = 'var(--avatar-size)';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="avatar pa-0 border-0" style:--color="{color}" style:--size="{size}" on:click>
|
<button class="avatar pa-0 border-0 shrink-0" style:--color="{color}" style:--size="{size}" on:click>
|
||||||
<img src="{avatar}" alt="{`${username}'s avatar`}">
|
<img src="{avatar}" alt="{`${username}'s avatar`}">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Message } from "fedwave-chat-client";
|
import type { Message } from "fedwave-chat-client";
|
||||||
import { room, username } from "./chat";
|
import { room } from "./chat";
|
||||||
|
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let globalMode = false;
|
export let globalMode = false;
|
||||||
export let message: Message;
|
export let message: Message;
|
||||||
export let isHeader: boolean = true;
|
export let isHeader: boolean = true;
|
||||||
@ -8,7 +12,11 @@
|
|||||||
|
|
||||||
<div class="message d-flex p-relative pl-8 pr-24" class:subseq="{!isHeader}">
|
<div class="message d-flex p-relative pl-8 pr-24" class:subseq="{!isHeader}">
|
||||||
{#if isHeader}
|
{#if isHeader}
|
||||||
<div class="avatar radius-pill overflow-hidden no-select" style="background-color: {message.color}">
|
<!-- TODO: Use <Avatar/> here -->
|
||||||
|
<div class="avatar radius-pill overflow-hidden no-select clickable"
|
||||||
|
on:click="{() => dispatch('avatar:click', message)}"
|
||||||
|
on:dblclick="{() => dispatch('avatar:dblclick', message)}"
|
||||||
|
style="background-color: {message.color}">
|
||||||
<img class="w-100" alt="{message.username}'s avatar" src="{message.avatar || '/troll_haz2.png'}">
|
<img class="w-100" alt="{message.username}'s avatar" src="{message.avatar || '/troll_haz2.png'}">
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
let viewport: Element, contents: Element;
|
let viewport: Element, contents: Element;
|
||||||
let tweet: HTMLAudioElement;
|
let tweet: HTMLAudioElement;
|
||||||
|
|
||||||
|
let input: string = '';
|
||||||
|
|
||||||
shouldTweet.subscribe((should) => {
|
shouldTweet.subscribe((should) => {
|
||||||
if (should) {
|
if (should) {
|
||||||
if (tweet) {
|
if (tweet) {
|
||||||
@ -91,6 +93,10 @@
|
|||||||
bind:viewport
|
bind:viewport
|
||||||
bind:contents
|
bind:contents
|
||||||
on:scroll={onScroll}
|
on:scroll={onScroll}
|
||||||
|
on:avatar:click={(e) => (
|
||||||
|
(input += ` @${e.detail.username}#${e.detail.unum} `), (input = input.trim())
|
||||||
|
)}
|
||||||
|
on:avatar:dblclick={(e) => (input = `/w ${e.detail.username}#${e.detail.unum} `)}
|
||||||
/>
|
/>
|
||||||
{#if !stickToBottom}
|
{#if !stickToBottom}
|
||||||
<FAB --background={'var(--base-700)'} on:click={() => scrollToBottom()}
|
<FAB --background={'var(--base-700)'} on:click={() => scrollToBottom()}
|
||||||
@ -98,7 +104,7 @@
|
|||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Input />
|
<Input bind:input />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.scroller {
|
.scroller {
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from "svelte"
|
import { tick } from "svelte"
|
||||||
import { send } from "./chat";
|
import { send, whisper } from "./chat";
|
||||||
|
|
||||||
let input = "";
|
export let input = "";
|
||||||
async function keydown(e: KeyboardEvent) {
|
async function keydown(e: KeyboardEvent) {
|
||||||
if(e.key === "Enter") {
|
if(e.key === "Enter") {
|
||||||
send(input);
|
console.log(e);
|
||||||
|
const tokens = input.trim().split(" ");
|
||||||
|
|
||||||
|
if(tokens[0] === '/w') {
|
||||||
|
const who = tokens[1];
|
||||||
|
const m = tokens.slice(2).join(' ');
|
||||||
|
whisper(who, m);
|
||||||
|
} else {
|
||||||
|
send(input);
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await tick();
|
await tick();
|
||||||
input = "";
|
input = "";
|
||||||
|
@ -12,7 +12,10 @@
|
|||||||
<div class="viewport" bind:this={viewport} on:scroll>
|
<div class="viewport" bind:this={viewport} on:scroll>
|
||||||
<div class="contents" bind:this={contents}>
|
<div class="contents" bind:this={contents}>
|
||||||
{#each messages as message}
|
{#each messages as message}
|
||||||
<ChatMessage message={message.m} isHeader={message.isHeader} globalMode={showChannel} />
|
<ChatMessage message={message.m}
|
||||||
|
isHeader={message.isHeader}
|
||||||
|
globalMode={showChannel}
|
||||||
|
on:avatar:click on:avatar:dblclick />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,19 +3,54 @@
|
|||||||
export let mobile = false;
|
export let mobile = false;
|
||||||
|
|
||||||
import { FontAwesomeIcon as FA } from '@fortawesome/svelte-fontawesome';
|
import { FontAwesomeIcon as FA } from '@fortawesome/svelte-fontawesome';
|
||||||
import { faHome, faPerson } from '@fortawesome/free-solid-svg-icons';
|
import { faHome, faPerson, faCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
import { channels, room } from './chat';
|
||||||
|
import Avatar from './Avatar.svelte';
|
||||||
|
|
||||||
|
let selected = "channels";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sidebar h-100vh d-flex" bind:clientWidth class:mobile="{mobile}">
|
<div class="sidebar h-100vh d-flex" bind:clientWidth class:mobile="{mobile}">
|
||||||
<div class="overlay" />
|
<div class="overlay" />
|
||||||
<div class="servers d-flex col">
|
<div class="servers shrink-0 d-flex col">
|
||||||
<button class="selected">
|
<button
|
||||||
|
class:selected="{selected === 'whispers'}"
|
||||||
|
on:click="{() => selected = 'whispers'}">
|
||||||
<FA size="2x" icon="{faPerson}" />
|
<FA size="2x" icon="{faPerson}" />
|
||||||
</button>
|
</button>
|
||||||
<button>
|
<button
|
||||||
|
class:selected="{selected === 'channels'}"
|
||||||
|
on:click="{() => selected = 'channels'}">
|
||||||
<FA size="2x" icon="{faHome}" />
|
<FA size="2x" icon="{faHome}" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{#if selected === 'whispers'}
|
||||||
|
<div class="whispers grow-1 mw-0"></div>
|
||||||
|
{:else if selected === 'channels'}
|
||||||
|
<div class="channels grow-1 d-flex col mw-0 py-8">
|
||||||
|
{#each $channels.sort((a, b) => b.viewCount - a.viewCount) as channel}
|
||||||
|
<a href="{channel.name}"
|
||||||
|
class="channel d-flex align-center mb-8 mx-8 pa-6 radius-12"
|
||||||
|
class:selected="{$room === channel.name.toLowerCase()}">
|
||||||
|
<Avatar username="{channel.name}"
|
||||||
|
avatar="{channel.avatar}" />
|
||||||
|
<div class="channel-name mw-0 ellipsis ml-8">
|
||||||
|
{channel.name}
|
||||||
|
</div>
|
||||||
|
{#if false && channel.live}
|
||||||
|
<div class="channel-live ml-2 mt-8 color-red">
|
||||||
|
<FA icon="{faCircle}" size="xs" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="spacer"/>
|
||||||
|
<div class="channel-viewers px-6 py-2 radius-pill">
|
||||||
|
{channel.viewCount + channel.viewCountRTC}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -48,6 +83,15 @@
|
|||||||
pointer-events: var(--should-display);
|
pointer-events: var(--should-display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-live {
|
||||||
|
align-self: normal;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-viewers {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.servers {
|
.servers {
|
||||||
width: var(--sidebar-server-select-width);
|
width: var(--sidebar-server-select-width);
|
||||||
background-color: var(--base-500);
|
background-color: var(--base-500);
|
||||||
@ -75,6 +119,29 @@
|
|||||||
background-color: var(--base-700);
|
background-color: var(--base-700);
|
||||||
color: var(--base-300);
|
color: var(--base-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: var(--base-400);
|
||||||
|
color: var(--base-700);
|
||||||
|
|
||||||
|
transition-property: background-color, color, border-radius;
|
||||||
|
transition-duration: 80ms;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--base-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: var(--base-700);
|
||||||
|
color: var(--base-300);
|
||||||
|
|
||||||
|
& .channel-viewers {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -20,6 +20,10 @@ export function send(m: string | OutgoingMessage) {
|
|||||||
chat.sendMessage(m);
|
chat.sendMessage(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function whisper(who: string, m: string) {
|
||||||
|
chat.sendWhisper(who, m);
|
||||||
|
}
|
||||||
|
|
||||||
let setConnected: Subscriber<boolean> | undefined;
|
let setConnected: Subscriber<boolean> | undefined;
|
||||||
export const connected = readable<boolean>(false, (setf) => {
|
export const connected = readable<boolean>(false, (setf) => {
|
||||||
setConnected = setf;
|
setConnected = setf;
|
||||||
@ -97,10 +101,46 @@ globalMode.subscribe((v) => {
|
|||||||
|
|
||||||
export const shouldTweet = writable(false);
|
export const shouldTweet = writable(false);
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
color: string;
|
||||||
|
page: string;
|
||||||
|
username: string;
|
||||||
|
unum: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Channel = Record<
|
||||||
|
| 'avatar'
|
||||||
|
| 'channel'
|
||||||
|
| 'cover'
|
||||||
|
| 'desc'
|
||||||
|
| 'name'
|
||||||
|
| 'src'
|
||||||
|
| 'thumbnail'
|
||||||
|
| 'title'
|
||||||
|
| 'type'
|
||||||
|
| 'url'
|
||||||
|
| 'user',
|
||||||
|
string
|
||||||
|
> &
|
||||||
|
Record<'live' | 'nsfw', boolean> &
|
||||||
|
Record<'viewCount' | 'viewCountRTC', number> & {
|
||||||
|
users: Record<string, { watching: string[]; data: User }>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
let setChannels: Subscriber<any> | undefined;
|
||||||
|
export const channels = readable<Channel[]>(chat.channelViewers, (setf) => {
|
||||||
|
setChannels = setf;
|
||||||
|
return () => {
|
||||||
|
setChannels = undefined;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
chat.onUpdateUsernames = (vs) => setChannels?.(vs) as any;
|
||||||
|
|
||||||
// Essentially a shared_ptr idiom. When nobody is subscribed to this, the chat
|
// Essentially a shared_ptr idiom. When nobody is subscribed to this, the chat
|
||||||
// d/c's.
|
// d/c's.
|
||||||
export const chat_lock = readable(null, () => {
|
export const chat_lock = readable(null, () => {
|
||||||
const release_connected = connected.subscribe(() => {});
|
const release_connected = connected.subscribe(() => {});
|
||||||
|
const release_channels = channels.subscribe(() => {});
|
||||||
|
|
||||||
const creds = get(credentials);
|
const creds = get(credentials);
|
||||||
|
|
||||||
@ -111,6 +151,7 @@ export const chat_lock = readable(null, () => {
|
|||||||
setConnected?.(false);
|
setConnected?.(false);
|
||||||
|
|
||||||
release_connected();
|
release_connected();
|
||||||
|
release_channels();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,6 +37,10 @@
|
|||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shrink-0 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.basis-0 {
|
.basis-0 {
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
}
|
}
|
||||||
@ -62,8 +66,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-flex {
|
@each $i in (block, inline, inline-block, flex, grid, contents) {
|
||||||
display: flex;
|
.d-#{$i} {
|
||||||
|
display: #{$i};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
@ -82,9 +88,16 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mw-0 { min-width: 0 }
|
||||||
|
|
||||||
.w-100 { width: 100%; }
|
.w-100 { width: 100%; }
|
||||||
.h-100 { height: 100%; }
|
.h-100 { height: 100%; }
|
||||||
|
|
||||||
|
.ellipsis {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.hide-scrollbar {
|
.hide-scrollbar {
|
||||||
/* hide scrollbar */
|
/* hide scrollbar */
|
||||||
-ms-overflow-style: none !important;
|
-ms-overflow-style: none !important;
|
||||||
|
@ -60,7 +60,9 @@
|
|||||||
updateCredentials(undefined);
|
updateCredentials(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mobile = primaryInput === 'touch';
|
let viewportWidth;
|
||||||
|
let mobile: boolean;
|
||||||
|
$: mobile = (primaryInput === 'touch' || viewportWidth <= 550);
|
||||||
|
|
||||||
let scrolledToTop = false, scrolledToBottom = false,
|
let scrolledToTop = false, scrolledToBottom = false,
|
||||||
scrolledToLeft = false, scrolledToRight = false;
|
scrolledToLeft = false, scrolledToRight = false;
|
||||||
@ -154,6 +156,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:body use:updateTheme="{$theme}" />
|
<svelte:body use:updateTheme="{$theme}" />
|
||||||
|
<svelte:window bind:innerWidth={viewportWidth}/>
|
||||||
|
|
||||||
<div class="container hide-scrollbar snap-y h-100vh"
|
<div class="container hide-scrollbar snap-y h-100vh"
|
||||||
class:mobile="{mobile}"
|
class:mobile="{mobile}"
|
||||||
|
Loading…
Reference in New Issue
Block a user