Skip to content

Commit 9168228

Browse files
feat: applying timestamp by delta (#206)
* feat: applying timestamp by delta * feat: maintain the current synchronization relationship when applying delta * fix: a logical bug that can cause unexpected accumulation
1 parent ecf7fa4 commit 9168228

File tree

1 file changed

+52
-10
lines changed

1 file changed

+52
-10
lines changed

src/components/RibbonBar/edit-mode.tsx

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,27 +217,69 @@ function EditField<
217217
try {
218218
const selectedItems = store.get(itemAtom);
219219
if (fieldName === "endTime" && showDurationInput) {
220-
const durationValue = Number(rawValue.trim());
221-
if (!Number.isFinite(durationValue) || durationValue <= 0) return;
220+
const trimmedValue = rawValue.trim();
221+
const isDelta = trimmedValue.startsWith("+") || trimmedValue.startsWith("-");
222+
const parsedValue = Number(trimmedValue);
223+
if (!Number.isFinite(parsedValue)) return;
224+
if (!isDelta && parsedValue <= 0) return;
222225
editLyricLines((state) => {
223226
for (const line of state.lyricLines) {
224227
if (isWordField) {
228+
const updates = new Map<string, { startTime?: number, endTime?: number }>();
229+
230+
// First pass: Calculate all new end times for selected words
225231
for (let wordIndex = 0; wordIndex < line.words.length; wordIndex++) {
226232
const word = line.words[wordIndex];
227233
if (!selectedItems.has(word.id)) continue;
234+
228235
const nextWord = line.words[wordIndex + 1];
229236
const nextStartTime = nextWord?.startTime;
230-
const newEndTime = word.startTime + durationValue;
231-
if (
232-
typeof nextStartTime === "number" &&
233-
newEndTime < nextStartTime
234-
) {
235-
continue;
237+
const originalEndTime = word.endTime;
238+
239+
// Calculate new end time
240+
const newEndTimeRaw = isDelta ? word.endTime + parsedValue : word.startTime + parsedValue;
241+
const newEndTime = Math.max(word.startTime, newEndTimeRaw);
242+
243+
// Store the update for the current word
244+
const wordUpdate = updates.get(word.id) || {};
245+
wordUpdate.endTime = newEndTime;
246+
updates.set(word.id, wordUpdate);
247+
248+
// If it was synchronized, store the start time update for the next word
249+
if (isDelta && nextWord && originalEndTime === nextStartTime) {
250+
// We only move nextWord's startTime if the new end time doesn't exceed its original end time
251+
// to avoid inverting its duration (unless it's also selected, handled below)
252+
const nextWordOriginalEndTime = nextWord.endTime;
253+
if (newEndTime <= nextWordOriginalEndTime || selectedItems.has(nextWord.id)) {
254+
const nextUpdate = updates.get(nextWord.id) || {};
255+
nextUpdate.startTime = newEndTime;
256+
// Don't auto-fix nextWord.endTime here, let the second pass or its own delta fix it
257+
updates.set(nextWord.id, nextUpdate);
258+
}
259+
}
260+
}
261+
262+
// Second pass: Apply updates and ensure durations are valid
263+
for (let wordIndex = 0; wordIndex < line.words.length; wordIndex++) {
264+
const word = line.words[wordIndex];
265+
const update = updates.get(word.id);
266+
267+
if (update) {
268+
if (update.startTime !== undefined) {
269+
word.startTime = update.startTime;
270+
}
271+
if (update.endTime !== undefined) {
272+
word.endTime = update.endTime;
273+
}
274+
// Ensure valid duration after applying updates
275+
if (word.endTime < word.startTime) {
276+
word.endTime = word.startTime;
277+
}
236278
}
237-
word.endTime = newEndTime;
238279
}
239280
} else if (selectedItems.has(line.id)) {
240-
line.endTime = line.startTime + durationValue;
281+
const newEndTimeRaw = isDelta ? line.endTime + parsedValue : line.startTime + parsedValue;
282+
line.endTime = Math.max(line.startTime, newEndTimeRaw);
241283
}
242284
}
243285
return state;

0 commit comments

Comments
 (0)