Skip to content

Commit 9985fd4

Browse files
committed
Handle drag and drop retime operations
1 parent 3105eb3 commit 9985fd4

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

packages/blueprints/src/base/studio/userEditOperations/processIngestData.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,151 @@ async function applyUserOperation(
106106
case DefaultUserOperationsTypes.UPDATE_PROPS:
107107
processUpdateProps(context, mutableIngestRundown, changes.operationTarget, changes)
108108
break
109+
// Handle drag and drop retime operations:
110+
case DefaultUserOperationsTypes.RETIME_PIECE:
111+
processRetimePiece(context, mutableIngestRundown, changes.operationTarget, changes)
112+
break
109113
default:
110114
context.logWarning(`Unknown operation: ${changes.operation.id}`)
111115
}
112116
}
113117

118+
/**
119+
* Process piece retiming operations from the UI.
120+
*
121+
* This function handles drag-and-drop retime operations on pieces in the Sofie Rundown Editor.
122+
* It modifies the underlying ingest data (objectTime and duration) for pieces within parts.
123+
*
124+
* Expected payload formats:
125+
* - { newTime: number, newDuration?: number }
126+
* - { deltaTime: number, deltaDuration?: number }
127+
* - { startTime: number }
128+
* - { timing: { start: number, duration?: number } }
129+
*
130+
* @param context - The ingest data processing context
131+
* @param mutableIngestRundown - The mutable rundown being processed
132+
* @param operationTarget - Target containing partExternalId and pieceExternalId
133+
* @param changes - The user operation change containing timing information
134+
*/
135+
function processRetimePiece(
136+
context: IProcessIngestDataContext,
137+
mutableIngestRundown: BlueprintMutableIngestRundown,
138+
operationTarget: UserOperationTarget,
139+
changes: UserOperationChange<BlueprintsUserOperations | DefaultUserOperations>
140+
) {
141+
// Extract piece timing information from the operation
142+
const operation = changes.operation as any // DefaultUserOperationRetimePiece would be the proper type
143+
144+
context.logDebug('Processing piece retime operation: ' + JSON.stringify(changes, null, 2))
145+
146+
// Ensure we have the required identifiers
147+
if (!operationTarget.partExternalId || !operationTarget.pieceExternalId) {
148+
context.logError('Retime piece operation missing part or piece external ID')
149+
return
150+
}
151+
152+
// Find the part containing the piece
153+
const part = mutableIngestRundown.findPart(operationTarget.partExternalId)
154+
if (!part?.payload) {
155+
context.logError(`Part not found for retime operation: ${operationTarget.partExternalId}`)
156+
return
157+
}
158+
159+
// Parse the part payload to access pieces
160+
const partPayload: any = part.payload
161+
if (!partPayload.pieces || !Array.isArray(partPayload.pieces)) {
162+
context.logError(`Part has no pieces array: ${operationTarget.partExternalId}`)
163+
return
164+
}
165+
166+
context.logDebug('Original partPayload: ' + JSON.stringify(partPayload, null, 2))
167+
168+
// Find the specific piece to retime
169+
const pieceIndex = partPayload.pieces.findIndex((piece: any) => piece.id === operationTarget.pieceExternalId)
170+
if (pieceIndex === -1) {
171+
context.logError(`Piece not found for retime: ${operationTarget.pieceExternalId}`)
172+
return
173+
}
174+
175+
const piece = partPayload.pieces[pieceIndex]
176+
const originalTime = piece.objectTime || 0
177+
178+
// Extract new timing from operation payload
179+
const payload = operation.payload || {}
180+
const newTime = payload.inPoint / 1000 // Convert milliseconds to seconds for comparison
181+
182+
// Example payload structure for retime operations:
183+
// "payload": {
184+
// "segmentExternalId": "d26d22e2-4f4e-4d31-a0ca-de6f37ff9b3f",
185+
// "partExternalId": "42077925-8d15-4a5d-abeb-a445ccee2984",
186+
// "inPoint": 1061.4136732329084
187+
// }
188+
189+
// Handle different retime operation types
190+
if (payload.inPoint === undefined) {
191+
context.logError('Retime piece operation missing inPoint in payload')
192+
return
193+
}
194+
195+
// Check if there are any unknown values in the payload that need to be handled apart from inPoint, segmentExternalId, partExternalId
196+
const knownKeys = ['inPoint', 'segmentExternalId', 'partExternalId']
197+
const unknownKeys = Object.keys(payload).filter((key) => !knownKeys.includes(key))
198+
if (unknownKeys.length > 0) {
199+
context.logWarning(`Retime piece operation has unknown keys in payload: ${unknownKeys.join(', ')}`)
200+
}
201+
202+
// Check if there are actually changes to apply (now comparing in the same units)
203+
const timeDifference = Math.abs(newTime - originalTime)
204+
if (timeDifference < 0.005) {
205+
// Less than 5ms difference - consider it unchanged
206+
context.logDebug(
207+
`No significant timing changes needed for piece ${operationTarget.pieceExternalId} (${timeDifference}s difference)`
208+
)
209+
return
210+
}
211+
212+
// Apply the retime changes to the ingest data structure
213+
// Note: Ingest pieces use objectTime, not enable.start
214+
const updatedPiece = {
215+
...piece,
216+
objectTime: newTime,
217+
}
218+
219+
// Update the piece in the part payload
220+
partPayload.pieces[pieceIndex] = updatedPiece
221+
222+
// Mark the part as modified using replacePayload
223+
part.replacePayload(partPayload)
224+
225+
// Mark the segment/part as having been edited by the user
226+
// This helps track that the content has been modified from the original ingest
227+
const partAndSegment = mutableIngestRundown.findPartAndSegment(operationTarget.partExternalId)
228+
if (partAndSegment?.segment && partAndSegment?.part) {
229+
// Mark both segment and part as user-edited
230+
partAndSegment.segment.setUserEditState(BlueprintUserOperationTypes.USER_EDITED, true)
231+
partAndSegment.part.setUserEditState(BlueprintUserOperationTypes.USER_EDITED, true)
232+
233+
// Create a unique user edit state key for this piece retime operation
234+
// This ensures each retime operation creates a new state change, triggering persistence
235+
const pieceRetimeKey = `${operation.id}_${operationTarget.pieceExternalId}_${Date.now()}`
236+
237+
// Store the retime operation as a user edit state
238+
// Each retime gets a unique key to ensure state changes trigger persistence
239+
partAndSegment.segment.setUserEditState(pieceRetimeKey, true)
240+
partAndSegment.part.setUserEditState(pieceRetimeKey, true)
241+
242+
// Also mark the specific operation that was performed
243+
partAndSegment.segment.setUserEditState(operation.id, true)
244+
partAndSegment.part.setUserEditState(operation.id, true)
245+
246+
// Lock both segment and part to prevent NRCS updates:
247+
partAndSegment.segment.setUserEditState(BlueprintUserOperationTypes.LOCK_SEGMENT_NRCS_UPDATES, true)
248+
partAndSegment.part.setUserEditState(BlueprintUserOperationTypes.LOCK_PART_NRCS_UPDATES, true)
249+
250+
context.logDebug(`Marked segment and part as user-edited and created unique retime state: ${pieceRetimeKey}`)
251+
}
252+
}
253+
114254
function processUpdateProps(
115255
context: IProcessIngestDataContext,
116256
mutableIngestRundown: BlueprintMutableIngestRundown,

0 commit comments

Comments
 (0)