feat(app): clear all events from the Event Center (red trash icon)
Adds Cmd::ClearEvents + Evt::EventsCleared: the daemon drops the persistent event log (keeping next_id monotonic), persists, and broadcasts so every client empties its list. A red trash icon next to 'Mark all read' triggers it; disabled when the list is empty. Threaded through proto, the daemon handler, the Tauri bridge, and socketBridge. Includes an EventLog::clear test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+4
-1
@@ -9,7 +9,7 @@ import { Settings } from "./Settings";
|
||||
import { EventCenter } from "./EventCenter";
|
||||
import { maybeNotify } from "./notify";
|
||||
import { COLORS, applyTheme, resolvePalette } from "./theme";
|
||||
import { getStatusFull, applyPreset, onDaemonEvent, onDaemonRawEvent, setWorkspaceMeta, focusSurface, getEventLog, markEventsRead, getHealth, closeWorkspaceCmd, getConfig } from "./socketBridge";
|
||||
import { getStatusFull, applyPreset, onDaemonEvent, onDaemonRawEvent, setWorkspaceMeta, focusSurface, getEventLog, markEventsRead, clearEvents, getHealth, closeWorkspaceCmd, getConfig } from "./socketBridge";
|
||||
import type { EventRecord, DaemonHealth, ConfigView } from "./socketBridge";
|
||||
import { leafIds } from "./layoutTypes";
|
||||
import type { Group, WorkspaceView, SurfaceState } from "./layoutTypes";
|
||||
@@ -95,6 +95,8 @@ export function App() {
|
||||
} else if (evt.evt === "events_read") {
|
||||
const ids = new Set(evt.data.ids);
|
||||
setEvents((es) => es.map((e) => (ids.has(e.id) ? { ...e, read: true } : e)));
|
||||
} else if (evt.evt === "events_cleared") {
|
||||
setEvents([]);
|
||||
} else if (evt.evt === "state") {
|
||||
setStates((m) => ({ ...m, [evt.data.surface_id]: evt.data.state }));
|
||||
void refresh();
|
||||
@@ -177,6 +179,7 @@ export function App() {
|
||||
<EventCenter
|
||||
events={events}
|
||||
onMarkAllRead={() => { void markEventsRead({ target: "all" }); }}
|
||||
onClear={() => { void clearEvents(); }}
|
||||
onSelect={(sid, id) => { void focusSurface(sid); void markEventsRead({ target: "ids", value: [id] }); }}
|
||||
/>
|
||||
)}
|
||||
|
||||
+11
-3
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Check, Hourglass, X, Power, Send, MessageSquare } from "lucide-react";
|
||||
import { Check, Hourglass, X, Power, Send, MessageSquare, Trash2 } from "lucide-react";
|
||||
import { COLORS, FONT } from "./theme";
|
||||
import type { EventRecord } from "./socketBridge";
|
||||
|
||||
@@ -26,10 +26,11 @@ function rel(ts: number): string {
|
||||
}
|
||||
|
||||
export function EventCenter({
|
||||
events, onMarkAllRead, onSelect,
|
||||
events, onMarkAllRead, onClear, onSelect,
|
||||
}: {
|
||||
events: EventRecord[];
|
||||
onMarkAllRead: () => void;
|
||||
onClear: () => void;
|
||||
onSelect: (surfaceId: string, id: number) => void;
|
||||
}) {
|
||||
const [tab, setTab] = useState<Tab>("all");
|
||||
@@ -41,7 +42,14 @@ export function EventCenter({
|
||||
<div style={{ display: "flex", flexDirection: "column", width: 300, flex: "0 0 300px", background: COLORS.bgSidebar, height: "100%", padding: 14, boxSizing: "border-box", borderLeft: `1px solid ${COLORS.borderSubtle}` }}>
|
||||
<div style={{ display: "flex", alignItems: "center", marginBottom: 12 }}>
|
||||
<span style={{ fontFamily: FONT.ui, fontSize: 13, fontWeight: 700, color: COLORS.textPrimary, flex: 1 }}>Event Center</span>
|
||||
<span onClick={onMarkAllRead} style={{ fontFamily: FONT.ui, fontSize: 11, color: COLORS.accent, cursor: "pointer" }}>Mark all read</span>
|
||||
<span onClick={onMarkAllRead} style={{ fontFamily: FONT.ui, fontSize: 11, color: COLORS.accent, cursor: "pointer", marginRight: 10 }}>Mark all read</span>
|
||||
<span
|
||||
title="Clear all events"
|
||||
onClick={() => { if (events.length) onClear(); }}
|
||||
style={{ display: "flex", cursor: events.length ? "pointer" : "default", opacity: events.length ? 1 : 0.4 }}
|
||||
>
|
||||
<Trash2 size={14} color={COLORS.stError} aria-label="Clear all" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: 6, marginBottom: 12 }}>
|
||||
|
||||
@@ -86,6 +86,10 @@ export async function markEventsRead(target: MarkReadTarget): Promise<void> {
|
||||
await invoke("mark_read", { target });
|
||||
}
|
||||
|
||||
export async function clearEvents(): Promise<void> {
|
||||
await invoke("clear_events");
|
||||
}
|
||||
|
||||
export type DaemonEvt =
|
||||
| { evt: "exit"; data: { surface_id: string; code: number } }
|
||||
| { evt: "surface_created"; data: { surface_id: string; workspace_id: string } }
|
||||
@@ -96,7 +100,8 @@ export type DaemonEvt =
|
||||
| { evt: "groups_changed"; data: unknown }
|
||||
| { evt: "config_changed"; data: { config: ConfigView } }
|
||||
| { evt: "event"; data: { record: EventRecord } }
|
||||
| { evt: "events_read"; data: { ids: number[] } };
|
||||
| { evt: "events_read"; data: { ids: number[] } }
|
||||
| { evt: "events_cleared"; data: unknown };
|
||||
|
||||
export function onDaemonEvent(handler: (evt: DaemonEvt) => void): Promise<() => void> {
|
||||
return listen<DaemonEvt>("spacesh:evt", (e) => handler(e.payload));
|
||||
|
||||
Reference in New Issue
Block a user