prototype with two windows
This commit is contained in:
@@ -68,6 +68,14 @@ const config: ForgeConfig = {
|
||||
name: 'main_window',
|
||||
config: 'vite.renderer.config.mts',
|
||||
},
|
||||
{
|
||||
name: 'autoplay_window',
|
||||
config: 'vite.autoplay.config.mts',
|
||||
},
|
||||
{
|
||||
name: 'huddle_window',
|
||||
config: 'vite.huddle.config.mts',
|
||||
},
|
||||
],
|
||||
}),
|
||||
// Fuses are used to enable/disable various Electron functionality
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function AutoplayApp() {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh', color: 'white', background: '#1a1a1a' }}>
|
||||
<p>Autoplay Window</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>llink - Autoplay</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./renderer.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { AutoplayApp } from '@/autoplay/AutoplayApp';
|
||||
import '@/styles/globals.css';
|
||||
|
||||
const root = createRoot(document.getElementById('root')!);
|
||||
root.render(<AutoplayApp />);
|
||||
Vendored
+4
@@ -7,6 +7,10 @@ declare global {
|
||||
maximize: () => void;
|
||||
fullscreen: () => void;
|
||||
close: () => void;
|
||||
openAutoplay: () => void;
|
||||
closeAutoplay: () => void;
|
||||
openHuddle: () => void;
|
||||
closeHuddle: () => void;
|
||||
};
|
||||
electronLink: {
|
||||
fetchMetadata: (url: string) => Promise<LinkMetadata | null>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { List, LayoutGrid } from "lucide-react";
|
||||
import { particlePath } from "@/lib/particle-path";
|
||||
@@ -25,6 +25,11 @@ export default function NetworkRoot() {
|
||||
const viewMode = useViewModeStore((s) => s.viewMode);
|
||||
const setViewMode = useViewModeStore((s) => s.setViewMode);
|
||||
|
||||
useEffect(() => {
|
||||
window.electronWindow.openAutoplay();
|
||||
window.electronWindow.openHuddle();
|
||||
}, []);
|
||||
|
||||
const { streams } = useStreamParticles(path);
|
||||
const userId = useAuthStore((s) => s.user?.id);
|
||||
useDockBadge(streams, userId);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Mic, MicOff, Video, VideoOff, PhoneOff } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function HuddleApp() {
|
||||
const [micOn, setMicOn] = useState(true);
|
||||
const [videoOn, setVideoOn] = useState(true);
|
||||
|
||||
const handleLeave = () => {
|
||||
window.electronWindow.closeHuddle();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col bg-background text-foreground">
|
||||
{/* Video grid */}
|
||||
<div className="flex flex-1 items-center justify-center gap-4 p-6">
|
||||
<div className="bg-muted flex aspect-video w-full max-w-md items-center justify-center rounded-xl">
|
||||
<span className="text-muted-foreground text-sm">You</span>
|
||||
</div>
|
||||
<div className="bg-muted flex aspect-video w-full max-w-md items-center justify-center rounded-xl">
|
||||
<span className="text-muted-foreground text-sm">Participant</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls bar */}
|
||||
<div className="flex items-center justify-center gap-3 border-t px-6 py-4">
|
||||
<button
|
||||
className="rounded-full bg-muted p-3 hover:bg-muted/80 transition-colors"
|
||||
onClick={() => setMicOn(!micOn)}
|
||||
>
|
||||
{micOn ? <Mic className="size-5" /> : <MicOff className="size-5 text-destructive" />}
|
||||
</button>
|
||||
<button
|
||||
className="rounded-full bg-muted p-3 hover:bg-muted/80 transition-colors"
|
||||
onClick={() => setVideoOn(!videoOn)}
|
||||
>
|
||||
{videoOn ? <Video className="size-5" /> : <VideoOff className="size-5 text-destructive" />}
|
||||
</button>
|
||||
<button
|
||||
className="rounded-full bg-destructive p-3 text-destructive-foreground hover:bg-destructive/90 transition-colors"
|
||||
onClick={handleLeave}
|
||||
>
|
||||
<PhoneOff className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>llink - Huddle</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./renderer.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { HuddleApp } from '@/huddle/HuddleApp';
|
||||
import '@/styles/globals.css';
|
||||
|
||||
const root = createRoot(document.getElementById('root')!);
|
||||
root.render(<HuddleApp />);
|
||||
@@ -22,6 +22,9 @@ if (process.platform === 'darwin' && !app.isPackaged) {
|
||||
app.dock?.setIcon(path.join(__dirname, '../../assets/icon.png'));
|
||||
}
|
||||
|
||||
let autoplayWindow: BrowserWindow | null = null;
|
||||
let huddleWindow: BrowserWindow | null = null;
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
@@ -46,6 +49,68 @@ const createWindow = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const createAutoplayWindow = () => {
|
||||
if (autoplayWindow) {
|
||||
autoplayWindow.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
autoplayWindow = new BrowserWindow({
|
||||
width: 300,
|
||||
height: 100,
|
||||
resizable: false,
|
||||
frame: false,
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
},
|
||||
});
|
||||
|
||||
if (AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL) {
|
||||
autoplayWindow.loadURL(AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL);
|
||||
} else {
|
||||
autoplayWindow.loadFile(
|
||||
path.join(__dirname, `../renderer/${AUTOPLAY_WINDOW_VITE_NAME}/index.html`),
|
||||
);
|
||||
}
|
||||
|
||||
autoplayWindow.on('closed', () => {
|
||||
autoplayWindow = null;
|
||||
});
|
||||
};
|
||||
|
||||
const createHuddleWindow = () => {
|
||||
if (huddleWindow) {
|
||||
huddleWindow.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
huddleWindow = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 768,
|
||||
resizable: true,
|
||||
fullscreenable: true,
|
||||
frame: true,
|
||||
title: 'Huddle',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
},
|
||||
});
|
||||
|
||||
if (HUDDLE_WINDOW_VITE_DEV_SERVER_URL) {
|
||||
huddleWindow.loadURL(HUDDLE_WINDOW_VITE_DEV_SERVER_URL);
|
||||
} else {
|
||||
huddleWindow.loadFile(
|
||||
path.join(__dirname, `../renderer/${HUDDLE_WINDOW_VITE_NAME}/index.html`),
|
||||
);
|
||||
}
|
||||
|
||||
huddleWindow.on('closed', () => {
|
||||
huddleWindow = null;
|
||||
});
|
||||
};
|
||||
|
||||
// Window control IPC handlers
|
||||
ipcMain.on('window:minimize', (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
||||
@@ -66,6 +131,20 @@ ipcMain.on('window:fullscreen', (event) => {
|
||||
if (win) win.setFullScreen(!win.isFullScreen());
|
||||
});
|
||||
|
||||
// Secondary window IPC handlers
|
||||
ipcMain.on('window:open-autoplay', () => {
|
||||
createAutoplayWindow();
|
||||
});
|
||||
ipcMain.on('window:close-autoplay', () => {
|
||||
autoplayWindow?.close();
|
||||
});
|
||||
ipcMain.on('window:open-huddle', () => {
|
||||
createHuddleWindow();
|
||||
});
|
||||
ipcMain.on('window:close-huddle', () => {
|
||||
huddleWindow?.close();
|
||||
});
|
||||
|
||||
// --- Link metadata ---
|
||||
|
||||
const metadataCache = new Map<string, LinkMetadata>();
|
||||
|
||||
@@ -7,6 +7,10 @@ contextBridge.exposeInMainWorld('electronWindow', {
|
||||
maximize: () => ipcRenderer.send('window:maximize'),
|
||||
fullscreen: () => ipcRenderer.send('window:fullscreen'),
|
||||
close: () => ipcRenderer.send('window:close'),
|
||||
openAutoplay: () => ipcRenderer.send('window:open-autoplay'),
|
||||
closeAutoplay: () => ipcRenderer.send('window:close-autoplay'),
|
||||
openHuddle: () => ipcRenderer.send('window:open-huddle'),
|
||||
closeHuddle: () => ipcRenderer.send('window:close-huddle'),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('electronLink', {
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Electron Forge VitePlugin injects these globals at dev/build time.
|
||||
// Each renderer entry in forge.config.ts gets a pair of constants.
|
||||
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
|
||||
declare const MAIN_WINDOW_VITE_NAME: string;
|
||||
declare const AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
|
||||
declare const AUTOPLAY_WINDOW_VITE_NAME: string;
|
||||
declare const HUDDLE_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
|
||||
declare const HUDDLE_WINDOW_VITE_NAME: string;
|
||||
@@ -0,0 +1,20 @@
|
||||
import path from "path"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config
|
||||
export default defineConfig({
|
||||
root: path.resolve(__dirname, './src/autoplay_window'),
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
allow: [path.resolve(__dirname)],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import path from "path"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config
|
||||
export default defineConfig({
|
||||
root: path.resolve(__dirname, './src/huddle_window'),
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
allow: [path.resolve(__dirname)],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user