Revert "fix: split LiveShell into Astro layout + individual islands"
This reverts commit 6e1757410a.
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
|||||||
|
[submodule "vendor/discord.js-selfbot-v13"]
|
||||||
|
path = vendor/discord.js-selfbot-v13
|
||||||
|
url = https://github.com/MythEclipse/discord.js-selfbot-v13.git
|
||||||
|
|
||||||
|
[submodule "vendor/discord-video-stream"]
|
||||||
|
path = vendor/discord-video-stream
|
||||||
|
url = https://github.com/MythEclipse/discord-video-stream.git
|
||||||
|
[submodule "vendor/drizzle-orm"]
|
||||||
|
path = vendor/drizzle-orm
|
||||||
|
url = https://github.com/MythEclipse/drizzle-orm.git
|
||||||
|
[submodule "vendor/better-sqlite3"]
|
||||||
|
path = vendor/better-sqlite3
|
||||||
|
url = https://github.com/MythEclipse/better-sqlite3.git
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// ─── LiveShell.tsx — Page shell for /live ────────────────────────────────────
|
||||||
|
// Composes Sidebar, Header, ParticleBackground, and the four live islands
|
||||||
|
// (VoiceControls, ActiveSpeakers, AudioVisualizer, NowPlaying) into a full-page
|
||||||
|
// responsive layout.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
getGuilds,
|
||||||
|
getVoiceChannels,
|
||||||
|
getVoiceStatus,
|
||||||
|
} from "../shared/api/client.js";
|
||||||
|
import { Header } from "../shared/components/Header.js";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../shared/components/index.js";
|
||||||
|
import { ParticleBackground } from "../shared/components/particles/ParticleBackground.js";
|
||||||
|
import { Sidebar } from "../shared/components/Sidebar.js";
|
||||||
|
import { useTheme } from "../shared/hooks/useTheme.js";
|
||||||
|
import type { Channel, Guild } from "../shared/types/guild.js";
|
||||||
|
import type { VoiceStatus } from "../shared/types/voice.js";
|
||||||
|
import { useVoiceStore } from "../stores/voice-store.js";
|
||||||
|
import ActiveSpeakers from "./ActiveSpeakers.js";
|
||||||
|
import AudioVisualizer from "./AudioVisualizer.js";
|
||||||
|
import NowPlaying from "./NowPlaying.js";
|
||||||
|
import VoiceControls from "./VoiceControls.js";
|
||||||
|
|
||||||
|
const emptyVoiceStatus: VoiceStatus = {
|
||||||
|
connected: false,
|
||||||
|
activeGuildId: null,
|
||||||
|
activeChannelId: null,
|
||||||
|
activeChannelName: null,
|
||||||
|
connections: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LiveShell() {
|
||||||
|
const { mode, isDark, toggle: toggleTheme } = useTheme();
|
||||||
|
const [voiceStatus, setVoiceStatus] = useState<VoiceStatus>(emptyVoiceStatus);
|
||||||
|
const [guilds, setGuilds] = useState<Guild[]>([]);
|
||||||
|
const [voiceChannels, setVoiceChannels] = useState<Channel[]>([]);
|
||||||
|
|
||||||
|
// Watch store for guild changes so we can fetch voice channels
|
||||||
|
const guildId = useVoiceStore((state) => state.guildId);
|
||||||
|
|
||||||
|
// Fetch initial data on mount
|
||||||
|
useEffect(() => {
|
||||||
|
getVoiceStatus()
|
||||||
|
.then(setVoiceStatus)
|
||||||
|
.catch(() => {
|
||||||
|
/* offline — leave default */
|
||||||
|
});
|
||||||
|
getGuilds()
|
||||||
|
.then(setGuilds)
|
||||||
|
.catch(() => {
|
||||||
|
/* offline — leave default */
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Refetch voice channels when guild selection changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (guildId) {
|
||||||
|
getVoiceChannels(guildId)
|
||||||
|
.then(setVoiceChannels)
|
||||||
|
.catch(() => setVoiceChannels([]));
|
||||||
|
} else {
|
||||||
|
setVoiceChannels([]);
|
||||||
|
}
|
||||||
|
}, [guildId]);
|
||||||
|
|
||||||
|
const handleTabChange = useCallback((tab: string) => {
|
||||||
|
if (tab !== "live") {
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen bg-background text-foreground">
|
||||||
|
{/* Background layers */}
|
||||||
|
<ParticleBackground />
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 pointer-events-none grid-pattern opacity-[0.03]"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative flex min-h-screen">
|
||||||
|
<Sidebar
|
||||||
|
activeTab="live"
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
collapsed={false}
|
||||||
|
recentMessages={[]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<main className="flex min-w-0 flex-1 flex-col">
|
||||||
|
<Header
|
||||||
|
activeTab="live"
|
||||||
|
wsStatus="disconnected"
|
||||||
|
voiceStatus={voiceStatus}
|
||||||
|
themeMode={mode}
|
||||||
|
isDark={isDark}
|
||||||
|
onThemeToggle={toggleTheme}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-auto p-4 md:p-6 lg:p-8 pb-16 md:pb-0">
|
||||||
|
{/* Responsive grid: stacks on <1024px */}
|
||||||
|
<div className="grid gap-6 lg:grid-cols-[1fr_320px]">
|
||||||
|
{/* Main content column */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<VoiceControls guilds={guilds} voiceChannels={voiceChannels} />
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-base">Live Audio</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<AudioVisualizer barCount={48} height={32} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<NowPlaying />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar column */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-base">Active Speakers</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ActiveSpeakers />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,47 +1,29 @@
|
|||||||
// ─── VoiceControls.tsx — Voice bridge island ────────────────────────────────
|
// ─── VoiceControls.tsx — Voice bridge island ────────────────────────────────
|
||||||
// Self-contained: fetches guilds/channels internally, reads/writes useVoiceStore,
|
// Self-contained: reads/writes useVoiceStore, calls API for connect/disconnect.
|
||||||
// calls API for connect/disconnect.
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
import { Radio } from "lucide-react";
|
import { Radio } from "lucide-react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import {
|
import { connectVoice, disconnectVoice } from "../shared/api/client.js";
|
||||||
connectVoice,
|
|
||||||
disconnectVoice,
|
|
||||||
getGuilds,
|
|
||||||
getVoiceChannels,
|
|
||||||
} from "../shared/api/client.js";
|
|
||||||
import { Button, Select } from "../shared/components/index.js";
|
import { Button, Select } from "../shared/components/index.js";
|
||||||
import type { Channel, Guild } from "../shared/types/guild.js";
|
import type { Channel, Guild } from "../shared/types/guild.js";
|
||||||
import { useVoiceStore } from "../stores/voice-store.js";
|
import { useVoiceStore } from "../stores/voice-store.js";
|
||||||
|
|
||||||
export default function VoiceControls() {
|
interface VoiceControlsProps {
|
||||||
|
guilds: Guild[];
|
||||||
|
voiceChannels: Channel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VoiceControls({
|
||||||
|
guilds,
|
||||||
|
voiceChannels,
|
||||||
|
}: VoiceControlsProps) {
|
||||||
const connected = useVoiceStore((state) => state.connected);
|
const connected = useVoiceStore((state) => state.connected);
|
||||||
const guildId = useVoiceStore((state) => state.guildId);
|
const guildId = useVoiceStore((state) => state.guildId);
|
||||||
const channelId = useVoiceStore((state) => state.channelId);
|
const channelId = useVoiceStore((state) => state.channelId);
|
||||||
const setGuildChannel = useVoiceStore((state) => state.setGuildChannel);
|
const setGuildChannel = useVoiceStore((state) => state.setGuildChannel);
|
||||||
const setConnected = useVoiceStore((state) => state.setConnected);
|
const setConnected = useVoiceStore((state) => state.setConnected);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [guilds, setGuilds] = useState<Guild[]>([]);
|
|
||||||
const [voiceChannels, setVoiceChannels] = useState<Channel[]>([]);
|
|
||||||
|
|
||||||
// Fetch guilds on mount
|
|
||||||
useEffect(() => {
|
|
||||||
getGuilds()
|
|
||||||
.then(setGuilds)
|
|
||||||
.catch(() => { /* offline */ });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch voice channels when guild selection changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (guildId) {
|
|
||||||
getVoiceChannels(guildId)
|
|
||||||
.then(setVoiceChannels)
|
|
||||||
.catch(() => setVoiceChannels([]));
|
|
||||||
} else {
|
|
||||||
setVoiceChannels([]);
|
|
||||||
}
|
|
||||||
}, [guildId]);
|
|
||||||
|
|
||||||
const handleConnect = useCallback(async () => {
|
const handleConnect = useCallback(async () => {
|
||||||
if (!guildId || !channelId) return;
|
if (!guildId || !channelId) return;
|
||||||
@@ -69,7 +51,7 @@ export default function VoiceControls() {
|
|||||||
}, [setConnected]);
|
}, [setConnected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="glass rounded-xl p-6">
|
||||||
<h3 className="flex items-center gap-2 text-lg font-semibold tracking-tight">
|
<h3 className="flex items-center gap-2 text-lg font-semibold tracking-tight">
|
||||||
<Radio className="h-5 w-5 text-primary" /> Voice Bridge
|
<Radio className="h-5 w-5 text-primary" /> Voice Bridge
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -1,108 +1,14 @@
|
|||||||
---
|
---
|
||||||
// ─── live.astro — Voice & Media live monitoring page ────────────────────────
|
// ─── live.astro — Voice & Media live monitoring page ────────────────────────
|
||||||
// Layout shell (Sidebar, Header) rendered as zero-JS Astro components.
|
// Full-page layout with Sidebar navigation, Header, and the LiveShell React
|
||||||
// Only the truly interactive parts are React islands with client directives.
|
// island that composes all live components (VoiceControls, ActiveSpeakers,
|
||||||
|
// AudioVisualizer, NowPlaying).
|
||||||
|
// Particles and MascotChat are embedded inside the LiveShell island.
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
import Sidebar from "../components/sidebar/Sidebar.astro";
|
import LiveShell from "../islands/LiveShell";
|
||||||
import Header from "../components/header/Header.astro";
|
|
||||||
import Card from "../components/ui/Card.astro";
|
|
||||||
|
|
||||||
// React islands — interactive only
|
|
||||||
import ThemeToggle from "../islands/ThemeToggle";
|
|
||||||
import VoiceControls from "../islands/VoiceControls";
|
|
||||||
import ActiveSpeakers from "../islands/ActiveSpeakers";
|
|
||||||
import AudioVisualizer from "../islands/AudioVisualizer";
|
|
||||||
import NowPlaying from "../islands/NowPlaying";
|
|
||||||
import Particles from "../islands/Particles";
|
|
||||||
import MascotChat from "../islands/MascotChat";
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<div class="live-page-layout">
|
<LiveShell client:only="react" />
|
||||||
<!-- Sidebar — zero JS Astro component -->
|
|
||||||
<Sidebar activeTab="live" />
|
|
||||||
|
|
||||||
<main class="live-main-area">
|
|
||||||
<!-- Header — zero JS, ThemeToggle is a tiny React island -->
|
|
||||||
<Header title="Live">
|
|
||||||
<ThemeToggle slot="actions" client:idle />
|
|
||||||
</Header>
|
|
||||||
|
|
||||||
<div class="live-content p-4 md:p-6 lg:p-8">
|
|
||||||
<!-- Responsive grid: stacks on <1024px -->
|
|
||||||
<div class="live-grid">
|
|
||||||
<!-- Main column -->
|
|
||||||
<div class="live-main-col space-y-4 md:space-y-6">
|
|
||||||
<!-- VoiceControls — core functionality, load immediately -->
|
|
||||||
<Card variant="glass">
|
|
||||||
<h2 class="text-base font-semibold mb-3">Voice Connection</h2>
|
|
||||||
<VoiceControls client:load />
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- AudioVisualizer — deferred, not critical -->
|
|
||||||
<Card>
|
|
||||||
<h2 class="text-base font-semibold mb-3">Live Audio</h2>
|
|
||||||
<AudioVisualizer client:idle barCount={48} height={32} />
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- NowPlaying — deferred -->
|
|
||||||
<Card>
|
|
||||||
<h2 class="text-base font-semibold mb-3">Now Playing</h2>
|
|
||||||
<NowPlaying client:idle />
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar column -->
|
|
||||||
<aside class="live-sidebar-col space-y-4 md:space-y-6">
|
|
||||||
<Card>
|
|
||||||
<h2 class="text-base font-semibold mb-3">Active Speakers</h2>
|
|
||||||
<ActiveSpeakers client:idle />
|
|
||||||
</Card>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Deferred background & mascot -->
|
|
||||||
<Particles client:idle />
|
|
||||||
<MascotChat client:idle />
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style is:global>
|
|
||||||
.live-page-layout {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.live-main-area {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.live-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.live-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.live-grid {
|
|
||||||
grid-template-columns: 1fr 320px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.live-sidebar-col {
|
|
||||||
position: sticky;
|
|
||||||
top: calc(56px + 2rem);
|
|
||||||
align-self: start;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user