mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 06:06:18 -08:00
feat(ui): add custom title bar window controls for Windows and Linux
Hide the native title bar on win32/linux (titleBarStyle: 'hidden', frame untouched so native resize borders and snapping keep working) and render minimize / maximize-restore / close buttons in the renderer, mirroring the existing macOS traffic-light setup. - New WINDOW:* IPC contract (minimize, toggle-maximize, close, get-state) handled in window.events.ts, resolved from the sender WebContents; close goes through win.close() so window-bounds persistence still runs. - WINDOW:STATE_CHANGED pushed on maximize/unmaximize/fullscreen so the maximize/restore glyph stays correct for OS-triggered changes; controls hide while fullscreen. - WindowControlsComponent mounts once in app-root as a manual popover so it stays in the browser top layer above CDK overlays (dialogs, multi-EPG) - same behavior as macOS traffic lights. - Theme-aware via CSS vars (--app-on-surface, --app-hover-overlay); Windows-red close hover. Drag regions get right padding through a body-level frameless-platform class. - Gated by RuntimeCapabilitiesService.usesCustomWindowControls; PWA and macOS never mount the controls. Includes unit specs for the component and IPC handlers, an Electron E2E suite (window-controls.e2e.ts), and a window-chrome section in docs/architecture/workspace-shell.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2be42257ab
commit
24c2da48ae
18 files changed
+939
-25
No files matched your search
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
closeElectronApp,
|
||||
expect,
|
||||
launchElectronApp,
|
||||
test,
|
||||
} from './electron-test-fixtures';
|
||||
|
||||
// Custom window controls are only rendered on Windows/Linux; macOS keeps
|
||||
// the native traffic lights.
|
||||
test.describe('Custom window controls', () => {
|
||||
test.skip(
|
||||
process.platform === 'darwin',
|
||||
'macOS uses native traffic lights instead of custom controls'
|
||||
);
|
||||
|
||||
test('@electron renders the window control buttons', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-minimize')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-maximize')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-close')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-maximize-glyph')
|
||||
).toBeVisible();
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('@electron maximize button toggles the native window state', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
const isMaximized = () =>
|
||||
app.electronApp.evaluate(({ BrowserWindow }) => {
|
||||
const mainWindow = BrowserWindow.getAllWindows()[0];
|
||||
return mainWindow ? mainWindow.isMaximized() : false;
|
||||
});
|
||||
|
||||
await app.mainWindow.getByTestId('window-maximize').click();
|
||||
await expect.poll(isMaximized, { timeout: 10_000 }).toBe(true);
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-restore-glyph')
|
||||
).toBeVisible();
|
||||
|
||||
await app.mainWindow.getByTestId('window-maximize').click();
|
||||
await expect.poll(isMaximized, { timeout: 10_000 }).toBe(false);
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-maximize-glyph')
|
||||
).toBeVisible();
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('@electron reflects window state changes triggered from the main process', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await app.electronApp.evaluate(({ BrowserWindow }) => {
|
||||
BrowserWindow.getAllWindows()[0]?.maximize();
|
||||
});
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-restore-glyph')
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await app.electronApp.evaluate(({ BrowserWindow }) => {
|
||||
BrowserWindow.getAllWindows()[0]?.unmaximize();
|
||||
});
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('window-maximize-glyph')
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('@electron minimize button minimizes the window', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
const isMinimized = () =>
|
||||
app.electronApp.evaluate(({ BrowserWindow }) => {
|
||||
const mainWindow = BrowserWindow.getAllWindows()[0];
|
||||
return mainWindow ? mainWindow.isMinimized() : false;
|
||||
});
|
||||
|
||||
await app.mainWindow.getByTestId('window-minimize').click();
|
||||
await expect.poll(isMinimized, { timeout: 10_000 }).toBe(true);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user