From 22845805fde57de2459399da72c1c6bc6b4dd867 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Mon, 24 Nov 2025 00:21:06 +0800 Subject: [PATCH] feat(stage-tamagotchi): Notice window --- .../src/main/windows/notice/index.ts | 69 +++++++++++++++++++ .../main/windows/notice/rpc/index.electron.ts | 34 +++++++++ apps/stage-tamagotchi/src/shared/eventa.ts | 3 + 3 files changed, 106 insertions(+) create mode 100644 apps/stage-tamagotchi/src/main/windows/notice/index.ts create mode 100644 apps/stage-tamagotchi/src/main/windows/notice/rpc/index.electron.ts diff --git a/apps/stage-tamagotchi/src/main/windows/notice/index.ts b/apps/stage-tamagotchi/src/main/windows/notice/index.ts new file mode 100644 index 000000000..b0e8f5aab --- /dev/null +++ b/apps/stage-tamagotchi/src/main/windows/notice/index.ts @@ -0,0 +1,69 @@ +import type { BrowserWindow } from 'electron' + +import type { RequestWindowPayload } from '../../../shared/eventa' + +import { join, resolve } from 'node:path' + +import { defineInvokeHandler } from '@moeru/eventa' +import { BrowserWindow as ElectronBrowserWindow, shell } from 'electron' + +import icon from '../../../../resources/icon.png?asset' + +import { noticeWindowEventa } from '../../../shared/eventa' +import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location' +import { createReferencedWindowManager } from '../shared/referenced-window' + +export interface NoticeWindowManager { + open: (payload: RequestWindowPayload) => Promise +} + +export function setupNoticeWindowManager(): NoticeWindowManager { + const rendererBase = baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')) + + function createWindow(_id: string): BrowserWindow { + const window = new ElectronBrowserWindow({ + title: 'Notice', + width: 1020, + height: 600, + show: false, + icon, + webPreferences: { + preload: join(__dirname, '../preload/index.mjs'), + sandbox: false, + }, + }) + + window.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + return window + } + + async function loadNoticeRoute(window: BrowserWindow, payload: RequestWindowPayload & { id: string }) { + const routeWithId = `${payload.route}?id=${payload.id}` + await load(window, withHashRoute(rendererBase, routeWithId)) + } + + const manager = createReferencedWindowManager({ + eventa: noticeWindowEventa, + createWindow, + loadRoute: loadNoticeRoute, + }) + + return { + open: async (payload: RequestWindowPayload) => { + const handle = await manager.open(payload) + return await new Promise((resolve) => { + defineInvokeHandler(handle.context, noticeWindowEventa.windowAction, (action) => { + if (!action?.id || action.id !== handle.id) + return + resolve(action.action === 'confirm') + if (!handle.window.isDestroyed()) + handle.window.close() + }) + }) + }, + } +} diff --git a/apps/stage-tamagotchi/src/main/windows/notice/rpc/index.electron.ts b/apps/stage-tamagotchi/src/main/windows/notice/rpc/index.electron.ts new file mode 100644 index 000000000..9afb5ae7c --- /dev/null +++ b/apps/stage-tamagotchi/src/main/windows/notice/rpc/index.electron.ts @@ -0,0 +1,34 @@ +import type { BrowserWindow } from 'electron' + +import type { RequestWindowActionDefault } from '../../../../shared/eventa' + +import { defineInvokeHandler } from '@moeru/eventa' +import { createContext } from '@moeru/eventa/adapters/electron/main' +import { ipcMain } from 'electron' + +import { noticeWindowEventa } from '../../../../shared/eventa' +import { createWindowService } from '../../../services/electron' + +export function setupNoticeWindowInvokes(params: { + window: BrowserWindow + onAction: (payload: { id?: string, action: RequestWindowActionDefault }) => void + onPageMounted: () => { id: string, type?: string, payload?: Record } | undefined + onPageUnmounted: () => void +}) { + // TODO: once we refactored eventa to support window-namespaced contexts, + // we can remove the setMaxListeners call below since eventa will be able to dispatch and + // manage events within eventa's context system. + ipcMain.setMaxListeners(0) + + const { context } = createContext(ipcMain, params.window) + + createWindowService({ context, window: params.window }) + + defineInvokeHandler(context, noticeWindowEventa.windowAction, (payload) => { + params.onAction({ id: payload?.id, action: payload?.action ?? 'close' }) + }) + defineInvokeHandler(context, noticeWindowEventa.pageMounted, () => params.onPageMounted()) + defineInvokeHandler(context, noticeWindowEventa.pageUnmounted, () => params.onPageUnmounted()) + + return { context } +} diff --git a/apps/stage-tamagotchi/src/shared/eventa.ts b/apps/stage-tamagotchi/src/shared/eventa.ts index 1c84f3e82..ea5379384 100644 --- a/apps/stage-tamagotchi/src/shared/eventa.ts +++ b/apps/stage-tamagotchi/src/shared/eventa.ts @@ -35,6 +35,9 @@ export function createRequestWindowEventa(namespace: string) { } } +// Notice window events built from generic factory +export const noticeWindowEventa = createRequestWindowEventa('notice') + // Widgets / Adhoc window events export interface WidgetsAddPayload { id?: string