mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
- 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.
137 lines
4.3 KiB
TypeScript
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,
|
|
};
|
|
}
|