Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ const Dashboard = () => {
setWorkspaceModalMode,
setWorkspaceModalPath,
setIsWorkspaceModalOpen,
setIsSettingsModalOpen,
});

const handleSelectWorkspace = useCallback(async () => {
Expand Down
6 changes: 6 additions & 0 deletions src/dashboard/core/hooks/useDashboardBootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type UseDashboardBootstrapArgs = {
setWorkspaceModalMode: (mode: "initial" | "lostSync") => void;
setWorkspaceModalPath: (path: string | null) => void;
setIsWorkspaceModalOpen: (open: boolean) => void;
setIsSettingsModalOpen: (open: boolean) => void;
};

export const useDashboardBootstrap = ({
Expand All @@ -36,6 +37,7 @@ export const useDashboardBootstrap = ({
setWorkspaceModalMode,
setWorkspaceModalPath,
setIsWorkspaceModalOpen,
setIsSettingsModalOpen,
}: UseDashboardBootstrapArgs) => {
useEffect(() => {
const initializeUserData = async () => {
Expand Down Expand Up @@ -138,4 +140,8 @@ export const useDashboardBootstrap = ({
setWorkspaceModalPath(lostPath);
setIsWorkspaceModalOpen(true);
});

useIPCListener("menu:openSettings", () => {
setIsSettingsModalOpen(true);
});
};
2 changes: 2 additions & 0 deletions src/dashboard/core/hooks/useIPC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export const useIPCListener = (
cleanup = messaging.onWorkspaceModulesChanged?.(handler);
} else if (channel === "workspace:lostSync") {
cleanup = messaging.onWorkspaceLostSync?.(handler);
} else if (channel === "menu:openSettings") {
cleanup = messaging.onMenuOpenSettings?.(handler);
} else {
return;
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/mainProcess/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
registerWorkspaceSelectionIpc,
} from "./workspace";
import { isExistingDirectory } from "./pathSafety";
import { registerMenuHandlers, createApplicationMenu } from "./menu";

const getTestProjectDir = (): string | null => {
const raw = process.env.NW_WRLD_TEST_PROJECT_DIR;
Expand All @@ -26,6 +27,9 @@ const getTestProjectDir = (): string | null => {
export function start() {
setupApp();

// Register menu handlers before creating windows
registerMenuHandlers();

registerIpcBridge();
registerSandboxIpc();
registerMessagingIpc({ ipcMain });
Expand All @@ -48,5 +52,8 @@ export function start() {
}
registerActivate({ createWindow });
createWindow(testProjectDir);

// Create application menu after window is ready
createApplicationMenu();
});
}
127 changes: 127 additions & 0 deletions src/main/mainProcess/menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Menu, ipcMain, BrowserWindow } from "electron";

import { state } from "./state";

const { app } = require("electron") as { app: { name: string } };

// Module-level handler functions for menu actions
function openSettingsHandler() {
try {
const dashboard = state.dashboardWindow as BrowserWindow | null;
if (dashboard && !dashboard.isDestroyed()) {
dashboard.show();
dashboard.focus();
dashboard.webContents.send("menu:openSettings");
}
} catch {}
}

function setAspectRatioHandler(aspectRatioId: string) {
try {
ipcMain.emit("dashboard-to-projector", null, {
type: "toggleAspectRatioStyle",
props: { name: aspectRatioId },
});
} catch {}
}

export function registerMenuHandlers(): void {
// Handler to open settings modal
ipcMain.on("menu:openSettings", () => {
openSettingsHandler();
});

// Handler to set aspect ratio
ipcMain.on("menu:setAspectRatio", (_event, aspectRatioId: string) => {
setAspectRatioHandler(aspectRatioId);
});
}

export function createApplicationMenu(): void {
const isMac = process.platform === "darwin";

const template: Electron.MenuItemConstructorOptions[] = [
// App menu (macOS only)
...(isMac
? [
{
label: app.name,
submenu: [
{ role: "about" as const },
{ type: "separator" as const },
{ role: "services" as const },
{ type: "separator" as const },
{ role: "hide" as const },
{ role: "hideOthers" as const },
{ role: "unhide" as const },
{ type: "separator" as const },
{ role: "quit" as const },
],
},
]
: []),
// Window menu
{
label: "Window",
submenu: [
{
label: "Settings...",
accelerator: "CmdOrCtrl+,",
click: () => {
openSettingsHandler();
},
},
{ type: "separator" as const },
{
label: "Set Pane Size",
submenu: [
{
label: "Default",
click: () => {
setAspectRatioHandler("default");
},
},
{
label: "16:9 (Landscape)",
click: () => {
setAspectRatioHandler("16-9");
},
},
{
label: "9:16 (Vertical)",
click: () => {
setAspectRatioHandler("9-16");
},
},
{
label: "4:5 (Portrait)",
click: () => {
setAspectRatioHandler("4-5");
},
},
{ type: "separator" as const },
{
label: "Fullscreen",
click: () => {
setAspectRatioHandler("fullscreen");
},
},
],
},
{ type: "separator" as const },
{ role: "reload" as const },
{ role: "forceReload" as const },
{ role: "toggleDevTools" as const },
...(isMac
? [
{ type: "separator" as const },
{ role: "front" as const },
]
: []),
],
},
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
6 changes: 6 additions & 0 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ const nwWrldBridge = {
ipcRenderer.on("workspace:lostSync", wrapped);
return () => ipcRenderer.removeListener("workspace:lostSync", wrapped);
},
onMenuOpenSettings: (handler: IpcHandler) => {
if (typeof handler !== "function") return undefined;
const wrapped = (event: IpcRendererEvent, data: unknown) => handler(event, data);
ipcRenderer.on("menu:openSettings", wrapped);
return () => ipcRenderer.removeListener("menu:openSettings", wrapped);
},
configureInput: (payload: unknown) => ipcRenderer.invoke("input:configure", payload),
getMidiDevices: () => ipcRenderer.invoke("input:get-midi-devices"),
emitAudioBand: (payload: unknown) => ipcRenderer.invoke("input:audio:emitBand", payload),
Expand Down
1 change: 1 addition & 0 deletions tsconfig.runtime.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"src/sandboxPreload.ts",
"src/main/mainProcess/state.ts",
"src/main/mainProcess/pathSafety.ts",
"src/main/mainProcess/menu.ts",
"src/main/mainProcess/ipcBridge.ts",
"src/main/mainProcess/ipcBridge/projectContext.ts",
"src/main/mainProcess/ipcBridge/registerProjectBridge.ts",
Expand Down