83 lines
3.8 KiB
JavaScript
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)];
|
|
}
|