Remove Activity and the notification system

This commit is contained in:
2026-10-05 17:25:39 -04:00
parent 193383ddd5
commit b62fb5db30
11 changed files with 32 additions and 216 deletions
+7 -5
View File
@@ -1,11 +1,13 @@
<script lang="ts">
import Button from './primitives/Button.svelte';
import { toast } from '../toast.js';
let { value, label = 'ID' }: { value: string; label?: string } = $props();
let copyStatus = $state('');
$effect(() => { value; copyStatus = ''; });
async function copy() {
try { await navigator.clipboard.writeText(value); toast(`${label} copied.`, { variant: 'success' }); }
catch { toast('Copy unavailable. Select and copy the text directly.', { variant: 'warning' }); }
copyStatus = '';
try { await navigator.clipboard.writeText(value); copyStatus = `${label} copied.`; }
catch { copyStatus = 'Copy unavailable. Select and copy the text directly.'; }
}
</script>
<div class="copy"><code>{value}</code><Button variant="ghost" size="sm" ariaLabel={`Copy ${label}`} onclick={copy}>Copy</Button></div>
<style>.copy { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); color: var(--text-secondary); } code { font: var(--text-xs) var(--font-mono); user-select: all; overflow-wrap: anywhere; }</style>
<div class="copy"><code>{value}</code><Button variant="ghost" size="sm" ariaLabel={`Copy ${label}`} onclick={copy}>Copy</Button>{#if copyStatus}<span class="copy-status" role="status">{copyStatus}</span>{/if}</div>
<style>.copy-status { font-size: var(--text-xs); } .copy { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); color: var(--text-secondary); } code { font: var(--text-xs) var(--font-mono); user-select: all; overflow-wrap: anywhere; }</style>
+7 -4
View File
@@ -2,7 +2,6 @@
import { tick, untrack, onMount, onDestroy } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { toast } from '$ui/toast.js';
import { createLogRefresh, type LogResponse } from '$ui/log-refresh.js';
import Tabs from './primitives/Tabs.svelte';
import Button from './primitives/Button.svelte';
@@ -33,11 +32,12 @@
let loading = $state(false);
let autoRefresh = $state(true);
let refreshError = $state('');
let copyStatus = $state('');
let bodyEl = $state<HTMLPreElement | null>(null);
let stickToBottom = $state(true);
const refresh = createLogRefresh({
onReset: () => { data = {}; refreshError = ''; },
onReset: () => { data = {}; refreshError = ''; copyStatus = ''; },
onLoading: value => { loading = value; },
onError: message => { refreshError = message; },
onData: (key, body) => {
@@ -67,8 +67,9 @@
await tick(); if (bodyEl) bodyEl.scrollTop = bodyEl.scrollHeight;
}
async function copy(value: string, message: string) {
try { await navigator.clipboard.writeText(value); toast(message); }
catch { toast('Could not copy. Select the log text to copy manually.', { variant: 'danger' }); }
copyStatus = '';
try { await navigator.clipboard.writeText(value); copyStatus = message; }
catch { copyStatus = 'Could not copy. Select the log text or browser address to copy manually.'; }
}
function copyLink() {
const url = new URL(window.location.href); url.searchParams.set('tab', 'logs'); url.searchParams.set('log', active);
@@ -134,6 +135,7 @@
</div>
</div>
<div class="log-tools"><label class="log-search">Search loaded lines<input type="search" bind:value={query} placeholder="Find text in this log" /></label><div class="log-actions"><Button size="sm" onclick={copyLink}>Copy link</Button><Button size="sm" disabled={!current?.lines?.length} onclick={() => copy(visibleLines.join('\n'), 'Log text copied.')}>Copy {query ? 'matches' : 'log'}</Button><Button size="sm" disabled={!current?.lines?.length} onclick={download}>Download tail</Button></div></div>
{#if copyStatus}<p class="copy-status" role="status">{copyStatus}</p>{/if}
<div class="follow-state"><span>{!autoRefresh ? 'Refresh paused' : 'Auto-refresh on'} · {query ? `${visibleLines.length} matching lines` : stickToBottom ? 'Following latest lines' : 'Reading earlier lines'}</span>{#if !stickToBottom || query}<Button size="sm" variant="ghost" onclick={jumpToLatest}>Jump to latest</Button>{/if}</div>
<div id={uid + '-panel'} role="tabpanel" aria-labelledby={uid + '-' + active}>
@@ -167,6 +169,7 @@
.log-tools { align-items: end; }
.log-search { display: grid; gap: 6px; flex: 1; min-width: 180px; font-size: var(--text-xs); color: var(--text-secondary); }
.log-search input { background: var(--surface-sunken); color: var(--text-primary); border: 1px solid var(--border-control); border-radius: var(--radius-sm); padding: 9px 12px; font: inherit; }
.copy-status { margin: 0; font-size: var(--text-xs); color: var(--text-secondary); }
.follow-state { justify-content: space-between; font-size: var(--text-xs); color: var(--text-secondary); }
mark { background: var(--state-warning); color: var(--surface-sunken); }
.log-panel {
@@ -1,25 +0,0 @@
<script lang="ts">
import { timeFormat } from '$ui/stores/time-zone.js';
import Dialog from './primitives/Dialog.svelte';
import Button from './primitives/Button.svelte';
import { notificationHistory, markNotificationsRead, clearNotifications } from '$ui/toast.js';
let { open = $bindable(false) }: { open?: boolean } = $props();
$effect(() => { if (open) markNotificationsRead(); });
</script>
<Dialog bind:open title="Recent notifications" description="Recent action results and completed lobby watches in this browser tab. Watches run while the app is open.">
{#if $notificationHistory.length}
<ol>{#each $notificationHistory as item (item.id)}<li class:attention={item.variant === 'danger' || item.variant === 'warning'}><p>{item.message}</p><time datetime={item.createdAt}>{$timeFormat.dateTime(item.createdAt)}</time>{#if item.href}<a href={item.href} onclick={() => open = false}>View details →</a>{/if}</li>{/each}</ol>
{:else}<p class="empty">No recent notifications. Completed lobby watches and action results will appear here.</p>{/if}
{#snippet actions()}<Button disabled={!$notificationHistory.length} onclick={clearNotifications}>Clear history</Button><Button onclick={() => open = false}>Close</Button>{/snippet}
</Dialog>
<style>
ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
li { border: 1px solid var(--border-strong); padding: 12px; border-radius: var(--radius-md); }
li.attention { border-color: var(--state-warning); }
p { margin: 0 0 8px; font-size: var(--text-sm); }
time { display: block; color: var(--text-secondary); font-size: var(--text-xs); }
a { display: inline-block; margin-top: 8px; font-size: var(--text-sm); }
.empty { color: var(--text-secondary); }
</style>
@@ -6,7 +6,6 @@
import Card from './primitives/Card.svelte';
import Button from './primitives/Button.svelte';
import Badge from './primitives/Badge.svelte';
import { toast } from '$ui/toast.js';
let { pluginId, targetPath, disabled = false, busy = $bindable(false), onimport }: {
pluginId?: number; targetPath?: string; disabled?: boolean; busy?: boolean; onimport?: (result: { pluginId: number; betaUrl: string; alreadyImported: boolean }) => void | Promise<void>;
@@ -71,12 +70,12 @@
});
const body = await response.json();
if (!response.ok) throw new Error(body.error ?? 'Import failed.');
if (!active) { toast('Plugin imported. Review its version when ready.', { variant: 'success', href: `/plugins/${body.pluginId}` }); return; }
if (!active) return;
success = body;
await onimport?.(body);
if (startBeta && window.location.pathname === startPath) await goto(body.betaUrl);
else if (pluginId) await checkUpdates();
} catch (e) { error = (e as Error).message; if (!active) toast(`Plugin import failed: ${error}`, { variant: 'danger', href: '/plugins' }); }
} catch (e) { error = (e as Error).message; }
finally { busy = false; }
}
</script>
-88
View File
@@ -1,88 +0,0 @@
<script lang="ts">
import { fly, fade } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { toasts, dismiss } from '$ui/toast.js';
</script>
<div class="toaster" role="region" aria-label="Notifications" aria-live="polite">
{#each $toasts as t (t.id)}
<div
class="toast v-{t.variant}"
in:fly={{ y: 16, duration: 240, easing: cubicOut }}
out:fade={{ duration: 160 }}
>
<span class="dot" aria-hidden="true"></span>
<span class="msg">{t.message}</span>
{#if t.href}<a href={t.href} onclick={() => dismiss(t.id)}>View</a>{/if}
<button
type="button"
class="close"
onclick={() => dismiss(t.id)}
aria-label="Dismiss notification"
>×</button>
</div>
{/each}
</div>
<style>
.toaster {
position: fixed;
bottom: var(--space-6);
right: var(--space-6);
z-index: 200;
display: flex;
flex-direction: column;
gap: var(--space-2);
pointer-events: none;
max-width: min(420px, calc(100vw - 2 * var(--space-6)));
}
.toast {
pointer-events: auto;
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
background: color-mix(in oklab, var(--surface-raised) 92%, transparent);
color: var(--text-primary);
border: 1px solid var(--border-strong);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
font-size: var(--text-sm);
}
@supports (backdrop-filter: blur(4px)) {
.toast {
backdrop-filter: blur(12px) saturate(1.4);
-webkit-backdrop-filter: blur(12px) saturate(1.4);
background: color-mix(in oklab, var(--surface-raised) 78%, transparent);
}
}
.dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
background: var(--text-tertiary);
}
.v-success .dot { background: var(--state-online); box-shadow: 0 0 4px rgba(124, 212, 255, 0.5); }
.v-warning .dot { background: var(--state-warning); }
.v-danger .dot { background: var(--state-danger); }
.v-info .dot { background: var(--accent-primary); }
.msg { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.close {
appearance: none;
background: transparent;
border: none;
color: var(--text-tertiary);
font-size: var(--text-lg);
line-height: 1;
cursor: pointer;
padding: 0 var(--space-1);
border-radius: var(--radius-sm);
transition: color var(--dur-fast) var(--ease-standard);
}
.close:hover { color: var(--text-primary); }
.close:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (max-width: 720px) {
.toaster { left: max(16px, env(safe-area-inset-left)); right: max(16px, env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom)); max-width: none; }
.close { min-width: 44px; flex-shrink: 0; }
}
</style>