This commit is contained in:
Timothy Jaeryang Baek 2025-01-11 19:36:23 -08:00
parent e91a8ab4a7
commit c5f5681f94
6 changed files with 117 additions and 93 deletions

View File

@ -1,6 +1,8 @@
import {
app,
nativeImage,
desktopCapturer,
session,
Tray,
Menu,
MenuItem,
@ -52,14 +54,14 @@ if (!gotTheLock) {
let mainWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let SERVER_URL = null;
const loadDefaultView = () => {
// Load index.html or dev server URL
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
} else {
mainWindow.loadFile(
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
);
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
}
};
@ -83,15 +85,54 @@ if (!gotTheLock) {
});
mainWindow.setIcon(path.join(__dirname, 'assets/icon.png'));
// Enables navigator.mediaDevices.getUserMedia API. See https://www.electronjs.org/docs/latest/api/desktop-capturer
session.defaultSession.setDisplayMediaRequestHandler(
(request, callback) => {
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
// Grant access to the first screen found.
callback({ video: sources[0], audio: 'loopback' });
});
},
{ useSystemPicker: true }
);
loadDefaultView();
if (!app.isPackaged) {
mainWindow.webContents.openDevTools();
}
if (validateInstallation()) {
const serverUrl = await startServer();
mainWindow.loadURL(serverUrl);
}
// Wait for the renderer to finish loading
mainWindow.webContents.once('did-finish-load', async () => {
console.log('Renderer finished loading');
// Check installation and start the server
if (validateInstallation()) {
try {
SERVER_URL = await startServer();
console.log('Server URL:', SERVER_URL);
// Send the server URL to the renderer
mainWindow.webContents.send('main:data', {
type: 'server:url',
data: SERVER_URL
});
} catch (error) {
console.error('Failed to start server:', error);
// Send an error message if the server fails to start
mainWindow.webContents.send('main:data', {
type: 'server:error',
data: 'Failed to start the server'
});
}
} else {
// No valid installation, send fallback info
mainWindow.webContents.send('main:data', {
type: 'server:url',
data: null
});
}
});
globalShortcut.register('Alt+CommandOrControl+O', () => {
mainWindow?.show();
@ -101,12 +142,7 @@ if (!gotTheLock) {
});
const defaultMenu = Menu.getApplicationMenu();
console.log(defaultMenu);
// Convert the default menu to a template we can modify
let menuTemplate = defaultMenu ? defaultMenu.items.map((item) => item) : [];
// Add your own custom menu items
menuTemplate.push({
label: 'Action',
submenu: [
@ -119,8 +155,6 @@ if (!gotTheLock) {
}
]
});
// Build the updated menu and set it as the application menu
const updatedMenu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(updatedMenu);
@ -179,6 +213,10 @@ if (!gotTheLock) {
stopAllServers();
});
ipcMain.handle('server:url', async (event) => {
return SERVER_URL;
});
ipcMain.handle('load-webui', async (event, arg) => {
console.log(arg); // prints "ping"
mainWindow.loadURL('http://localhost:8080');

View File

@ -1,4 +1,4 @@
import { ipcRenderer, contextBridge } from 'electron';
import { ipcRenderer, contextBridge, desktopCapturer } from 'electron';
const isLocalSource = () => {
// Check if the execution environment is local
@ -19,8 +19,8 @@ window.addEventListener('DOMContentLoaded', () => {
// Forward the message to the renderer using window.postMessage
window.postMessage(
{
type: `electron:${data.type}`,
data: data
...data,
type: `electron:${data.type}`
},
window.location.origin
);
@ -35,9 +35,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
installPackage: async () => {
if (!isLocalSource()) {
throw new Error(
'Access restricted: This operation is only allowed in a local environment.'
);
throw new Error('Access restricted: This operation is only allowed in a local environment.');
}
await ipcRenderer.invoke('install');
@ -45,9 +43,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
removePackage: async () => {
if (!isLocalSource()) {
throw new Error(
'Access restricted: This operation is only allowed in a local environment.'
);
throw new Error('Access restricted: This operation is only allowed in a local environment.');
}
await ipcRenderer.invoke('remove');
@ -55,9 +51,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
startServer: async () => {
if (!isLocalSource()) {
throw new Error(
'Access restricted: This operation is only allowed in a local environment.'
);
throw new Error('Access restricted: This operation is only allowed in a local environment.');
}
await ipcRenderer.invoke('server:start');
@ -65,11 +59,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
stopServer: async () => {
if (!isLocalSource()) {
throw new Error(
'Access restricted: This operation is only allowed in a local environment.'
);
throw new Error('Access restricted: This operation is only allowed in a local environment.');
}
await ipcRenderer.invoke('server:stop');
},
getServerUrl: async () => {
return await ipcRenderer.invoke('server:url');
}
});

View File

@ -1,7 +1,40 @@
<script lang="ts">
import { onMount } from 'svelte';
import { serverUrl } from './lib/stores.ts';
import Main from './lib/components/Main.svelte';
onMount(() => {});
onMount(async () => {
window.addEventListener('message', (event) => {
// Ensure the message is coming from a trusted origin
if (event.origin !== window.location.origin) {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
// Check the type of the message
if (event.data && event.data.type && event.data.type.startsWith('electron:')) {
console.log('Received message:', event.data);
// Perform actions based on the `type` or the `data`
switch (event.data.type) {
case 'electron:server:url':
console.log('Setting server URL:', event.data.data);
// Set the server URL
serverUrl.set(event.data.data);
break;
default:
console.warn('Unhandled message type:', event.data.type);
}
}
});
if (!$serverUrl) {
const url = await window.electronAPI.getServerUrl();
serverUrl.set(url);
}
});
</script>
<main class="w-screen h-screen bg-gray-900">

View File

@ -5,7 +5,7 @@
let selected = 'home';
</script>
<div class="min-w-18 bg-gray-950 flex gap-2.5 flex-col pt-9.5">
<div class="min-w-18 bg-black flex gap-2.5 flex-col pt-9.5">
<div class="flex justify-center relative">
{#if selected === 'home'}
<div class="absolute top-0 left-0 flex h-full">
@ -32,7 +32,7 @@
</Tooltip>
</div>
<div class="border-t border-gray-900 mx-3"></div>
<!-- <div class="border-t border-gray-900 mx-3"></div> -->
<!-- <div class="flex justify-center relative group">
{#if selected === ""}
@ -56,9 +56,9 @@
</button>
</div> -->
<div class="flex justify-center relative group text-gray-400">
<!-- <div class="flex justify-center relative group text-gray-400">
<button class=" cursor-pointer p-2" onclick={() => {}}>
<Plus className="size-5" strokeWidth="2" />
</button>
</div>
</div> -->
</div>

View File

@ -1,75 +1,29 @@
<script lang="ts">
import { onMount } from 'svelte';
import { serverUrl } from '../stores.ts';
import Spinner from './common/Spinner.svelte';
import ListView from './ListView.svelte';
</script>
<div class="flex flex-row w-full h-full relative dark:text-gray-100">
<div class="absolute top-0 left-0 w-full h-6 bg-transparent draggable"></div>
<div class="absolute top-0 left-0 w-full h-7 bg-transparent draggable"></div>
<ListView />
<div class="flex-1 w-full flex justify-center">
<div class="my-auto flex flex-col max-w-xs w-full">
<div class=" flex justify-center mb-3">
<!-- <img
src="./assets/images/splash.png"
class=" size-24 dark:invert"
alt="hero"
/> -->
{#if !$serverUrl}
<div class="m-auto">
<Spinner className="size-5" />
</div>
<!-- <div class=" text-2xl text-gray-50 font-secondary">Install Open WebUI</div> -->
<div class=" text-gray-500 hover:text-white transition">
<div class="flex justify-center items-center gap-2">
<div>Loading...</div>
<div>
<Spinner className="size-4" />
</div>
</div>
</div>
<!-- <button
class=" hover:text-white transition font-medium cursor-pointer"
onclick={() => {
console.log("install clicked");
if (window?.electronAPI) {
window.electronAPI.installPackage();
}
}}
>
<div class="flex justify-center items-center gap-2">
<div>Install</div>
<div>
<Spinner className="size-4" />
</div>
</div>
</button> -->
<!--
<button
class=" text-gray-100 hover:text-white transition font-medium cursor-pointer"
onclick={() => {
console.log("start clicked");
if (window?.electronAPI) {
window.electronAPI.startServer();
}
}}>Start Open WebUI</button
>
<button
class=" text-gray-100 hover:text-white transition font-medium cursor-pointer"
onclick={() => {
console.log("stop clicked");
if (window?.electronAPI) {
window.electronAPI.stopServer();
}
}}>Stop Open WebUI</button
> -->
</div>
{:else}
<iframe
src={$serverUrl}
class="w-full h-full"
allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; display-capture; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; navigation-override; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-script; sync-xhr; usb; screen-wake-lock; web-share; unoptimized-images; unsized-media; xr-spatial-tracking"
/>
{/if}
</div>
</div>

3
src/render/lib/stores.ts Normal file
View File

@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const serverUrl = writable(null);