Files
SubMiner/src/renderer/positioning/position-state.ts
sudacode 3e445aee9e chore: archive refactor milestones and remove structural quality-gates task
- Remove structural quality gates task and references from task-27 roadmap.
- Remove structural-gates-adjacent work from scripts/positioning cleanup context, including check-main-lines adjustments.
- Archive completed backlog tasks 11 and 27.7 by moving them to completed directory.
- Finish task-27.5 module split by moving/anonymizing anki-integration and renderer positioning files into their dedicated directories and updating paths.
2026-02-15 17:48:08 -08:00

137 lines
4.3 KiB
TypeScript

import type { SubtitlePosition } from "../../types";
import type { RendererContext } from "../context";
const PREFERRED_Y_PERCENT_MIN = 2;
const PREFERRED_Y_PERCENT_MAX = 80;
export type SubtitlePositionController = {
applyStoredSubtitlePosition: (position: SubtitlePosition | null, source: string) => void;
getCurrentYPercent: () => number;
applyYPercent: (yPercent: number) => void;
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 getPersistedYPercent(
ctx: RendererContext,
position: SubtitlePosition | null,
): number {
if (!position || typeof position.yPercent !== "number" || !Number.isFinite(position.yPercent)) {
return ctx.state.persistedSubtitlePosition.yPercent;
}
return position.yPercent;
}
function getPersistedOffset(
ctx: RendererContext,
position: SubtitlePosition | null,
key: "invisibleOffsetXPx" | "invisibleOffsetYPx",
): number {
if (
position &&
typeof position[key] === "number" &&
Number.isFinite(position[key])
) {
return position[key];
}
return 0;
}
function updatePersistedSubtitlePosition(
ctx: RendererContext,
position: SubtitlePosition | null,
): void {
ctx.state.persistedSubtitlePosition = {
yPercent: getPersistedYPercent(ctx, position),
invisibleOffsetXPx: getPersistedOffset(ctx, position, "invisibleOffsetXPx"),
invisibleOffsetYPx: getPersistedOffset(ctx, position, "invisibleOffsetYPx"),
};
}
function getNextPersistedPosition(
ctx: RendererContext,
patch: Partial<SubtitlePosition>,
): SubtitlePosition {
return {
yPercent:
typeof patch.yPercent === "number" && Number.isFinite(patch.yPercent)
? patch.yPercent
: ctx.state.persistedSubtitlePosition.yPercent,
invisibleOffsetXPx:
typeof patch.invisibleOffsetXPx === "number" &&
Number.isFinite(patch.invisibleOffsetXPx)
? patch.invisibleOffsetXPx
: ctx.state.persistedSubtitlePosition.invisibleOffsetXPx ?? 0,
invisibleOffsetYPx:
typeof patch.invisibleOffsetYPx === "number" &&
Number.isFinite(patch.invisibleOffsetYPx)
? patch.invisibleOffsetYPx
: ctx.state.persistedSubtitlePosition.invisibleOffsetYPx ?? 0,
};
}
export function createInMemorySubtitlePositionController(
ctx: RendererContext,
): SubtitlePositionController {
function getCurrentYPercent(): number {
if (ctx.state.currentYPercent !== null) {
return ctx.state.currentYPercent;
}
const marginBottom = parseFloat(ctx.dom.subtitleContainer.style.marginBottom) || 60;
ctx.state.currentYPercent = clampYPercent((marginBottom / window.innerHeight) * 100);
return ctx.state.currentYPercent;
}
function applyYPercent(yPercent: number): void {
const clampedPercent = clampYPercent(yPercent);
ctx.state.currentYPercent = clampedPercent;
const marginBottom = (clampedPercent / 100) * window.innerHeight;
ctx.dom.subtitleContainer.style.position = "";
ctx.dom.subtitleContainer.style.left = "";
ctx.dom.subtitleContainer.style.top = "";
ctx.dom.subtitleContainer.style.right = "";
ctx.dom.subtitleContainer.style.transform = "";
ctx.dom.subtitleContainer.style.marginBottom = `${marginBottom}px`;
}
function persistSubtitlePositionPatch(patch: Partial<SubtitlePosition>): void {
const nextPosition = getNextPersistedPosition(ctx, patch);
ctx.state.persistedSubtitlePosition = nextPosition;
window.electronAPI.saveSubtitlePosition(nextPosition);
}
function applyStoredSubtitlePosition(position: SubtitlePosition | null, source: string): void {
updatePersistedSubtitlePosition(ctx, position);
if (position && position.yPercent !== undefined) {
applyYPercent(position.yPercent);
console.log(
"Applied subtitle position from",
source,
":",
position.yPercent,
"%",
);
return;
}
const defaultMarginBottom = 60;
const defaultYPercent = (defaultMarginBottom / window.innerHeight) * 100;
applyYPercent(defaultYPercent);
console.log("Applied default subtitle position from", source);
}
return {
applyStoredSubtitlePosition,
getCurrentYPercent,
applyYPercent,
persistSubtitlePositionPatch,
};
}