diff --git a/apps/stage-tamagotchi/src/main/index.ts b/apps/stage-tamagotchi/src/main/index.ts index e2b6715a2..e09d7ddd8 100644 --- a/apps/stage-tamagotchi/src/main/index.ts +++ b/apps/stage-tamagotchi/src/main/index.ts @@ -19,7 +19,7 @@ import { emitAppBeforeQuit, emitAppReady, emitAppWindowAllClosed, onAppBeforeQui import { setElectronMainDirname } from './libs/electron/location' import { setupInlayWindow } from './windows/inlay' import { setupMainWindow } from './windows/main' -import { setupSettingsWindow } from './windows/settings' +import { setupSettingsWindowReusableFunc } from './windows/settings' import { toggleWindowShow } from './windows/shared/window' setElectronMainDirname(__dirname) @@ -32,7 +32,10 @@ const log = useLogg('main').useGlobalConfig() app.dock?.setIcon(icon) electronApp.setAppUserModelId('ai.moeru.airi') -function setupTray(params: { mainWindow: BrowserWindow }): void { +function setupTray(params: { + mainWindow: BrowserWindow + settingsWindow: () => Promise +}): void { once(() => { const appTray = new Tray(nativeImage.createFromPath(macOSTrayIcon).resize({ width: 16 })) onAppBeforeQuit(() => appTray.destroy()) @@ -41,7 +44,7 @@ function setupTray(params: { mainWindow: BrowserWindow }): void { { label: 'Show', click: () => toggleWindowShow(params.mainWindow) }, { label: 'Inlay', click: () => setupInlayWindow() }, { type: 'separator' }, - { label: 'Settings', click: () => setupSettingsWindow() }, + { label: 'Settings', click: () => params.settingsWindow().then(window => toggleWindowShow(window)) }, { type: 'separator' }, { label: 'Quit', click: () => app.quit() }, ]) @@ -92,9 +95,10 @@ app.whenReady().then(async () => { await setupProjectAIRIServerRuntime() injecta.setLogger(createLoggLogger()) - injecta.provide('mainWindow', async () => await setupMainWindow()) - injecta.provide<{ mainWindow: BrowserWindow }>('tray', { dependsOn: { mainWindow: 'mainWindow' }, build: async ({ dependsOn }) => setupTray({ mainWindow: dependsOn.mainWindow }) }) - injecta.invoke({ dependsOn: { mainWindow: 'mainWindow', tray: 'tray' }, callback: noop }) + injecta.provide('windows:settings', () => setupSettingsWindowReusableFunc()) + injecta.provide<{ settingsWindow: () => Promise }>('windows:main', { dependsOn: { settingsWindow: 'windows:settings' }, build: async ({ dependsOn }) => setupMainWindow(dependsOn) }) + injecta.provide<{ mainWindow: BrowserWindow, settingsWindow: () => Promise }>('tray', { dependsOn: { mainWindow: 'windows:main', settingsWindow: 'windows:settings' }, build: async ({ dependsOn }) => setupTray(dependsOn) }) + injecta.invoke({ dependsOn: { mainWindow: 'windows:main', tray: 'tray' }, callback: noop }) injecta.start() // Lifecycle diff --git a/apps/stage-tamagotchi/src/main/libs/electron/window-manager/index.ts b/apps/stage-tamagotchi/src/main/libs/electron/window-manager/index.ts new file mode 100644 index 000000000..e2e28d4e1 --- /dev/null +++ b/apps/stage-tamagotchi/src/main/libs/electron/window-manager/index.ts @@ -0,0 +1 @@ +export * from './reusable' diff --git a/apps/stage-tamagotchi/src/main/libs/electron/window-manager/reusable.ts b/apps/stage-tamagotchi/src/main/libs/electron/window-manager/reusable.ts new file mode 100644 index 000000000..ef45038d4 --- /dev/null +++ b/apps/stage-tamagotchi/src/main/libs/electron/window-manager/reusable.ts @@ -0,0 +1,20 @@ +import type { BrowserWindow } from 'electron' + +export function createReusableWindow(setupFn: () => BrowserWindow | Promise): { getWindow: () => Promise } { + let window: BrowserWindow | undefined + + return { + getWindow: async () => { + if (!window) { + window = await setupFn() + return window + } + if (window.isDestroyed()) { + window = await setupFn() + return window + } + + return window + }, + } +} diff --git a/apps/stage-tamagotchi/src/main/windows/inlay/index.ts b/apps/stage-tamagotchi/src/main/windows/inlay/index.ts index 0a9daaa2d..3b4626704 100644 --- a/apps/stage-tamagotchi/src/main/windows/inlay/index.ts +++ b/apps/stage-tamagotchi/src/main/windows/inlay/index.ts @@ -1,17 +1,14 @@ -import { dirname, join, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' +import { join, resolve } from 'node:path' import { BrowserWindow, shell } from 'electron' import { isMacOS } from 'std-env' import icon from '../../../../resources/icon.png?asset' -import { baseUrl, load, withHashRoute } from '../../libs/electron/location' +import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location' import { currentDisplayBounds, mapForBreakpoints, resolutionBreakpoints, widthFrom } from '../shared/display' import { spotlightLikeWindowConfig } from '../shared/window' -const __dirname = dirname(fileURLToPath(import.meta.url)) - export async function setupInlayWindow() { const window = new BrowserWindow({ title: 'Inlay', @@ -20,7 +17,7 @@ export async function setupInlayWindow() { show: false, icon, webPreferences: { - preload: join(dirname(fileURLToPath(import.meta.url)), '../preload/index.mjs'), + preload: join(__dirname, '../preload/index.mjs'), sandbox: false, }, ...spotlightLikeWindowConfig(), @@ -63,7 +60,7 @@ export async function setupInlayWindow() { return { action: 'deny' } }) - await load(window, withHashRoute(baseUrl(resolve(__dirname, '..', '..', '..', 'renderer')), '/inlay')) + await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/inlay')) return window } diff --git a/apps/stage-tamagotchi/src/main/windows/main/rpc/index.electron.ts b/apps/stage-tamagotchi/src/main/windows/main/rpc/index.electron.ts index 50d9af777..afd8f99ef 100644 --- a/apps/stage-tamagotchi/src/main/windows/main/rpc/index.electron.ts +++ b/apps/stage-tamagotchi/src/main/windows/main/rpc/index.electron.ts @@ -1,13 +1,17 @@ import type { BrowserWindow } from 'electron' +import { defineInvokeHandler } from '@unbird/eventa' import { createContext } from '@unbird/eventa/adapters/electron/main' import { ipcMain } from 'electron' +import { electronOpenMainDevtools, electronOpenSettings } from '../../../../shared/eventa' import { createFadeOnHoverService } from '../../../services/fade-on-hover' +import { toggleWindowShow } from '../../shared' -export function setupAppInvokeHandlers(window: BrowserWindow) { - const eventaContext = createContext(ipcMain, window) - const { context } = eventaContext +export function setupMainWindowElectronInvokes(params: { window: BrowserWindow, settingsWindow: () => Promise }) { + const { context } = createContext(ipcMain, params.window) createFadeOnHoverService(context) + defineInvokeHandler(context, electronOpenMainDevtools, () => params.window.webContents.openDevTools({ mode: 'detach' })) + defineInvokeHandler(context, electronOpenSettings, async () => toggleWindowShow(await params.settingsWindow())) } diff --git a/apps/stage-tamagotchi/src/main/windows/manager/index.ts b/apps/stage-tamagotchi/src/main/windows/manager/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/stage-tamagotchi/src/main/windows/settings/index.ts b/apps/stage-tamagotchi/src/main/windows/settings/index.ts index 5c9682214..65179338c 100644 --- a/apps/stage-tamagotchi/src/main/windows/settings/index.ts +++ b/apps/stage-tamagotchi/src/main/windows/settings/index.ts @@ -5,27 +5,32 @@ import { BrowserWindow, shell } from 'electron' import icon from '../../../../resources/icon.png?asset' import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location' +import { createReusableWindow } from '../../libs/electron/window-manager' +import { setupSettingsWindowInvokes } from './rpc/index.electron' export function setupSettingsWindowReusableFunc() { - const window = new BrowserWindow({ - title: 'Settings', - width: 600.0, - height: 800.0, - show: false, - icon, - webPreferences: { - preload: join(dirname(fileURLToPath(import.meta.url)), '../preload/index.mjs'), - sandbox: false, - }, - }) + return createReusableWindow(async () => { + const window = new BrowserWindow({ + title: 'Settings', + width: 600.0, + height: 800.0, + show: false, + icon, + webPreferences: { + preload: join(__dirname, '../preload/index.mjs'), + sandbox: false, + }, + }) - window.on('ready-to-show', () => window.show()) - window.webContents.setWindowOpenHandler((details) => { - shell.openExternal(details.url) - return { action: 'deny' } - }) + window.on('ready-to-show', () => window.show()) + window.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) - await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/settings')) + await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/settings')) + await setupSettingsWindowInvokes({ settingsWindow: window }) - return window + return window + }).getWindow } diff --git a/apps/stage-tamagotchi/src/main/windows/settings/rpc/index.electron.ts b/apps/stage-tamagotchi/src/main/windows/settings/rpc/index.electron.ts new file mode 100644 index 000000000..84192f5a0 --- /dev/null +++ b/apps/stage-tamagotchi/src/main/windows/settings/rpc/index.electron.ts @@ -0,0 +1,13 @@ +import type { BrowserWindow } from 'electron' + +import { defineInvokeHandler } from '@unbird/eventa' +import { createContext } from '@unbird/eventa/adapters/electron/main' +import { ipcMain } from 'electron' + +import { electronOpenSettingsDevtools } from '../../../../shared/eventa' + +export async function setupSettingsWindowInvokes(params: { settingsWindow: BrowserWindow }) { + const { context } = createContext(ipcMain, params.settingsWindow) + + defineInvokeHandler(context, electronOpenSettingsDevtools, async () => params.settingsWindow.webContents.openDevTools({ mode: 'detach' })) +} diff --git a/apps/stage-tamagotchi/src/renderer/components/Widgets/ControlsIsland/index.vue b/apps/stage-tamagotchi/src/renderer/components/Widgets/ControlsIsland/index.vue index d329fe334..1c5b0f533 100644 --- a/apps/stage-tamagotchi/src/renderer/components/Widgets/ControlsIsland/index.vue +++ b/apps/stage-tamagotchi/src/renderer/components/Widgets/ControlsIsland/index.vue @@ -1,21 +1,31 @@