Add channel and permission management, sounds, themes and the public server list
- Channels: create, edit, delete, link and move (drag and drop for channels and people), right-click on free space in the channel list - Permission editor with inherited rules, allow/deny per permission, groups and members; menus grey out actions the server does not allow - Notification sounds for connection, channel, chat, mute and push-to-talk events; custom files and TeamSpeak 3 style sound packs (settings.ini), stored in IndexedDB - Color schemes: Dark, Light, Midnight, Frost and Windows 95 - Public server list with live UDP ping, fetched through Chromium's network stack - Kick, ban and connection-lost card with Reconnect - Right-click menus for text fields, links, images and message authors - Formatting toolbar in the message box, shared with the description editor - Resizable channel list, menu headers, registration from the self menu only - Fix: client-side pacing now mirrors Murmur's whole-second leaky bucket exactly, so quick actions are no longer dropped silently Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+17
-3
@@ -12,10 +12,12 @@
|
||||
import IdentityWizard from './ui/IdentityWizard.svelte';
|
||||
import QuickAudio from './ui/QuickAudio.svelte';
|
||||
import UserInfoDialog from './ui/UserInfoDialog.svelte';
|
||||
import ChannelDialog from './ui/ChannelDialog.svelte';
|
||||
import PublicServers from './ui/PublicServers.svelte';
|
||||
import { identities } from './lib/identities.svelte.ts';
|
||||
import { session } from './lib/session.svelte.ts';
|
||||
import { ui } from './lib/ui.svelte.ts';
|
||||
import type { SavedServer } from './lib/settings.svelte.ts';
|
||||
import { store, type SavedServer } from './lib/settings.svelte.ts';
|
||||
|
||||
let width = $state(window.innerWidth);
|
||||
// wide: rail + sidebar + chat + panel; medium: panel as an overlay; narrow: everything but chat in drawers
|
||||
@@ -26,6 +28,13 @@
|
||||
|
||||
const showPanel = $derived(ui.panelOpen && session.status === 'connected');
|
||||
|
||||
// Color scheme on the root element; dark is the stylesheet default
|
||||
$effect(() => {
|
||||
const t = store.settings.theme;
|
||||
if (t && t !== 'dark') document.documentElement.dataset.theme = t;
|
||||
else delete document.documentElement.dataset.theme;
|
||||
});
|
||||
|
||||
// The panel starts open on wide layouts only
|
||||
let lastLayout = '';
|
||||
$effect(() => {
|
||||
@@ -53,9 +62,9 @@
|
||||
<div class="app {layout}">
|
||||
{#if layout !== 'narrow' || navOpen}
|
||||
<div class="nav" class:drawer={layout === 'narrow'}>
|
||||
<ServerRail onadd={() => (editing = { server: null })} onedit={s => (editing = { server: s })}
|
||||
<ServerRail onadd={() => (editing = { server: null })} onedit={s => (editing = { server: s })} onbrowse={() => (ui.publicServers = true)}
|
||||
onsettings={() => (ui.settingsOpen = true)} />
|
||||
<Sidebar onnavigate={() => { if (layout === 'narrow') navOpen = false; }} />
|
||||
<Sidebar resizable={layout !== 'narrow'} onnavigate={() => { if (layout === 'narrow') navOpen = false; }} />
|
||||
</div>
|
||||
{#if layout === 'narrow'}<button class="scrim" aria-label="Close" onclick={() => (navOpen = false)}></button>{/if}
|
||||
{/if}
|
||||
@@ -73,6 +82,8 @@
|
||||
</div>
|
||||
|
||||
{#if editing}<ConnectDialog server={editing.server} onclose={() => (editing = null)} />{/if}
|
||||
{#if ui.addServer}<ConnectDialog server={null} prefill={ui.addServer} onclose={() => (ui.addServer = null)} />{/if}
|
||||
{#if ui.publicServers}<PublicServers onclose={() => (ui.publicServers = false)} />{/if}
|
||||
{#if ui.settingsOpen}<SettingsDialog tab={ui.settingsTab} onclose={() => { ui.settingsOpen = false; ui.settingsTab = 'voice'; }} />{/if}
|
||||
{#if ui.quickAudio}<QuickAudio />{/if}
|
||||
{#if ui.userInfo != null && session.status === 'connected'}
|
||||
@@ -82,6 +93,9 @@
|
||||
{#if ui.cert}<CertViewer {...ui.cert} onclose={() => (ui.cert = null)} />{/if}
|
||||
{#if identities.loaded && !identities.setupDone}<IdentityWizard mode="setup" onclose={() => {}} />
|
||||
{:else if ui.wizard}<IdentityWizard mode={ui.wizard} onclose={() => (ui.wizard = null)} />{/if}
|
||||
{#if ui.channelDialog && session.status === 'connected'}
|
||||
{#key ui.channelDialog}<ChannelDialog {...ui.channelDialog} onclose={() => (ui.channelDialog = null)} />{/key}
|
||||
{/if}
|
||||
{#if ui.prompt}<PromptDialog {...ui.prompt} onclose={() => (ui.prompt = null)} />{/if}
|
||||
<ContextMenu />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user