mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-22 00:11:27 -07:00
Prepare Windows release and signing process (#16)
This commit is contained in:
83
src/renderer/positioning/position-state.test.ts
Normal file
83
src/renderer/positioning/position-state.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { createInMemorySubtitlePositionController } from './position-state.js';
|
||||
|
||||
function withWindow<T>(windowValue: unknown, callback: () => T): T {
|
||||
const previousWindow = (globalThis as { window?: unknown }).window;
|
||||
Object.defineProperty(globalThis, 'window', {
|
||||
configurable: true,
|
||||
value: windowValue,
|
||||
});
|
||||
|
||||
try {
|
||||
return callback();
|
||||
} finally {
|
||||
Object.defineProperty(globalThis, 'window', {
|
||||
configurable: true,
|
||||
value: previousWindow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createContext(subtitleHeight: number) {
|
||||
return {
|
||||
dom: {
|
||||
subtitleContainer: {
|
||||
style: {
|
||||
position: '',
|
||||
left: '',
|
||||
top: '',
|
||||
right: '',
|
||||
transform: '',
|
||||
marginBottom: '',
|
||||
},
|
||||
offsetHeight: subtitleHeight,
|
||||
},
|
||||
},
|
||||
state: {
|
||||
currentYPercent: null,
|
||||
persistedSubtitlePosition: { yPercent: 10 },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('subtitle position clamp keeps tall subtitles inside the overlay viewport', () => {
|
||||
withWindow(
|
||||
{
|
||||
innerHeight: 1000,
|
||||
electronAPI: {
|
||||
saveSubtitlePosition: () => {},
|
||||
},
|
||||
},
|
||||
() => {
|
||||
const ctx = createContext(300);
|
||||
const controller = createInMemorySubtitlePositionController(ctx as never);
|
||||
|
||||
controller.applyYPercent(80);
|
||||
|
||||
assert.equal(ctx.state.currentYPercent, 68.8);
|
||||
assert.equal(ctx.dom.subtitleContainer.style.marginBottom, '688px');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('subtitle position clamp falls back to the minimum safe inset when subtitle is taller than the viewport', () => {
|
||||
withWindow(
|
||||
{
|
||||
innerHeight: 200,
|
||||
electronAPI: {
|
||||
saveSubtitlePosition: () => {},
|
||||
},
|
||||
},
|
||||
() => {
|
||||
const ctx = createContext(260);
|
||||
const controller = createInMemorySubtitlePositionController(ctx as never);
|
||||
|
||||
controller.applyYPercent(80);
|
||||
|
||||
assert.equal(ctx.state.currentYPercent, 6);
|
||||
assert.equal(ctx.dom.subtitleContainer.style.marginBottom, '12px');
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import type { RendererContext } from '../context';
|
||||
|
||||
const PREFERRED_Y_PERCENT_MIN = 2;
|
||||
const PREFERRED_Y_PERCENT_MAX = 80;
|
||||
const SUBTITLE_EDGE_PADDING_PX = 12;
|
||||
|
||||
export type SubtitlePositionController = {
|
||||
applyStoredSubtitlePosition: (position: SubtitlePosition | null, source: string) => void;
|
||||
@@ -11,8 +12,47 @@ export type SubtitlePositionController = {
|
||||
persistSubtitlePositionPatch: (patch: Partial<SubtitlePosition>) => void;
|
||||
};
|
||||
|
||||
function clampYPercent(yPercent: number): number {
|
||||
return Math.max(PREFERRED_Y_PERCENT_MIN, Math.min(PREFERRED_Y_PERCENT_MAX, yPercent));
|
||||
function getViewportHeight(): number {
|
||||
return Math.max(window.innerHeight || 0, 1);
|
||||
}
|
||||
|
||||
function getSubtitleContainerHeight(ctx: RendererContext): number {
|
||||
const container = ctx.dom.subtitleContainer as HTMLElement & {
|
||||
offsetHeight?: number;
|
||||
getBoundingClientRect?: () => { height?: number };
|
||||
};
|
||||
if (typeof container.offsetHeight === 'number' && Number.isFinite(container.offsetHeight)) {
|
||||
return Math.max(container.offsetHeight, 0);
|
||||
}
|
||||
if (typeof container.getBoundingClientRect === 'function') {
|
||||
const height = container.getBoundingClientRect().height;
|
||||
if (typeof height === 'number' && Number.isFinite(height)) {
|
||||
return Math.max(height, 0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function resolveYPercentClampRange(ctx: RendererContext): { min: number; max: number } {
|
||||
const viewportHeight = getViewportHeight();
|
||||
const subtitleHeight = getSubtitleContainerHeight(ctx);
|
||||
const minPercent = Math.max(PREFERRED_Y_PERCENT_MIN, (SUBTITLE_EDGE_PADDING_PX / viewportHeight) * 100);
|
||||
const maxMarginBottomPx = Math.max(
|
||||
SUBTITLE_EDGE_PADDING_PX,
|
||||
viewportHeight - subtitleHeight - SUBTITLE_EDGE_PADDING_PX,
|
||||
);
|
||||
const maxPercent = Math.min(PREFERRED_Y_PERCENT_MAX, (maxMarginBottomPx / viewportHeight) * 100);
|
||||
|
||||
if (maxPercent < minPercent) {
|
||||
return { min: minPercent, max: minPercent };
|
||||
}
|
||||
|
||||
return { min: minPercent, max: maxPercent };
|
||||
}
|
||||
|
||||
function clampYPercent(ctx: RendererContext, yPercent: number): number {
|
||||
const { min, max } = resolveYPercentClampRange(ctx);
|
||||
return Math.max(min, Math.min(max, yPercent));
|
||||
}
|
||||
|
||||
function getPersistedYPercent(ctx: RendererContext, position: SubtitlePosition | null): number {
|
||||
@@ -53,14 +93,14 @@ export function createInMemorySubtitlePositionController(
|
||||
}
|
||||
|
||||
const marginBottom = parseFloat(ctx.dom.subtitleContainer.style.marginBottom) || 60;
|
||||
ctx.state.currentYPercent = clampYPercent((marginBottom / window.innerHeight) * 100);
|
||||
ctx.state.currentYPercent = clampYPercent(ctx, (marginBottom / getViewportHeight()) * 100);
|
||||
return ctx.state.currentYPercent;
|
||||
}
|
||||
|
||||
function applyYPercent(yPercent: number): void {
|
||||
const clampedPercent = clampYPercent(yPercent);
|
||||
const clampedPercent = clampYPercent(ctx, yPercent);
|
||||
ctx.state.currentYPercent = clampedPercent;
|
||||
const marginBottom = (clampedPercent / 100) * window.innerHeight;
|
||||
const marginBottom = (clampedPercent / 100) * getViewportHeight();
|
||||
|
||||
ctx.dom.subtitleContainer.style.position = '';
|
||||
ctx.dom.subtitleContainer.style.left = '';
|
||||
@@ -85,7 +125,7 @@ export function createInMemorySubtitlePositionController(
|
||||
}
|
||||
|
||||
const defaultMarginBottom = 60;
|
||||
const defaultYPercent = (defaultMarginBottom / window.innerHeight) * 100;
|
||||
const defaultYPercent = (defaultMarginBottom / getViewportHeight()) * 100;
|
||||
applyYPercent(defaultYPercent);
|
||||
console.log('Applied default subtitle position from', source);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user