Skip to content

Commit 3ed6f11

Browse files
committed
Handle drag and drop retime operations
1 parent 3105eb3 commit 3ed6f11

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
DefaultUserOperationEditProperties,
3+
DefaultUserOperationRetimePiece,
34
DefaultUserOperations,
45
DefaultUserOperationsTypes,
56
IngestChangeType,
@@ -106,11 +107,142 @@ async function applyUserOperation(
106107
case DefaultUserOperationsTypes.UPDATE_PROPS:
107108
processUpdateProps(context, mutableIngestRundown, changes.operationTarget, changes)
108109
break
110+
// Handle drag and drop retime operations:
111+
case DefaultUserOperationsTypes.RETIME_PIECE:
112+
processRetimePiece(context, mutableIngestRundown, changes.operationTarget, changes)
113+
break
109114
default:
110115
context.logWarning(`Unknown operation: ${changes.operation.id}`)
111116
}
112117
}
113118

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

219351
const lifespan = operation.payload.globalProperties['lifespan']
220352
context.logDebug('Update piece ' + operationTarget.pieceExternalId + ': ' + lifespan)
353+
// TODO: Do we actually update anything here? We just seem to log.
221354
}
222355

223356
function changeSource(

0 commit comments

Comments
 (0)