mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-07 13:08:54 -07:00
fix(overlay): prevent field grouping modal from freezing overlay on Hyprland (#138)
This commit is contained in:
@@ -1,21 +1,41 @@
|
||||
import { KikuFieldGroupingChoice, KikuFieldGroupingRequestData } from '../../types';
|
||||
|
||||
const DEFAULT_FIELD_GROUPING_RESPONSE_TIMEOUT_MS = 90000;
|
||||
|
||||
export function createFieldGroupingCallback(options: {
|
||||
getVisibleOverlayVisible: () => boolean;
|
||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
||||
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
||||
sendRequestToVisibleOverlay: (data: KikuFieldGroupingRequestData) => boolean | Promise<boolean>;
|
||||
/**
|
||||
* Tears down the modal UI when the request is abandoned without a renderer response
|
||||
* (send failure or response timeout). Without this the dedicated modal window, its
|
||||
* restore-set entry, and the forced main-overlay passthrough stay orphaned — which on
|
||||
* Wayland leaves an invisible modal covering mpv and the overlay stuck unresponsive.
|
||||
*/
|
||||
dismissModalUi?: () => void;
|
||||
responseTimeoutMs?: number;
|
||||
}): (data: KikuFieldGroupingRequestData) => Promise<KikuFieldGroupingChoice> {
|
||||
const cancelledChoice = (): KikuFieldGroupingChoice => ({
|
||||
keepNoteId: 0,
|
||||
deleteNoteId: 0,
|
||||
deleteDuplicate: true,
|
||||
cancelled: true,
|
||||
});
|
||||
|
||||
const dismissModalUi = (): void => {
|
||||
try {
|
||||
options.dismissModalUi?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to dismiss Kiku field grouping modal UI:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return async (data: KikuFieldGroupingRequestData): Promise<KikuFieldGroupingChoice> => {
|
||||
return new Promise((resolve) => {
|
||||
if (options.getResolver()) {
|
||||
resolve({
|
||||
keepNoteId: 0,
|
||||
deleteNoteId: 0,
|
||||
deleteDuplicate: true,
|
||||
cancelled: true,
|
||||
});
|
||||
resolve(cancelledChoice());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,18 +43,26 @@ export function createFieldGroupingCallback(options: {
|
||||
let settled = false;
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const finish = (choice: KikuFieldGroupingChoice): void => {
|
||||
const finish = (choice: KikuFieldGroupingChoice, abandoned = false): void => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
if (options.getResolver() === finish) {
|
||||
options.setResolver(null);
|
||||
}
|
||||
// Always release the resolver. Callers (main) wrap this in a sequence-guarded
|
||||
// resolver, so an identity check against `finish` never matches and would leak
|
||||
// the resolver — blocking every later grouping attempt with an instant cancel.
|
||||
options.setResolver(null);
|
||||
resolve(choice);
|
||||
|
||||
// When abandoned without a renderer response, tear down the modal window/state
|
||||
// that the request path spun up. A normal response already routes through the
|
||||
// renderer's close handler, so only the abandon paths need this.
|
||||
if (abandoned) {
|
||||
dismissModalUi();
|
||||
}
|
||||
|
||||
if (!previousVisibleOverlay && options.getVisibleOverlayVisible()) {
|
||||
options.setVisibleOverlayVisible(false);
|
||||
}
|
||||
@@ -45,32 +73,17 @@ export function createFieldGroupingCallback(options: {
|
||||
(sent) => {
|
||||
if (settled) return;
|
||||
if (!sent) {
|
||||
finish({
|
||||
keepNoteId: 0,
|
||||
deleteNoteId: 0,
|
||||
deleteDuplicate: true,
|
||||
cancelled: true,
|
||||
});
|
||||
finish(cancelledChoice(), true);
|
||||
return;
|
||||
}
|
||||
timeout = setTimeout(() => {
|
||||
if (!settled) {
|
||||
finish({
|
||||
keepNoteId: 0,
|
||||
deleteNoteId: 0,
|
||||
deleteDuplicate: true,
|
||||
cancelled: true,
|
||||
});
|
||||
finish(cancelledChoice(), true);
|
||||
}
|
||||
}, 90000);
|
||||
}, options.responseTimeoutMs ?? DEFAULT_FIELD_GROUPING_RESPONSE_TIMEOUT_MS);
|
||||
},
|
||||
() => {
|
||||
finish({
|
||||
keepNoteId: 0,
|
||||
deleteNoteId: 0,
|
||||
deleteDuplicate: true,
|
||||
cancelled: true,
|
||||
});
|
||||
finish(cancelledChoice(), true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user