mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-30 18:12:08 -07:00
refactor: migrate shared type imports
This commit is contained in:
@@ -47,7 +47,10 @@ type ControllerBindingCaptureResult =
|
||||
dpadDirection: ControllerDpadFallback;
|
||||
};
|
||||
|
||||
function isActiveButton(button: ControllerButtonState | undefined, triggerDeadzone: number): boolean {
|
||||
function isActiveButton(
|
||||
button: ControllerButtonState | undefined,
|
||||
triggerDeadzone: number,
|
||||
): boolean {
|
||||
if (!button) return false;
|
||||
return Boolean(button.pressed) || button.value >= triggerDeadzone;
|
||||
}
|
||||
@@ -90,7 +93,10 @@ export function createControllerBindingCapture(options: {
|
||||
});
|
||||
}
|
||||
|
||||
function arm(nextTarget: ControllerBindingCaptureTarget, snapshot: ControllerBindingCaptureSnapshot): void {
|
||||
function arm(
|
||||
nextTarget: ControllerBindingCaptureTarget,
|
||||
snapshot: ControllerBindingCaptureSnapshot,
|
||||
): void {
|
||||
target = nextTarget;
|
||||
resetBlockedState(snapshot);
|
||||
}
|
||||
@@ -139,7 +145,10 @@ export function createControllerBindingCapture(options: {
|
||||
}
|
||||
|
||||
// After dpad early-return, only 'discrete' | 'axis' remain
|
||||
const narrowedTarget: Extract<ControllerBindingCaptureTarget, { bindingType: 'discrete' | 'axis' }> = target;
|
||||
const narrowedTarget: Extract<
|
||||
ControllerBindingCaptureTarget,
|
||||
{ bindingType: 'discrete' | 'axis' }
|
||||
> = target;
|
||||
|
||||
for (let index = 0; index < snapshot.buttons.length; index += 1) {
|
||||
if (!isActiveButton(snapshot.buttons[index], options.triggerDeadzone)) continue;
|
||||
|
||||
@@ -194,13 +194,7 @@ export function createKeyboardHandlers(
|
||||
(isBackslashConfigured && e.key === '\\') ||
|
||||
(toggleKey.length === 1 && e.key === toggleKey);
|
||||
|
||||
return (
|
||||
keyMatches &&
|
||||
!e.ctrlKey &&
|
||||
!e.altKey &&
|
||||
!e.metaKey &&
|
||||
!e.repeat
|
||||
);
|
||||
return keyMatches && !e.ctrlKey && !e.altKey && !e.metaKey && !e.repeat;
|
||||
}
|
||||
|
||||
function isStatsOverlayToggle(e: KeyboardEvent): boolean {
|
||||
|
||||
@@ -3,10 +3,7 @@ import test from 'node:test';
|
||||
|
||||
import type { SubtitleSidebarConfig } from '../../types';
|
||||
import { createMouseHandlers } from './mouse.js';
|
||||
import {
|
||||
YOMITAN_POPUP_HIDDEN_EVENT,
|
||||
YOMITAN_POPUP_SHOWN_EVENT,
|
||||
} from '../yomitan-popup.js';
|
||||
import { YOMITAN_POPUP_HIDDEN_EVENT, YOMITAN_POPUP_SHOWN_EVENT } from '../yomitan-popup.js';
|
||||
|
||||
function createClassList() {
|
||||
const classes = new Set<string>();
|
||||
@@ -118,9 +115,15 @@ test('secondary hover pauses on enter, reveals secondary subtitle, and resumes o
|
||||
});
|
||||
|
||||
await handlers.handleSecondaryMouseEnter();
|
||||
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), true);
|
||||
assert.equal(
|
||||
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
|
||||
true,
|
||||
);
|
||||
await handlers.handleSecondaryMouseLeave();
|
||||
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
|
||||
assert.equal(
|
||||
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
|
||||
false,
|
||||
);
|
||||
|
||||
assert.deepEqual(mpvCommands, [
|
||||
['set_property', 'pause', 'yes'],
|
||||
@@ -186,7 +189,10 @@ test('secondary leave toward primary subtitle container clears the secondary hov
|
||||
} as unknown as MouseEvent);
|
||||
|
||||
assert.equal(ctx.state.isOverSubtitle, false);
|
||||
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
|
||||
assert.equal(
|
||||
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(mpvCommands, [['set_property', 'pause', 'yes']]);
|
||||
});
|
||||
|
||||
@@ -237,7 +243,10 @@ test('primary hover pauses on enter without revealing secondary subtitle', async
|
||||
});
|
||||
|
||||
await handlers.handlePrimaryMouseEnter();
|
||||
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
|
||||
assert.equal(
|
||||
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
|
||||
false,
|
||||
);
|
||||
await handlers.handlePrimaryMouseLeave();
|
||||
|
||||
assert.deepEqual(mpvCommands, [
|
||||
@@ -394,7 +403,10 @@ test('restorePointerInteractionState reapplies the secondary hover class from po
|
||||
mousemove?.({ clientX: 10, clientY: 20 } as MouseEvent);
|
||||
|
||||
assert.equal(ctx.state.isOverSubtitle, true);
|
||||
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), true);
|
||||
assert.equal(
|
||||
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
|
||||
true,
|
||||
);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: originalDocument });
|
||||
Object.defineProperty(globalThis, 'window', { configurable: true, value: originalWindow });
|
||||
|
||||
@@ -228,10 +228,7 @@ export function createMouseHandlers(
|
||||
syncOverlayMouseIgnoreState(ctx);
|
||||
}
|
||||
|
||||
async function handleMouseEnter(
|
||||
_event?: MouseEvent,
|
||||
showSecondaryHover = false,
|
||||
): Promise<void> {
|
||||
async function handleMouseEnter(_event?: MouseEvent, showSecondaryHover = false): Promise<void> {
|
||||
ctx.state.isOverSubtitle = true;
|
||||
if (showSecondaryHover) {
|
||||
ctx.dom.secondarySubContainer.classList.add('secondary-sub-hover-active');
|
||||
@@ -267,10 +264,7 @@ export function createMouseHandlers(
|
||||
pausedBySubtitleHover = true;
|
||||
}
|
||||
|
||||
async function handleMouseLeave(
|
||||
_event?: MouseEvent,
|
||||
hideSecondaryHover = false,
|
||||
): Promise<void> {
|
||||
async function handleMouseLeave(_event?: MouseEvent, hideSecondaryHover = false): Promise<void> {
|
||||
const relatedTarget = _event?.relatedTarget ?? null;
|
||||
const otherContainer = hideSecondaryHover
|
||||
? ctx.dom.subtitleContainer
|
||||
|
||||
@@ -118,10 +118,14 @@ export function getDefaultControllerBinding(actionId: ControllerBindingActionId)
|
||||
if (!definition) {
|
||||
return { kind: 'none' } as const;
|
||||
}
|
||||
return JSON.parse(JSON.stringify(definition.defaultBinding)) as ResolvedControllerConfig['bindings'][ControllerBindingActionId];
|
||||
return JSON.parse(
|
||||
JSON.stringify(definition.defaultBinding),
|
||||
) as ResolvedControllerConfig['bindings'][ControllerBindingActionId];
|
||||
}
|
||||
|
||||
export function getDefaultDpadFallback(actionId: ControllerBindingActionId): ControllerDpadFallback {
|
||||
export function getDefaultDpadFallback(
|
||||
actionId: ControllerBindingActionId,
|
||||
): ControllerDpadFallback {
|
||||
const definition = getControllerBindingDefinition(actionId);
|
||||
if (!definition || definition.defaultBinding.kind !== 'axis') return 'none';
|
||||
const binding = definition.defaultBinding;
|
||||
@@ -249,7 +253,11 @@ export function createControllerConfigForm(options: {
|
||||
|
||||
if (definition.bindingType === 'axis') {
|
||||
renderAxisStickRow(definition, binding as ResolvedControllerAxisBinding, learningActionId);
|
||||
renderAxisDpadRow(definition, binding as ResolvedControllerAxisBinding, dpadLearningActionId);
|
||||
renderAxisDpadRow(
|
||||
definition,
|
||||
binding as ResolvedControllerAxisBinding,
|
||||
dpadLearningActionId,
|
||||
);
|
||||
} else {
|
||||
renderDiscreteRow(definition, binding, learningActionId);
|
||||
}
|
||||
@@ -265,7 +273,12 @@ export function createControllerConfigForm(options: {
|
||||
const isExpanded = expandedRowKey === rowKey;
|
||||
const isLearning = learningActionId === definition.id;
|
||||
|
||||
const row = createRow(definition.label, formatFriendlyBindingLabel(binding), binding.kind === 'none', isExpanded);
|
||||
const row = createRow(
|
||||
definition.label,
|
||||
formatFriendlyBindingLabel(binding),
|
||||
binding.kind === 'none',
|
||||
isExpanded,
|
||||
);
|
||||
row.addEventListener('click', () => {
|
||||
expandedRowKey = expandedRowKey === rowKey ? null : rowKey;
|
||||
render();
|
||||
@@ -277,9 +290,18 @@ export function createControllerConfigForm(options: {
|
||||
? 'Press a button, trigger, or move a stick\u2026'
|
||||
: `Currently: ${formatControllerBindingSummary(binding)}`;
|
||||
const panel = createEditPanel(hint, isLearning, {
|
||||
onLearn: (e) => { e.stopPropagation(); options.onLearn(definition.id, definition.bindingType); },
|
||||
onClear: (e) => { e.stopPropagation(); options.onClear(definition.id); },
|
||||
onReset: (e) => { e.stopPropagation(); options.onReset(definition.id); },
|
||||
onLearn: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onLearn(definition.id, definition.bindingType);
|
||||
},
|
||||
onClear: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onClear(definition.id);
|
||||
},
|
||||
onReset: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onReset(definition.id);
|
||||
},
|
||||
});
|
||||
options.container.appendChild(panel);
|
||||
}
|
||||
@@ -294,7 +316,12 @@ export function createControllerConfigForm(options: {
|
||||
const isExpanded = expandedRowKey === rowKey;
|
||||
const isLearning = learningActionId === definition.id;
|
||||
|
||||
const row = createRow(`${definition.label} (Stick)`, formatFriendlyStickLabel(binding), binding.kind === 'none', isExpanded);
|
||||
const row = createRow(
|
||||
`${definition.label} (Stick)`,
|
||||
formatFriendlyStickLabel(binding),
|
||||
binding.kind === 'none',
|
||||
isExpanded,
|
||||
);
|
||||
row.addEventListener('click', () => {
|
||||
expandedRowKey = expandedRowKey === rowKey ? null : rowKey;
|
||||
render();
|
||||
@@ -305,9 +332,18 @@ export function createControllerConfigForm(options: {
|
||||
const summary = binding.kind === 'none' ? 'Disabled' : `Axis ${binding.axisIndex}`;
|
||||
const hint = isLearning ? 'Move a stick or trigger\u2026' : `Currently: ${summary}`;
|
||||
const panel = createEditPanel(hint, isLearning, {
|
||||
onLearn: (e) => { e.stopPropagation(); options.onLearn(definition.id, 'axis'); },
|
||||
onClear: (e) => { e.stopPropagation(); options.onClear(definition.id); },
|
||||
onReset: (e) => { e.stopPropagation(); options.onReset(definition.id); },
|
||||
onLearn: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onLearn(definition.id, 'axis');
|
||||
},
|
||||
onClear: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onClear(definition.id);
|
||||
},
|
||||
onReset: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onReset(definition.id);
|
||||
},
|
||||
});
|
||||
options.container.appendChild(panel);
|
||||
}
|
||||
@@ -322,9 +358,15 @@ export function createControllerConfigForm(options: {
|
||||
const isExpanded = expandedRowKey === rowKey;
|
||||
const isLearning = dpadLearningActionId === definition.id;
|
||||
|
||||
const dpadFallback: ControllerDpadFallback = binding.kind === 'none' ? 'none' : binding.dpadFallback;
|
||||
const dpadFallback: ControllerDpadFallback =
|
||||
binding.kind === 'none' ? 'none' : binding.dpadFallback;
|
||||
const badgeText = DPAD_FALLBACK_LABELS[dpadFallback];
|
||||
const row = createRow(`${definition.label} (D-pad)`, badgeText, dpadFallback === 'none', isExpanded);
|
||||
const row = createRow(
|
||||
`${definition.label} (D-pad)`,
|
||||
badgeText,
|
||||
dpadFallback === 'none',
|
||||
isExpanded,
|
||||
);
|
||||
row.addEventListener('click', () => {
|
||||
expandedRowKey = expandedRowKey === rowKey ? null : rowKey;
|
||||
render();
|
||||
@@ -336,15 +378,29 @@ export function createControllerConfigForm(options: {
|
||||
? 'Press a D-pad direction\u2026'
|
||||
: `Currently: ${DPAD_FALLBACK_LABELS[dpadFallback]}`;
|
||||
const panel = createEditPanel(hint, isLearning, {
|
||||
onLearn: (e) => { e.stopPropagation(); options.onDpadLearn(definition.id); },
|
||||
onClear: (e) => { e.stopPropagation(); options.onDpadClear(definition.id); },
|
||||
onReset: (e) => { e.stopPropagation(); options.onDpadReset(definition.id); },
|
||||
onLearn: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onDpadLearn(definition.id);
|
||||
},
|
||||
onClear: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onDpadClear(definition.id);
|
||||
},
|
||||
onReset: (e) => {
|
||||
e.stopPropagation();
|
||||
options.onDpadReset(definition.id);
|
||||
},
|
||||
});
|
||||
options.container.appendChild(panel);
|
||||
}
|
||||
}
|
||||
|
||||
function createRow(labelText: string, badgeText: string, isDisabled: boolean, isExpanded: boolean): HTMLDivElement {
|
||||
function createRow(
|
||||
labelText: string,
|
||||
badgeText: string,
|
||||
isDisabled: boolean,
|
||||
isExpanded: boolean,
|
||||
): HTMLDivElement {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'controller-config-row';
|
||||
if (isExpanded) row.classList.add('expanded');
|
||||
|
||||
@@ -66,7 +66,10 @@ function createFakeElement() {
|
||||
if (!match) return null;
|
||||
const testId = match[1];
|
||||
for (const child of el.children) {
|
||||
if (typeof child.getAttribute === 'function' && child.getAttribute('data-testid') === testId) {
|
||||
if (
|
||||
typeof child.getAttribute === 'function' &&
|
||||
child.getAttribute('data-testid') === testId
|
||||
) {
|
||||
return child;
|
||||
}
|
||||
if (typeof child.querySelector === 'function') {
|
||||
@@ -105,7 +108,10 @@ function installFakeDom() {
|
||||
return {
|
||||
restore: () => {
|
||||
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
||||
Object.defineProperty(globalThis, 'document', {
|
||||
configurable: true,
|
||||
value: previousDocument,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ export function createControllerSelectModal(
|
||||
let lastRenderedActiveGamepadId: string | null = null;
|
||||
let lastRenderedPreferredId = '';
|
||||
type ControllerBindingKey = keyof NonNullable<typeof ctx.state.controllerConfig>['bindings'];
|
||||
type ControllerBindingValue =
|
||||
NonNullable<NonNullable<typeof ctx.state.controllerConfig>['bindings']>[ControllerBindingKey];
|
||||
type ControllerBindingValue = NonNullable<
|
||||
NonNullable<typeof ctx.state.controllerConfig>['bindings']
|
||||
>[ControllerBindingKey];
|
||||
let learningActionId: ControllerBindingKey | null = null;
|
||||
let dpadLearningActionId: ControllerBindingKey | null = null;
|
||||
let bindingCapture: ReturnType<typeof createControllerBindingCapture> | null = null;
|
||||
@@ -198,7 +199,9 @@ export function createControllerSelectModal(
|
||||
lastRenderedPreferredId = preferredId;
|
||||
}
|
||||
|
||||
async function saveControllerConfig(update: Parameters<typeof window.electronAPI.saveControllerConfig>[0]) {
|
||||
async function saveControllerConfig(
|
||||
update: Parameters<typeof window.electronAPI.saveControllerConfig>[0],
|
||||
) {
|
||||
await window.electronAPI.saveControllerConfig(update);
|
||||
if (!ctx.state.controllerConfig) return;
|
||||
if (update.preferredGamepadId !== undefined) {
|
||||
@@ -304,7 +307,10 @@ export function createControllerSelectModal(
|
||||
if (result.bindingType === 'dpad') {
|
||||
void saveDpadFallback(result.actionId as ControllerBindingKey, result.dpadDirection);
|
||||
} else {
|
||||
void saveBinding(result.actionId as ControllerBindingKey, result.binding as ControllerBindingValue);
|
||||
void saveBinding(
|
||||
result.actionId as ControllerBindingKey,
|
||||
result.binding as ControllerBindingValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,10 +90,7 @@ test('findActiveSubtitleCueIndex prefers current subtitle timing over near-futur
|
||||
{ startTime: 233.05, endTime: 236, text: 'next' },
|
||||
];
|
||||
|
||||
assert.equal(
|
||||
findActiveSubtitleCueIndex(cues, { text: 'previous', startTime: 231 }, 233, 0),
|
||||
0,
|
||||
);
|
||||
assert.equal(findActiveSubtitleCueIndex(cues, { text: 'previous', startTime: 231 }, 233, 0), 0);
|
||||
});
|
||||
|
||||
test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback', async () => {
|
||||
@@ -1217,10 +1214,22 @@ test('subtitle sidebar polling schedules serialized timeouts instead of interval
|
||||
assert.equal(timeoutCount > 0, true);
|
||||
assert.equal(intervalCount, 0);
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'setTimeout', { configurable: true, value: previousSetTimeout });
|
||||
Object.defineProperty(globalThis, 'clearTimeout', { configurable: true, value: previousClearTimeout });
|
||||
Object.defineProperty(globalThis, 'setInterval', { configurable: true, value: previousSetInterval });
|
||||
Object.defineProperty(globalThis, 'clearInterval', { configurable: true, value: previousClearInterval });
|
||||
Object.defineProperty(globalThis, 'setTimeout', {
|
||||
configurable: true,
|
||||
value: previousSetTimeout,
|
||||
});
|
||||
Object.defineProperty(globalThis, 'clearTimeout', {
|
||||
configurable: true,
|
||||
value: previousClearTimeout,
|
||||
});
|
||||
Object.defineProperty(globalThis, 'setInterval', {
|
||||
configurable: true,
|
||||
value: previousSetInterval,
|
||||
});
|
||||
Object.defineProperty(globalThis, 'clearInterval', {
|
||||
configurable: true,
|
||||
value: previousClearInterval,
|
||||
});
|
||||
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
||||
}
|
||||
@@ -1564,17 +1573,13 @@ test('subtitle sidebar embedded layout reserves and releases mpv right margin',
|
||||
assert.ok(
|
||||
mpvCommands.some(
|
||||
(command) =>
|
||||
command[0] === 'set_property' &&
|
||||
command[1] === 'osd-align-x' &&
|
||||
command[2] === 'left',
|
||||
command[0] === 'set_property' && command[1] === 'osd-align-x' && command[2] === 'left',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
mpvCommands.some(
|
||||
(command) =>
|
||||
command[0] === 'set_property' &&
|
||||
command[1] === 'osd-align-y' &&
|
||||
command[2] === 'top',
|
||||
command[0] === 'set_property' && command[1] === 'osd-align-y' && command[2] === 'top',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
@@ -1597,7 +1602,11 @@ test('subtitle sidebar embedded layout reserves and releases mpv right margin',
|
||||
assert.deepEqual(mpvCommands.at(-5), ['set_property', 'video-margin-ratio-right', 0]);
|
||||
assert.deepEqual(mpvCommands.at(-4), ['set_property', 'osd-align-x', 'left']);
|
||||
assert.deepEqual(mpvCommands.at(-3), ['set_property', 'osd-align-y', 'top']);
|
||||
assert.deepEqual(mpvCommands.at(-2), ['set_property', 'user-data/osc/margins', '{"l":0,"r":0,"t":0,"b":0}']);
|
||||
assert.deepEqual(mpvCommands.at(-2), [
|
||||
'set_property',
|
||||
'user-data/osc/margins',
|
||||
'{"l":0,"r":0,"t":0,"b":0}',
|
||||
]);
|
||||
assert.deepEqual(mpvCommands.at(-1), ['set_property', 'video-pan-x', 0]);
|
||||
assert.equal(bodyClassList.contains('subtitle-sidebar-embedded-open'), false);
|
||||
assert.deepEqual(rootStyleCalls.at(-1), ['--subtitle-sidebar-reserved-width', '0px']);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import type {
|
||||
SubtitleCue,
|
||||
SubtitleData,
|
||||
SubtitleSidebarSnapshot,
|
||||
} from '../../types';
|
||||
import type { SubtitleCue, SubtitleData, SubtitleSidebarSnapshot } from '../../types';
|
||||
import type { ModalStateReader, RendererContext } from '../context';
|
||||
import { syncOverlayMouseIgnoreState } from '../overlay-mouse-ignore.js';
|
||||
|
||||
@@ -76,8 +72,7 @@ export function findActiveSubtitleCueIndex(
|
||||
if (typeof currentTimeSec === 'number' && Number.isFinite(currentTimeSec)) {
|
||||
const activeOrUpcomingCue = cues.findIndex(
|
||||
(cue) =>
|
||||
cue.endTime > currentTimeSec &&
|
||||
cue.startTime <= currentTimeSec + ACTIVE_CUE_LOOKAHEAD_SEC,
|
||||
cue.endTime > currentTimeSec && cue.startTime <= currentTimeSec + ACTIVE_CUE_LOOKAHEAD_SEC,
|
||||
);
|
||||
if (activeOrUpcomingCue >= 0) {
|
||||
return activeOrUpcomingCue;
|
||||
@@ -109,8 +104,7 @@ export function findActiveSubtitleCueIndex(
|
||||
return -1;
|
||||
}
|
||||
|
||||
const hasTiming =
|
||||
typeof current.startTime === 'number' && Number.isFinite(current.startTime);
|
||||
const hasTiming = typeof current.startTime === 'number' && Number.isFinite(current.startTime);
|
||||
|
||||
if (preferredCueIndex >= 0) {
|
||||
if (!hasTiming && currentTimeSec === null) {
|
||||
@@ -213,16 +207,8 @@ export function createSubtitleSidebarModal(
|
||||
'video-margin-ratio-right',
|
||||
Number(ratio.toFixed(4)),
|
||||
]);
|
||||
window.electronAPI.sendMpvCommand([
|
||||
'set_property',
|
||||
'osd-align-x',
|
||||
'left',
|
||||
]);
|
||||
window.electronAPI.sendMpvCommand([
|
||||
'set_property',
|
||||
'osd-align-y',
|
||||
'top',
|
||||
]);
|
||||
window.electronAPI.sendMpvCommand(['set_property', 'osd-align-x', 'left']);
|
||||
window.electronAPI.sendMpvCommand(['set_property', 'osd-align-y', 'top']);
|
||||
window.electronAPI.sendMpvCommand([
|
||||
'set_property',
|
||||
'user-data/osc/margins',
|
||||
@@ -302,13 +288,14 @@ export function createSubtitleSidebarModal(
|
||||
}
|
||||
|
||||
const list = ctx.dom.subtitleSidebarList;
|
||||
const active = list.children[ctx.state.subtitleSidebarActiveCueIndex] as HTMLElement | undefined;
|
||||
const active = list.children[ctx.state.subtitleSidebarActiveCueIndex] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetScrollTop =
|
||||
active.offsetTop - (list.clientHeight - active.clientHeight) / 2;
|
||||
const targetScrollTop = active.offsetTop - (list.clientHeight - active.clientHeight) / 2;
|
||||
const nextScrollTop = Math.max(0, targetScrollTop);
|
||||
if (previousActiveCueIndex < 0) {
|
||||
list.scrollTop = nextScrollTop;
|
||||
@@ -363,9 +350,9 @@ export function createSubtitleSidebarModal(
|
||||
}
|
||||
|
||||
if (ctx.state.subtitleSidebarActiveCueIndex >= 0) {
|
||||
const current = ctx.dom.subtitleSidebarList.children[ctx.state.subtitleSidebarActiveCueIndex] as
|
||||
| HTMLElement
|
||||
| undefined;
|
||||
const current = ctx.dom.subtitleSidebarList.children[
|
||||
ctx.state.subtitleSidebarActiveCueIndex
|
||||
] as HTMLElement | undefined;
|
||||
current?.classList.add('active');
|
||||
}
|
||||
}
|
||||
@@ -476,7 +463,11 @@ export function createSubtitleSidebarModal(
|
||||
|
||||
async function autoOpenSubtitleSidebarOnStartup(): Promise<void> {
|
||||
const snapshot = await refreshSnapshot();
|
||||
if (!snapshot.config.enabled || !snapshot.config.autoOpen || ctx.state.subtitleSidebarModalOpen) {
|
||||
if (
|
||||
!snapshot.config.enabled ||
|
||||
!snapshot.config.autoOpen ||
|
||||
ctx.state.subtitleSidebarModalOpen
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await openSubtitleSidebarModal();
|
||||
@@ -512,10 +503,7 @@ export function createSubtitleSidebarModal(
|
||||
return;
|
||||
}
|
||||
|
||||
updateActiveCue(
|
||||
{ text: data.text, startTime: data.startTime },
|
||||
data.startTime ?? null,
|
||||
);
|
||||
updateActiveCue({ text: data.text, startTime: data.startTime }, data.startTime ?? null);
|
||||
}
|
||||
|
||||
function wireDomEvents(): void {
|
||||
|
||||
@@ -28,13 +28,13 @@ export function createYoutubeTrackPickerModal(
|
||||
function setStatus(message: string, isError = false): void {
|
||||
ctx.state.youtubePickerStatus = message;
|
||||
ctx.dom.youtubePickerStatus.textContent = message;
|
||||
ctx.dom.youtubePickerStatus.style.color = isError
|
||||
? '#ed8796'
|
||||
: '#a5adcb';
|
||||
ctx.dom.youtubePickerStatus.style.color = isError ? '#ed8796' : '#a5adcb';
|
||||
}
|
||||
|
||||
function getTrackLabel(trackId: string): string {
|
||||
return ctx.state.youtubePickerPayload?.tracks.find((track) => track.id === trackId)?.label ?? '';
|
||||
return (
|
||||
ctx.state.youtubePickerPayload?.tracks.find((track) => track.id === trackId)?.label ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
function renderTrackList(): void {
|
||||
@@ -82,10 +82,7 @@ export function createYoutubeTrackPickerModal(
|
||||
if (track.id === primaryTrackId) continue;
|
||||
ctx.dom.youtubePickerSecondarySelect.appendChild(createOption(track.id, track.label));
|
||||
}
|
||||
if (
|
||||
primaryTrackId &&
|
||||
ctx.dom.youtubePickerSecondarySelect.value === primaryTrackId
|
||||
) {
|
||||
if (primaryTrackId && ctx.dom.youtubePickerSecondarySelect.value === primaryTrackId) {
|
||||
ctx.dom.youtubePickerSecondarySelect.value = '';
|
||||
}
|
||||
}
|
||||
@@ -126,7 +123,9 @@ export function createYoutubeTrackPickerModal(
|
||||
setStatus('Select the subtitle tracks to download.');
|
||||
}
|
||||
|
||||
async function resolveSelection(action: 'use-selected' | 'continue-without-subtitles'): Promise<void> {
|
||||
async function resolveSelection(
|
||||
action: 'use-selected' | 'continue-without-subtitles',
|
||||
): Promise<void> {
|
||||
if (resolveSelectionInFlight) {
|
||||
return;
|
||||
}
|
||||
@@ -238,7 +237,9 @@ export function createYoutubeTrackPickerModal(
|
||||
return true;
|
||||
}
|
||||
void resolveSelection(
|
||||
payloadHasTracks(ctx.state.youtubePickerPayload) ? 'use-selected' : 'continue-without-subtitles',
|
||||
payloadHasTracks(ctx.state.youtubePickerPayload)
|
||||
? 'use-selected'
|
||||
: 'continue-without-subtitles',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -269,7 +270,9 @@ export function createYoutubeTrackPickerModal(
|
||||
|
||||
ctx.dom.youtubePickerContinueButton.addEventListener('click', () => {
|
||||
void resolveSelection(
|
||||
payloadHasTracks(ctx.state.youtubePickerPayload) ? 'use-selected' : 'continue-without-subtitles',
|
||||
payloadHasTracks(ctx.state.youtubePickerPayload)
|
||||
? 'use-selected'
|
||||
: 'continue-without-subtitles',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ function isBlockingOverlayModalOpen(state: RendererState): boolean {
|
||||
|
||||
return Boolean(
|
||||
state.controllerSelectModalOpen ||
|
||||
state.controllerDebugModalOpen ||
|
||||
state.jimakuModalOpen ||
|
||||
state.youtubePickerModalOpen ||
|
||||
state.kikuModalOpen ||
|
||||
state.runtimeOptionsModalOpen ||
|
||||
state.subsyncModalOpen ||
|
||||
state.sessionHelpModalOpen ||
|
||||
(state.subtitleSidebarModalOpen && !embeddedSidebarOpen),
|
||||
state.controllerDebugModalOpen ||
|
||||
state.jimakuModalOpen ||
|
||||
state.youtubePickerModalOpen ||
|
||||
state.kikuModalOpen ||
|
||||
state.runtimeOptionsModalOpen ||
|
||||
state.subsyncModalOpen ||
|
||||
state.sessionHelpModalOpen ||
|
||||
(state.subtitleSidebarModalOpen && !embeddedSidebarOpen),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -552,8 +552,14 @@ async function init(): Promise<void> {
|
||||
|
||||
ctx.dom.subtitleContainer.addEventListener('mouseenter', mouseHandlers.handlePrimaryMouseEnter);
|
||||
ctx.dom.subtitleContainer.addEventListener('mouseleave', mouseHandlers.handlePrimaryMouseLeave);
|
||||
ctx.dom.secondarySubContainer.addEventListener('mouseenter', mouseHandlers.handleSecondaryMouseEnter);
|
||||
ctx.dom.secondarySubContainer.addEventListener('mouseleave', mouseHandlers.handleSecondaryMouseLeave);
|
||||
ctx.dom.secondarySubContainer.addEventListener(
|
||||
'mouseenter',
|
||||
mouseHandlers.handleSecondaryMouseEnter,
|
||||
);
|
||||
ctx.dom.secondarySubContainer.addEventListener(
|
||||
'mouseleave',
|
||||
mouseHandlers.handleSecondaryMouseLeave,
|
||||
);
|
||||
|
||||
mouseHandlers.setupResizeHandler();
|
||||
mouseHandlers.setupPointerTracking();
|
||||
|
||||
@@ -296,7 +296,7 @@ body {
|
||||
.youtube-picker-content {
|
||||
width: min(820px, 92%);
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(198, 160, 246, 0.10), transparent 34%),
|
||||
radial-gradient(circle at top right, rgba(198, 160, 246, 0.1), transparent 34%),
|
||||
linear-gradient(180deg, rgba(36, 39, 58, 0.98), rgba(30, 32, 48, 0.98));
|
||||
border-color: rgba(138, 173, 244, 0.25);
|
||||
}
|
||||
@@ -1342,8 +1342,14 @@ iframe[id^='yomitan-popup'] {
|
||||
}
|
||||
|
||||
@keyframes configEditSlideIn {
|
||||
from { max-height: 0; opacity: 0; }
|
||||
to { max-height: 120px; opacity: 1; }
|
||||
from {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
max-height: 120px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.controller-config-edit-inner {
|
||||
@@ -1365,8 +1371,13 @@ iframe[id^='yomitan-popup'] {
|
||||
}
|
||||
|
||||
@keyframes configLearnPulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.controller-config-edit-actions {
|
||||
@@ -1404,7 +1415,9 @@ iframe[id^='yomitan-popup'] {
|
||||
color: #6e738d;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 120ms ease, color 120ms ease;
|
||||
transition:
|
||||
background 120ms ease,
|
||||
color 120ms ease;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
@@ -1497,14 +1510,13 @@ body.subtitle-sidebar-embedded-open .subtitle-sidebar-modal {
|
||||
max-height: calc(100vh - 28px);
|
||||
height: auto;
|
||||
margin-left: auto;
|
||||
font-family:
|
||||
var(
|
||||
--subtitle-sidebar-font-family,
|
||||
'M PLUS 1',
|
||||
'Noto Sans CJK JP',
|
||||
'Hiragino Sans',
|
||||
sans-serif
|
||||
);
|
||||
font-family: var(
|
||||
--subtitle-sidebar-font-family,
|
||||
'M PLUS 1',
|
||||
'Noto Sans CJK JP',
|
||||
'Hiragino Sans',
|
||||
sans-serif
|
||||
);
|
||||
font-size: var(--subtitle-sidebar-font-size, 16px);
|
||||
background: var(--subtitle-sidebar-background-color, rgba(73, 77, 100, 0.9));
|
||||
color: var(--subtitle-sidebar-text-color, #cad3f5);
|
||||
|
||||
@@ -981,18 +981,9 @@ test('JLPT CSS rules use underline-only styling in renderer stylesheet', () => {
|
||||
cssText,
|
||||
'body.subtitle-sidebar-embedded-open #secondarySubContainer.secondary-sub-hover',
|
||||
);
|
||||
assert.match(
|
||||
secondaryEmbeddedHoverBlock,
|
||||
/right:\s*var\(--subtitle-sidebar-reserved-width\);/,
|
||||
);
|
||||
assert.match(
|
||||
secondaryEmbeddedHoverBlock,
|
||||
/max-width:\s*none;/,
|
||||
);
|
||||
assert.match(
|
||||
secondaryEmbeddedHoverBlock,
|
||||
/transform:\s*none;/,
|
||||
);
|
||||
assert.match(secondaryEmbeddedHoverBlock, /right:\s*var\(--subtitle-sidebar-reserved-width\);/);
|
||||
assert.match(secondaryEmbeddedHoverBlock, /max-width:\s*none;/);
|
||||
assert.match(secondaryEmbeddedHoverBlock, /transform:\s*none;/);
|
||||
assert.doesNotMatch(
|
||||
secondaryEmbeddedHoverBlock,
|
||||
/transform:\s*translateX\(calc\(var\(--subtitle-sidebar-reserved-width\)\s*\*\s*-0\.5\)\);/,
|
||||
|
||||
Reference in New Issue
Block a user