mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-27 00:55:16 -07:00
fix: settings window z-order on Hyprland and Linux app detach (#85)
This commit is contained in:
@@ -56,6 +56,7 @@ export interface ConfigSettingsRuntimeDeps<TWindow extends ConfigSettingsWindowL
|
||||
setSettingsWindow(window: TWindow | null): void;
|
||||
createSettingsWindow(): TWindow;
|
||||
settingsHtmlPath: string;
|
||||
promoteSettingsWindowAboveOverlay?: (window: TWindow) => void;
|
||||
openPath(path: string): Promise<string>;
|
||||
defaultAnkiConnectUrl: string;
|
||||
createAnkiClient(url: string): ConfigSettingsAnkiClient;
|
||||
@@ -144,6 +145,7 @@ export function createConfigSettingsRuntime<TWindow extends ConfigSettingsWindow
|
||||
setSettingsWindow: deps.setSettingsWindow,
|
||||
createSettingsWindow: deps.createSettingsWindow,
|
||||
settingsHtmlPath: deps.settingsHtmlPath,
|
||||
promoteSettingsWindowAboveOverlay: deps.promoteSettingsWindowAboveOverlay,
|
||||
log: deps.log,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ test('createOpenConfigSettingsWindowHandler focuses existing settings window', (
|
||||
const calls: string[] = [];
|
||||
const existing = {
|
||||
isDestroyed: () => false,
|
||||
show: () => calls.push('show'),
|
||||
focus: () => calls.push('focus'),
|
||||
loadFile: () => calls.push('load'),
|
||||
on: () => {},
|
||||
@@ -18,10 +19,11 @@ test('createOpenConfigSettingsWindowHandler focuses existing settings window', (
|
||||
throw new Error('Should not create a second window.');
|
||||
},
|
||||
settingsHtmlPath: '/tmp/settings.html',
|
||||
promoteSettingsWindowAboveOverlay: () => calls.push('promote'),
|
||||
});
|
||||
|
||||
assert.equal(open(), true);
|
||||
assert.deepEqual(calls, ['focus']);
|
||||
assert.deepEqual(calls, ['show', 'focus', 'promote']);
|
||||
});
|
||||
|
||||
test('createOpenConfigSettingsWindowHandler creates window and clears closed state', () => {
|
||||
@@ -29,6 +31,7 @@ test('createOpenConfigSettingsWindowHandler creates window and clears closed sta
|
||||
const handlers: { closed?: () => void } = {};
|
||||
const created = {
|
||||
isDestroyed: () => false,
|
||||
show: () => calls.push('show'),
|
||||
focus: () => calls.push('focus'),
|
||||
loadFile: (path: string) => calls.push(`load:${path}`),
|
||||
on: (event: string, handler: () => void) => {
|
||||
@@ -41,10 +44,11 @@ test('createOpenConfigSettingsWindowHandler creates window and clears closed sta
|
||||
setSettingsWindow: (window) => calls.push(window ? 'set:window' : 'set:null'),
|
||||
createSettingsWindow: () => created,
|
||||
settingsHtmlPath: '/tmp/settings.html',
|
||||
promoteSettingsWindowAboveOverlay: () => calls.push('promote'),
|
||||
});
|
||||
|
||||
assert.equal(open(), true);
|
||||
assert.deepEqual(calls, ['load:/tmp/settings.html', 'set:window', 'focus']);
|
||||
assert.deepEqual(calls, ['load:/tmp/settings.html', 'set:window', 'show', 'focus', 'promote']);
|
||||
assert.ok(handlers.closed);
|
||||
handlers.closed();
|
||||
assert.equal(calls.at(-1), 'set:null');
|
||||
@@ -54,6 +58,7 @@ test('createOpenConfigSettingsWindowHandler clears failed load window state', as
|
||||
const calls: string[] = [];
|
||||
const created = {
|
||||
isDestroyed: () => false,
|
||||
show: () => calls.push('show'),
|
||||
focus: () => calls.push('focus'),
|
||||
loadFile: (path: string) => {
|
||||
calls.push(`load:${path}`);
|
||||
@@ -76,6 +81,7 @@ test('createOpenConfigSettingsWindowHandler clears failed load window state', as
|
||||
assert.deepEqual(calls, [
|
||||
'load:/tmp/missing-settings.html',
|
||||
'set:window',
|
||||
'show',
|
||||
'focus',
|
||||
'set:null',
|
||||
'destroy',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface ConfigSettingsWindowLike {
|
||||
isDestroyed(): boolean;
|
||||
show(): void;
|
||||
focus(): void;
|
||||
loadFile(path: string): unknown;
|
||||
on(event: 'closed', handler: () => void): unknown;
|
||||
@@ -11,6 +12,7 @@ export interface OpenConfigSettingsWindowDeps<TWindow extends ConfigSettingsWind
|
||||
setSettingsWindow(window: TWindow | null): void;
|
||||
createSettingsWindow(): TWindow;
|
||||
settingsHtmlPath: string;
|
||||
promoteSettingsWindowAboveOverlay?: (window: TWindow) => void;
|
||||
log?: (message: string) => void;
|
||||
}
|
||||
|
||||
@@ -18,9 +20,15 @@ export function createOpenConfigSettingsWindowHandler<TWindow extends ConfigSett
|
||||
deps: OpenConfigSettingsWindowDeps<TWindow>,
|
||||
): () => boolean {
|
||||
return () => {
|
||||
const showAndFocus = (window: TWindow): void => {
|
||||
window.show();
|
||||
window.focus();
|
||||
deps.promoteSettingsWindowAboveOverlay?.(window);
|
||||
};
|
||||
|
||||
const existing = deps.getSettingsWindow();
|
||||
if (existing && !existing.isDestroyed()) {
|
||||
existing.focus();
|
||||
showAndFocus(existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,7 +43,7 @@ export function createOpenConfigSettingsWindowHandler<TWindow extends ConfigSett
|
||||
window.on('closed', () => {
|
||||
deps.setSettingsWindow(null);
|
||||
});
|
||||
window.focus();
|
||||
showAndFocus(window);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { shouldSuppressVisibleOverlayRaiseForSeparateWindow } from './settings-window-z-order';
|
||||
|
||||
test('separate settings windows suppress visible overlay restacking', () => {
|
||||
const mainWindow = { id: 'overlay', isDestroyed: () => false };
|
||||
const settingsWindow = { id: 'settings', isDestroyed: () => false };
|
||||
|
||||
assert.equal(
|
||||
shouldSuppressVisibleOverlayRaiseForSeparateWindow({
|
||||
window: mainWindow,
|
||||
mainWindow,
|
||||
separateWindows: [settingsWindow],
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('separate settings windows do not suppress unrelated or closed overlay work', () => {
|
||||
const mainWindow = { id: 'overlay', isDestroyed: () => false };
|
||||
const modalWindow = { id: 'modal', isDestroyed: () => false };
|
||||
const closedSettingsWindow = { id: 'settings', isDestroyed: () => true };
|
||||
|
||||
assert.equal(
|
||||
shouldSuppressVisibleOverlayRaiseForSeparateWindow({
|
||||
window: modalWindow,
|
||||
mainWindow,
|
||||
separateWindows: [{ isDestroyed: () => false }],
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
shouldSuppressVisibleOverlayRaiseForSeparateWindow({
|
||||
window: mainWindow,
|
||||
mainWindow,
|
||||
separateWindows: [closedSettingsWindow, null],
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
type SeparateWindowLike = {
|
||||
isDestroyed(): boolean;
|
||||
};
|
||||
|
||||
function hasLiveSeparateWindow(windows: Array<SeparateWindowLike | null | undefined>): boolean {
|
||||
return windows.some((window) => Boolean(window && !window.isDestroyed()));
|
||||
}
|
||||
|
||||
export function shouldSuppressVisibleOverlayRaiseForSeparateWindow(options: {
|
||||
window: unknown;
|
||||
mainWindow: unknown;
|
||||
separateWindows: Array<SeparateWindowLike | null | undefined>;
|
||||
}): boolean {
|
||||
if (!options.mainWindow || options.window !== options.mainWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasLiveSeparateWindow(options.separateWindows);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ test('createCreateConfigSettingsWindowHandler builds configuration settings wind
|
||||
width: 1040,
|
||||
height: 760,
|
||||
title: 'SubMiner Settings',
|
||||
show: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
resizable: true,
|
||||
backgroundColor: '#24273a',
|
||||
|
||||
@@ -2,6 +2,7 @@ interface SetupWindowConfig {
|
||||
width: number;
|
||||
height: number;
|
||||
title: string;
|
||||
show?: boolean;
|
||||
resizable?: boolean;
|
||||
minimizable?: boolean;
|
||||
maximizable?: boolean;
|
||||
@@ -19,7 +20,7 @@ function createSetupWindowHandler<TWindow>(
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
title: config.title,
|
||||
show: true,
|
||||
show: config.show ?? true,
|
||||
autoHideMenuBar: true,
|
||||
...(config.resizable === undefined ? {} : { resizable: config.resizable }),
|
||||
...(config.minimizable === undefined ? {} : { minimizable: config.minimizable }),
|
||||
@@ -77,6 +78,7 @@ export function createCreateConfigSettingsWindowHandler<TWindow>(deps: {
|
||||
width: 1040,
|
||||
height: 760,
|
||||
title: 'SubMiner Settings',
|
||||
show: false,
|
||||
resizable: true,
|
||||
preloadPath: deps.preloadPath,
|
||||
backgroundColor: '#24273a',
|
||||
|
||||
Reference in New Issue
Block a user