prototype with two windows
This commit is contained in:
@@ -68,6 +68,14 @@ const config: ForgeConfig = {
|
|||||||
name: 'main_window',
|
name: 'main_window',
|
||||||
config: 'vite.renderer.config.mts',
|
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
|
// 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;
|
maximize: () => void;
|
||||||
fullscreen: () => void;
|
fullscreen: () => void;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
|
openAutoplay: () => void;
|
||||||
|
closeAutoplay: () => void;
|
||||||
|
openHuddle: () => void;
|
||||||
|
closeHuddle: () => void;
|
||||||
};
|
};
|
||||||
electronLink: {
|
electronLink: {
|
||||||
fetchMetadata: (url: string) => Promise<LinkMetadata | null>;
|
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 { useNavigate, useParams } from "react-router-dom";
|
||||||
import { List, LayoutGrid } from "lucide-react";
|
import { List, LayoutGrid } from "lucide-react";
|
||||||
import { particlePath } from "@/lib/particle-path";
|
import { particlePath } from "@/lib/particle-path";
|
||||||
@@ -25,6 +25,11 @@ export default function NetworkRoot() {
|
|||||||
const viewMode = useViewModeStore((s) => s.viewMode);
|
const viewMode = useViewModeStore((s) => s.viewMode);
|
||||||
const setViewMode = useViewModeStore((s) => s.setViewMode);
|
const setViewMode = useViewModeStore((s) => s.setViewMode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.electronWindow.openAutoplay();
|
||||||
|
window.electronWindow.openHuddle();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { streams } = useStreamParticles(path);
|
const { streams } = useStreamParticles(path);
|
||||||
const userId = useAuthStore((s) => s.user?.id);
|
const userId = useAuthStore((s) => s.user?.id);
|
||||||
useDockBadge(streams, userId);
|
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'));
|
app.dock?.setIcon(path.join(__dirname, '../../assets/icon.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let autoplayWindow: BrowserWindow | null = null;
|
||||||
|
let huddleWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
const createWindow = () => {
|
const createWindow = () => {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
const mainWindow = new BrowserWindow({
|
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
|
// Window control IPC handlers
|
||||||
ipcMain.on('window:minimize', (event) => {
|
ipcMain.on('window:minimize', (event) => {
|
||||||
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
||||||
@@ -66,6 +131,20 @@ ipcMain.on('window:fullscreen', (event) => {
|
|||||||
if (win) win.setFullScreen(!win.isFullScreen());
|
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 ---
|
// --- Link metadata ---
|
||||||
|
|
||||||
const metadataCache = new Map<string, LinkMetadata>();
|
const metadataCache = new Map<string, LinkMetadata>();
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ contextBridge.exposeInMainWorld('electronWindow', {
|
|||||||
maximize: () => ipcRenderer.send('window:maximize'),
|
maximize: () => ipcRenderer.send('window:maximize'),
|
||||||
fullscreen: () => ipcRenderer.send('window:fullscreen'),
|
fullscreen: () => ipcRenderer.send('window:fullscreen'),
|
||||||
close: () => ipcRenderer.send('window:close'),
|
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', {
|
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