Files
matrix-screen-controller/核桃派软件源代码/app/static/animation-ui-model.js
T

83 lines
3.8 KiB
JavaScript

export const MIN_FRAME_DURATION_MS = 50;
export const MAX_FRAME_DURATION_MS = 604800000;
export function effectiveFrameName(animationName, frame, position = frame?.position) {
const customName = typeof frame?.name === "string" ? frame.name.trim() : "";
if (customName) return customName;
return `${String(animationName || "动图").trim() || "动图"}-第${position}帧`;
}
export function animationEditContextName(animationName, frame, position = frame?.position) {
const normalizedAnimationName = String(animationName || "动图").trim() || "动图";
const customName = typeof frame?.name === "string" ? frame.name.trim() : "";
return `${normalizedAnimationName}-${customName || `第${position}帧`}`;
}
export function animationPreviewFrameAtElapsed(frames, elapsedMs) {
if (!Array.isArray(frames) || !frames.length) return null;
const durations = frames.map((frame) => Number(frame?.duration_ms));
if (durations.some((duration) => !Number.isFinite(duration) || duration <= 0)) return null;
const totalDurationMs = durations.reduce((total, duration) => total + duration, 0);
const normalizedElapsed = Number.isFinite(Number(elapsedMs)) ? Number(elapsedMs) : 0;
const cycleElapsedMs = ((normalizedElapsed % totalDurationMs) + totalDurationMs) % totalDurationMs;
let boundary = 0;
for (let index = 0; index < frames.length; index += 1) {
boundary += durations[index];
if (cycleElapsedMs < boundary) {
return {
frame: frames[index],
index,
remainingMs: boundary - cycleElapsedMs,
totalDurationMs,
};
}
}
return null;
}
export function formatFrameDuration(durationMs, unit = "ms") {
const duration = Number(durationMs);
if (!Number.isInteger(duration)) return "";
if (unit === "ms") return String(duration);
if (unit !== "s") throw new Error("unknown animation duration unit");
return (duration / 1000).toFixed(3).replace(/\.?0+$/, "");
}
export function validateFrameDuration(value, unit = "ms") {
const normalized = typeof value === "string" ? value.trim() : String(value ?? "").trim();
const millisecondsPattern = /^\d+$/;
const secondsPattern = /^\d+(?:\.\d{1,3})?$/;
if (!normalized || (unit === "ms" ? !millisecondsPattern.test(normalized) : !secondsPattern.test(normalized))) {
return {
valid: false,
duration: null,
error: unit === "ms"
? `请输入 ${MIN_FRAME_DURATION_MS}..${MAX_FRAME_DURATION_MS} 之间的整数毫秒值。`
: "请输入 0.050..604800 之间、最多三位小数的秒数。",
};
}
const numeric = Number(normalized);
const duration = unit === "s" ? numeric * 1000 : numeric;
if (!Number.isInteger(duration) || duration < MIN_FRAME_DURATION_MS || duration > MAX_FRAME_DURATION_MS) {
return {
valid: false,
duration: null,
error: unit === "ms"
? `请输入 ${MIN_FRAME_DURATION_MS}..${MAX_FRAME_DURATION_MS} 之间的整数毫秒值。`
: "请输入 0.050..604800 之间、最多三位小数的秒数。",
};
}
return { valid: true, duration, error: "" };
}
export function reorderSelectedFrameIds(frameIds, selectedFrameIds, insertAfterFrameId = null) {
if (!Array.isArray(frameIds) || !Array.isArray(selectedFrameIds) || !selectedFrameIds.length) return null;
const selected = new Set(selectedFrameIds);
if (selected.size !== selectedFrameIds.length || selectedFrameIds.some((id) => !frameIds.includes(id))) return null;
if (insertAfterFrameId !== null && (!frameIds.includes(insertAfterFrameId) || selected.has(insertAfterFrameId))) return null;
const moving = frameIds.filter((id) => selected.has(id));
const remaining = frameIds.filter((id) => !selected.has(id));
const insertAt = insertAfterFrameId === null ? 0 : remaining.indexOf(insertAfterFrameId) + 1;
return [...remaining.slice(0, insertAt), ...moving, ...remaining.slice(insertAt)];
}