mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-08 07:21:31 -07:00
153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
import { KikuFieldGroupingChoice, KikuFieldGroupingRequestData } from '../../types';
|
|
import { IPC_CHANNELS } from '../../shared/ipc/contracts';
|
|
import { createFieldGroupingCallbackRuntime, sendToVisibleOverlayRuntime } from './overlay-bridge';
|
|
|
|
interface WindowLike {
|
|
isDestroyed: () => boolean;
|
|
webContents: {
|
|
send: (channel: string, payload?: unknown) => void;
|
|
};
|
|
}
|
|
|
|
const KIKU_FIELD_GROUPING_MODAL_OPEN_TIMEOUT_MS = 1500;
|
|
const KIKU_FIELD_GROUPING_MODAL_RETRY_WARNING =
|
|
'Kiku field grouping modal did not acknowledge modal open on first attempt; retrying dedicated modal window.';
|
|
|
|
export interface FieldGroupingOverlayRuntimeOptions<T extends string> {
|
|
getMainWindow: () => WindowLike | null;
|
|
getVisibleOverlayVisible: () => boolean;
|
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
|
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
|
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
|
getRestoreVisibleOverlayOnModalClose: () => Set<T>;
|
|
waitForModalOpen?: (modal: T, timeoutMs: number) => Promise<boolean>;
|
|
handleOverlayModalClosed?: (modal: T) => void;
|
|
logWarn?: (message: string) => void;
|
|
/**
|
|
* Prepare the overlay runtime and (re)create the visible overlay window before opening the
|
|
* modal — the same prerequisites every other modal runs via `openOverlayHostedModal`. Without
|
|
* them the field grouping modal can open with no sibling overlay window present, and on
|
|
* Hyprland it then fails to sit above / take focus over fullscreen mpv the way the other
|
|
* modals do.
|
|
*/
|
|
ensureOverlayStartupPrereqs?: () => void;
|
|
ensureOverlayWindowsReadyForVisibilityActions?: () => void;
|
|
sendToVisibleOverlay?: (
|
|
channel: string,
|
|
payload?: unknown,
|
|
runtimeOptions?: { restoreOnModalClose?: T; preferModalWindow?: boolean },
|
|
) => boolean;
|
|
}
|
|
|
|
export function createFieldGroupingOverlayRuntime<T extends string>(
|
|
options: FieldGroupingOverlayRuntimeOptions<T>,
|
|
): {
|
|
sendToVisibleOverlay: (
|
|
channel: string,
|
|
payload?: unknown,
|
|
runtimeOptions?: { restoreOnModalClose?: T; preferModalWindow?: boolean },
|
|
) => boolean;
|
|
createFieldGroupingCallback: () => (
|
|
data: KikuFieldGroupingRequestData,
|
|
) => Promise<KikuFieldGroupingChoice>;
|
|
} {
|
|
const sendToVisibleOverlay = (
|
|
channel: string,
|
|
payload?: unknown,
|
|
runtimeOptions?: { restoreOnModalClose?: T; preferModalWindow?: boolean },
|
|
): boolean => {
|
|
if (options.sendToVisibleOverlay) {
|
|
const wasVisible = options.getVisibleOverlayVisible();
|
|
const sent = options.sendToVisibleOverlay(channel, payload, runtimeOptions);
|
|
if (sent && !wasVisible && !options.getVisibleOverlayVisible()) {
|
|
options.setVisibleOverlayVisible(true);
|
|
}
|
|
return sent;
|
|
}
|
|
return sendToVisibleOverlayRuntime({
|
|
mainWindow: options.getMainWindow() as never,
|
|
visibleOverlayVisible: options.getVisibleOverlayVisible(),
|
|
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
|
channel,
|
|
payload,
|
|
restoreOnModalClose: runtimeOptions?.restoreOnModalClose,
|
|
restoreVisibleOverlayOnModalClose: options.getRestoreVisibleOverlayOnModalClose(),
|
|
});
|
|
};
|
|
|
|
const sendKikuFieldGroupingRequest = async (
|
|
data: KikuFieldGroupingRequestData,
|
|
): Promise<boolean> => {
|
|
const kikuModal = 'kiku' as T;
|
|
const sendOpen = (): boolean => {
|
|
// Match every other modal's open path (openOverlayHostedModal): ensure the overlay runtime
|
|
// and visible overlay window exist before handing off to the dedicated modal window.
|
|
options.ensureOverlayStartupPrereqs?.();
|
|
options.ensureOverlayWindowsReadyForVisibilityActions?.();
|
|
return sendToVisibleOverlay('kiku:field-grouping-request', data, {
|
|
restoreOnModalClose: kikuModal,
|
|
preferModalWindow: true,
|
|
});
|
|
};
|
|
|
|
if (!options.waitForModalOpen) {
|
|
return sendOpen();
|
|
}
|
|
|
|
if (!sendOpen()) {
|
|
return false;
|
|
}
|
|
if (await options.waitForModalOpen(kikuModal, KIKU_FIELD_GROUPING_MODAL_OPEN_TIMEOUT_MS)) {
|
|
return true;
|
|
}
|
|
|
|
options.logWarn?.(KIKU_FIELD_GROUPING_MODAL_RETRY_WARNING);
|
|
if (!sendOpen()) {
|
|
options.handleOverlayModalClosed?.(kikuModal);
|
|
return false;
|
|
}
|
|
|
|
const opened = await options.waitForModalOpen(
|
|
kikuModal,
|
|
KIKU_FIELD_GROUPING_MODAL_OPEN_TIMEOUT_MS,
|
|
);
|
|
if (!opened) {
|
|
options.handleOverlayModalClosed?.(kikuModal);
|
|
}
|
|
return opened;
|
|
};
|
|
|
|
const dismissModalUi = (): void => {
|
|
const kikuModal = 'kiku' as T;
|
|
// Best-effort: tell the renderer hosting the modal to close its dialog. When the modal
|
|
// lives in the dedicated modal window this is redundant with the teardown below, but it
|
|
// also covers the case where the request was routed into the visible overlay.
|
|
sendToVisibleOverlay(IPC_CHANNELS.event.kikuFieldGroupingCancel, undefined, {
|
|
preferModalWindow: true,
|
|
});
|
|
// Reliable teardown of main-side modal state (restore set, main-overlay passthrough,
|
|
// dedicated modal window). This is what recovers the frozen overlay when a grouping
|
|
// request times out or fails to reach a visible modal.
|
|
options.handleOverlayModalClosed?.(kikuModal);
|
|
};
|
|
|
|
const createFieldGroupingCallback = (): ((
|
|
data: KikuFieldGroupingRequestData,
|
|
) => Promise<KikuFieldGroupingChoice>) => {
|
|
return createFieldGroupingCallbackRuntime({
|
|
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
|
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
|
getResolver: options.getResolver,
|
|
setResolver: options.setResolver,
|
|
sendToVisibleOverlay,
|
|
sendKikuFieldGroupingRequest,
|
|
dismissModalUi,
|
|
});
|
|
};
|
|
|
|
return {
|
|
sendToVisibleOverlay,
|
|
createFieldGroupingCallback,
|
|
};
|
|
}
|