mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-07 07:21:35 -07:00
fix(overlay): Linux X11/XWayland stacking, stale pause state, multi-copy selector (#101)
This commit is contained in:
@@ -236,6 +236,7 @@ test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback
|
||||
const previousWindow = globals.window;
|
||||
const previousDocument = globals.document;
|
||||
const mpvCommands: Array<Array<string | number>> = [];
|
||||
const modalNotifications: string[] = [];
|
||||
|
||||
const snapshot: SubtitleSidebarSnapshot = {
|
||||
cues: [
|
||||
@@ -280,6 +281,12 @@ test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback
|
||||
sendMpvCommand: (command: Array<string | number>) => {
|
||||
mpvCommands.push(command);
|
||||
},
|
||||
notifyOverlayModalOpened: (modal: string) => {
|
||||
modalNotifications.push(`open:${modal}`);
|
||||
},
|
||||
notifyOverlayModalClosed: (modal: string) => {
|
||||
modalNotifications.push(`close:${modal}`);
|
||||
},
|
||||
} as unknown as ElectronAPI,
|
||||
},
|
||||
});
|
||||
@@ -329,9 +336,13 @@ test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback
|
||||
},
|
||||
state,
|
||||
};
|
||||
const visibilityChanges: boolean[] = [];
|
||||
|
||||
const modal = createSubtitleSidebarModal(ctx as never, {
|
||||
modalStateReader: { isAnyModalOpen: () => false },
|
||||
onVisibilityChanged: (visible) => {
|
||||
visibilityChanges.push(visible);
|
||||
},
|
||||
});
|
||||
|
||||
await modal.openSubtitleSidebarModal();
|
||||
@@ -345,9 +356,14 @@ test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback
|
||||
assert.equal(contentStyleValues.get('font-size'), '22px');
|
||||
assert.equal(contentStyle.color, '#ffffff');
|
||||
assert.equal(contentStyleValues.get('--subtitle-sidebar-timestamp-color'), '#aaaaaa');
|
||||
assert.deepEqual(visibilityChanges, [true]);
|
||||
|
||||
modal.seekToCue(snapshot.cues[0]!);
|
||||
assert.deepEqual(mpvCommands.at(-1), ['seek', 1.08, 'absolute+exact']);
|
||||
|
||||
modal.closeSubtitleSidebarModal();
|
||||
assert.deepEqual(visibilityChanges, [true, false]);
|
||||
assert.deepEqual(modalNotifications, ['open:subtitle-sidebar', 'close:subtitle-sidebar']);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
||||
@@ -760,6 +776,104 @@ test('subtitle sidebar auto-open on startup only opens when enabled and configur
|
||||
}
|
||||
});
|
||||
|
||||
test('subtitle sidebar auto-open restores previously open sidebar after renderer replacement', async () => {
|
||||
const globals = globalThis as typeof globalThis & { window?: unknown; document?: unknown };
|
||||
const previousWindow = globals.window;
|
||||
const previousDocument = globals.document;
|
||||
|
||||
const snapshot: SubtitleSidebarSnapshot = {
|
||||
cues: [{ startTime: 1, endTime: 2, text: 'first' }],
|
||||
currentSubtitle: {
|
||||
text: 'first',
|
||||
startTime: 1,
|
||||
endTime: 2,
|
||||
},
|
||||
config: {
|
||||
enabled: true,
|
||||
autoOpen: false,
|
||||
layout: 'overlay',
|
||||
toggleKey: 'Backslash',
|
||||
pauseVideoOnHover: false,
|
||||
autoScroll: true,
|
||||
maxWidth: 420,
|
||||
opacity: 0.92,
|
||||
backgroundColor: 'rgba(54, 58, 79, 0.88)',
|
||||
textColor: '#cad3f5',
|
||||
fontFamily: '"Iosevka Aile", sans-serif',
|
||||
fontSize: 17,
|
||||
timestampColor: '#a5adcb',
|
||||
activeLineColor: '#f5bde6',
|
||||
activeLineBackgroundColor: 'rgba(138, 173, 244, 0.22)',
|
||||
hoverLineBackgroundColor: 'rgba(54, 58, 79, 0.84)',
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, 'window', {
|
||||
configurable: true,
|
||||
value: {
|
||||
electronAPI: {
|
||||
getSubtitleSidebarSnapshot: async () => snapshot,
|
||||
sendMpvCommand: () => {},
|
||||
} as unknown as ElectronAPI,
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
},
|
||||
});
|
||||
Object.defineProperty(globalThis, 'document', {
|
||||
configurable: true,
|
||||
value: {
|
||||
createElement: () => createCueRow(),
|
||||
body: {
|
||||
classList: createClassList(),
|
||||
},
|
||||
documentElement: {
|
||||
style: {
|
||||
setProperty: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const state = createRendererState();
|
||||
const modalClassList = createClassList(['hidden']);
|
||||
const cueList = createListStub();
|
||||
const ctx = {
|
||||
dom: {
|
||||
overlay: { classList: createClassList() },
|
||||
subtitleSidebarModal: {
|
||||
classList: modalClassList,
|
||||
setAttribute: () => {},
|
||||
style: { setProperty: () => {} },
|
||||
addEventListener: () => {},
|
||||
},
|
||||
subtitleSidebarContent: {
|
||||
classList: createClassList(),
|
||||
getBoundingClientRect: () => ({ width: 420 }),
|
||||
},
|
||||
subtitleSidebarClose: { addEventListener: () => {} },
|
||||
subtitleSidebarStatus: { textContent: '' },
|
||||
subtitleSidebarList: cueList,
|
||||
},
|
||||
state,
|
||||
};
|
||||
|
||||
const modal = createSubtitleSidebarModal(ctx as never, {
|
||||
modalStateReader: { isAnyModalOpen: () => false },
|
||||
shouldRestoreOpenOnStartup: async () => true,
|
||||
});
|
||||
|
||||
await modal.autoOpenSubtitleSidebarOnStartup();
|
||||
|
||||
assert.equal(state.subtitleSidebarModalOpen, true);
|
||||
assert.equal(modalClassList.contains('hidden'), false);
|
||||
assert.equal(cueList.children.length, 1);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
||||
}
|
||||
});
|
||||
|
||||
test('subtitle sidebar refresh closes and clears state when config becomes disabled', async () => {
|
||||
const globals = globalThis as typeof globalThis & { window?: unknown; document?: unknown };
|
||||
const previousWindow = globals.window;
|
||||
|
||||
@@ -196,6 +196,8 @@ export function createSubtitleSidebarModal(
|
||||
ctx: RendererContext,
|
||||
options: {
|
||||
modalStateReader: Pick<ModalStateReader, 'isAnyModalOpen'>;
|
||||
onVisibilityChanged?: (visible: boolean) => void;
|
||||
shouldRestoreOpenOnStartup?: () => Promise<boolean>;
|
||||
},
|
||||
) {
|
||||
let snapshotPollInterval: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -648,13 +650,16 @@ export function createSubtitleSidebarModal(
|
||||
startSnapshotPolling();
|
||||
syncEmbeddedSidebarLayout();
|
||||
restoreEmbeddedSidebarPassthrough();
|
||||
window.electronAPI.notifyOverlayModalOpened?.('subtitle-sidebar');
|
||||
options.onVisibilityChanged?.(true);
|
||||
}
|
||||
|
||||
async function autoOpenSubtitleSidebarOnStartup(): Promise<void> {
|
||||
const snapshot = await refreshSnapshot();
|
||||
const shouldRestoreOpen = (await options.shouldRestoreOpenOnStartup?.()) === true;
|
||||
if (
|
||||
!snapshot.config.enabled ||
|
||||
!snapshot.config.autoOpen ||
|
||||
(!snapshot.config.autoOpen && !shouldRestoreOpen) ||
|
||||
ctx.state.subtitleSidebarModalOpen
|
||||
) {
|
||||
return;
|
||||
@@ -677,6 +682,8 @@ export function createSubtitleSidebarModal(
|
||||
ctx.dom.overlay.classList.remove('interactive');
|
||||
}
|
||||
restoreEmbeddedSidebarPassthrough();
|
||||
window.electronAPI.notifyOverlayModalClosed?.('subtitle-sidebar');
|
||||
options.onVisibilityChanged?.(false);
|
||||
}
|
||||
|
||||
async function toggleSubtitleSidebarModal(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user