mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-11 17:16:18 -07:00
127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
import type { ElectronAPI, SubtitleSidebarSnapshot } from '../../types';
|
|
import { SUBTITLE_DEFAULT_CONFIG } from '../../config/definitions/defaults-subtitle';
|
|
import { createRendererState } from '../state';
|
|
import { resolveRendererDom } from '../utils/dom';
|
|
import { resolvePlatformInfo } from '../utils/platform';
|
|
import { createSubtitleSidebarModal } from './subtitle-sidebar';
|
|
import {
|
|
getSubtitleSidebarSelection,
|
|
wireSubtitleSidebarSelection,
|
|
} from './subtitle-sidebar-selection';
|
|
|
|
export async function setup() {
|
|
const commands: unknown[] = [];
|
|
const snapshot: SubtitleSidebarSnapshot = {
|
|
sourceKey: 'episode-1:track-1',
|
|
cues: [
|
|
{ text: '最初の台詞', startTime: 0, endTime: 1 },
|
|
{ text: '同じ台詞\n二行目', startTime: 1, endTime: 2 },
|
|
{ text: '同じ台詞', startTime: 2, endTime: 3 },
|
|
...Array.from({ length: 30 }, (_, i) => ({
|
|
text: `後の台詞${i}`,
|
|
startTime: i + 3,
|
|
endTime: i + 4,
|
|
})),
|
|
],
|
|
currentSubtitle: { text: '最初の台詞', startTime: 0, endTime: 1 },
|
|
currentTimeSec: 0,
|
|
config: {
|
|
...SUBTITLE_DEFAULT_CONFIG.subtitleSidebar,
|
|
enabled: true,
|
|
layout: 'overlay',
|
|
pauseVideoOnHover: false,
|
|
autoScroll: true,
|
|
css: {},
|
|
},
|
|
};
|
|
Object.defineProperty(window, 'electronAPI', {
|
|
value: {
|
|
getSubtitleSidebarSnapshot: async () => snapshot,
|
|
copySubtitleSidebarSelection: async (text) => {
|
|
if (!('copyTestSelection' in window) || typeof window.copyTestSelection !== 'function')
|
|
throw new Error('Missing test clipboard bridge');
|
|
window.copyTestSelection(text);
|
|
},
|
|
getOverlayLayer: () => 'visible',
|
|
sendMpvCommand: (command) => {
|
|
commands.push(command);
|
|
},
|
|
setIgnoreMouseEvents: () => {},
|
|
} satisfies Pick<
|
|
ElectronAPI,
|
|
| 'getSubtitleSidebarSnapshot'
|
|
| 'copySubtitleSidebarSelection'
|
|
| 'getOverlayLayer'
|
|
| 'sendMpvCommand'
|
|
| 'setIgnoreMouseEvents'
|
|
>,
|
|
});
|
|
const ctx = {
|
|
dom: resolveRendererDom(),
|
|
state: createRendererState(),
|
|
platform: resolvePlatformInfo(),
|
|
};
|
|
const modal = createSubtitleSidebarModal(ctx, {
|
|
modalStateReader: { isAnyModalOpen: () => false },
|
|
});
|
|
modal.wireDomEvents();
|
|
wireSubtitleSidebarSelection(ctx);
|
|
await modal.openSubtitleSidebarModal();
|
|
const list = ctx.dom.subtitleSidebarList;
|
|
list.style.height = '180px';
|
|
list.style.overflowY = 'auto';
|
|
const selection = window.getSelection();
|
|
if (!selection) throw new Error('Native selection unavailable');
|
|
const textNode = (index: number) => {
|
|
const node = list.children[index]?.querySelector('.subtitle-sidebar-text')?.firstChild;
|
|
if (!node) throw new Error(`Missing cue ${index}`);
|
|
return node;
|
|
};
|
|
const select = (backward = false) => {
|
|
const start = textNode(0);
|
|
const end = textNode(2);
|
|
selection.setBaseAndExtent(backward ? end : start, 2, backward ? start : end, 2);
|
|
document.dispatchEvent(new Event('selectionchange'));
|
|
return getSubtitleSidebarSelection(list);
|
|
};
|
|
// This is the same competing action as the renderer's current-subtitle shortcut.
|
|
let fallbackCopies = 0;
|
|
document.addEventListener('keydown', (event) => {
|
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') fallbackCopies += 1;
|
|
});
|
|
return {
|
|
select,
|
|
selected: () => getSubtitleSidebarSelection(list),
|
|
buttonVisible: () => !ctx.dom.subtitleSidebarCopy.hidden,
|
|
fallbackCopies: () => fallbackCopies,
|
|
dragPoints: () =>
|
|
[0, 2].map((index) => {
|
|
const range = document.createRange();
|
|
range.setStart(textNode(index), 2);
|
|
range.collapse(true);
|
|
const rect = range.getBoundingClientRect();
|
|
return { x: Math.round(rect.x), y: Math.round(rect.y + rect.height / 2) };
|
|
}),
|
|
clickCopy: () => ctx.dom.subtitleSidebarCopy.click(),
|
|
clickCue: () => {
|
|
const before = commands.length;
|
|
list.children[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
return commands.length - before;
|
|
},
|
|
updatePlayback: async () => {
|
|
list.scrollTop = 0;
|
|
snapshot.currentTimeSec = 25;
|
|
snapshot.currentSubtitle = { text: '後の台詞22', startTime: 25, endTime: 26 };
|
|
await modal.refreshSubtitleSidebarSnapshot();
|
|
return list.scrollTop;
|
|
},
|
|
changeSource: async () => {
|
|
snapshot.sourceKey = 'episode-2:track-1';
|
|
await modal.refreshSubtitleSidebarSnapshot();
|
|
return getSubtitleSidebarSelection(list);
|
|
},
|
|
clear: () => selection.removeAllRanges(),
|
|
close: () => modal.closeSubtitleSidebarModal(),
|
|
};
|
|
}
|