@@ -15,9 +15,10 @@ import { v4 as uuid } from 'uuid'
1515import { coreHandler } from '../coreHandler'
1616import { getMutatedPiecesFromPart } from './pieces'
1717import { mutations as rundownMutations } from './rundowns'
18- import { mutations as segmentsMutations } from './segments'
19- import { stringifyError } from '../util'
18+ import { mutations as segmentsMutations , sendSegmentDiffToCore } from './segments'
19+ import { spliceReorder , stringifyError } from '../util'
2020import { mutations as settingsMutations } from './settings'
21+ import { mutations as piecesMutations } from './pieces'
2122
2223async function mutatePart ( part : Part ) : Promise < MutatedPart > {
2324 return {
@@ -69,6 +70,15 @@ async function sendPartDiffToCore(oldPart: Part, newPart: Part) {
6970export const mutations = {
7071 async create ( payload : MutationPartCreate ) : Promise < { result ?: Part ; error ?: Error } > {
7172 const partTypes : string [ ] | undefined = ( await settingsMutations . read ( ) ) . result ?. partTypes
73+ const segmentParts : Part | Part [ ] | undefined = (
74+ await mutations . read ( { segmentId : payload . segmentId } )
75+ ) . result
76+
77+ const partsLength : number = Array . isArray ( segmentParts )
78+ ? segmentParts . length
79+ : segmentParts
80+ ? 1
81+ : 0
7282
7383 const id = payload . id || uuid ( )
7484 const document : Partial < MutationPartCreate > = {
@@ -77,7 +87,8 @@ export const mutations = {
7787 // fallback Type to avoid errors in core
7888 type : partTypes ?. [ 0 ] ,
7989 ...payload . payload
80- }
90+ } ,
91+ rank : payload . rank ?? partsLength
8192 }
8293 delete document . playlistId
8394 delete document . rundownId
@@ -106,6 +117,46 @@ export const mutations = {
106117 return { error : e as Error }
107118 }
108119 } ,
120+ async move (
121+ sourcePart : Part ,
122+ targetPart : Part ,
123+ targetIndex : number
124+ ) : Promise < { result ?: Part ; error ?: Error } > {
125+ try {
126+ const addNewPart = await mutations . create ( {
127+ ...sourcePart ,
128+ rundownId : targetPart . rundownId ,
129+ playlistId : targetPart . playlistId ,
130+ segmentId : targetPart . segmentId ,
131+ rank : undefined ,
132+ id : uuid ( ) ,
133+ payload : {
134+ script : sourcePart . payload . script ,
135+ type : sourcePart . payload . type ,
136+ duration : sourcePart . payload . duration
137+ }
138+ } )
139+ if ( ! addNewPart . result ) {
140+ console . error ( addNewPart . error )
141+ throw new Error ( 'Could not create new part while cloning.' )
142+ }
143+ const clonePieces = await piecesMutations . cloneFromPartToPart ( {
144+ fromPartId : sourcePart . id ,
145+ toPartId : addNewPart . result . id
146+ } )
147+ const reorderParts = await mutations . reorder ( { part : addNewPart . result , targetIndex } )
148+ const removePart = await mutations . delete ( { id : sourcePart . id } )
149+
150+ if ( clonePieces . error && reorderParts . error && removePart . error ) {
151+ throw new Error ( 'Cloning the part failed' )
152+ }
153+
154+ return mutations . readOne ( addNewPart . result . id )
155+ } catch ( e ) {
156+ console . error ( e )
157+ return { error : e as Error }
158+ }
159+ } ,
109160 async readOne ( id : string ) : Promise < { result ?: Part ; error ?: Error } > {
110161 try {
111162 const stmt = db . prepare ( `
@@ -141,22 +192,32 @@ export const mutations = {
141192 }
142193
143194 let query = `
144- SELECT *
145- FROM parts
146- `
147- const args : string [ ] = [ ]
195+ SELECT *
196+ FROM parts
197+ `
198+ const args : ( string | number ) [ ] = [ ]
199+ const conditions : string [ ] = [ ]
200+
148201 if ( payload . id ) {
149- query += `\nWHERE id = ?`
202+ conditions . push ( ` id = ?`)
150203 args . push ( payload . id )
151204 }
152205 if ( payload . rundownId ) {
153- query += `\nWHERE rundownId = ?`
206+ conditions . push ( ` rundownId = ?`)
154207 args . push ( payload . rundownId )
155208 }
156209 if ( payload . segmentId ) {
157- query += `\nWHERE segmentId = ?`
210+ conditions . push ( ` segmentId = ?`)
158211 args . push ( payload . segmentId )
159212 }
213+ if ( payload . rank !== null && payload . rank !== undefined ) {
214+ conditions . push ( `JSON_EXTRACT(document, '$.rank') = ?` )
215+ args . push ( payload . rank )
216+ }
217+
218+ if ( conditions . length > 0 ) {
219+ query += `\nWHERE ${ conditions . join ( ' AND ' ) } ` // Join conditions with AND
220+ }
160221
161222 try {
162223 const stmt = db . prepare ( query )
@@ -208,6 +269,70 @@ export const mutations = {
208269 return { error : e as Error }
209270 }
210271 } ,
272+ async reorder ( {
273+ part,
274+ targetIndex
275+ } : {
276+ part : MutationPartUpdate
277+ targetIndex : number
278+ } ) : Promise < { result ?: Part | Part [ ] ; error ?: Error } > {
279+ try {
280+ const { result, error } = await this . read ( {
281+ segmentId : part . segmentId ,
282+ rundownId : part . rundownId
283+ } )
284+
285+ if ( error ) throw error
286+ if ( result && ( ! ( 'length' in result ) || result ?. length < 2 ) )
287+ throw new Error ( 'An error occurred when getting parts from the database during reorder.' )
288+
289+ const safeTargetIndex : number = Math . max (
290+ 0 ,
291+ Math . min ( ( result as Part [ ] ) . length - 1 , targetIndex )
292+ )
293+
294+ const partsInRankOrder = ( result as Part [ ] ) . sort ( ( partA , partB ) => partA . rank - partB . rank )
295+ const reorderedParts = spliceReorder ( partsInRankOrder , part . rank , safeTargetIndex )
296+
297+ db . exec ( 'BEGIN;' )
298+ try {
299+ const updateStmt = db . prepare ( `
300+ UPDATE parts
301+ SET playlistId = ?, segmentId = ?, document = (SELECT json_patch(parts.document, json(?)) FROM parts WHERE id = ?)
302+ WHERE id = ?;
303+ ` )
304+
305+ reorderedParts . forEach ( ( part , index ) => {
306+ updateStmt . run (
307+ part . playlistId || null ,
308+ part . segmentId || null ,
309+ // update rank based on array order
310+ JSON . stringify ( { ...part , rank : index } ) ,
311+ part . id ,
312+ part . id
313+ )
314+ } )
315+
316+ db . exec ( 'COMMIT;' )
317+ } catch ( transactionError ) {
318+ console . error ( transactionError )
319+ db . exec ( 'ROLLBACK;' )
320+ throw transactionError
321+ }
322+
323+ const { result : updatedParts , error : updatedPartserror } = await this . read ( {
324+ segmentId : part . segmentId ,
325+ rundownId : part . rundownId
326+ } )
327+
328+ if ( updatedPartserror ) throw updatedPartserror
329+
330+ return { result : updatedParts }
331+ } catch ( e ) {
332+ console . error ( e )
333+ return { error : e as Error }
334+ }
335+ } ,
211336 async delete ( payload : MutationPartDelete ) : Promise < { error ?: Error } > {
212337 try {
213338 db . exec ( 'BEGIN TRANSACTION' )
@@ -246,6 +371,37 @@ export async function init(): Promise<void> {
246371 }
247372
248373 return error || result
374+ } else if ( operation . type === IpcOperationType . Move ) {
375+ // TODO: Maybe this should be handled inside Sofie?
376+ try {
377+ const { sourcePart, targetPart, targetIndex } = operation . payload
378+ const { result : document , error : sourceError } = await mutations . readOne ( sourcePart . id )
379+ if ( sourceError ) throw sourceError
380+ const { result : target , error : targetError } = await mutations . readOne ( targetPart . id )
381+ if ( targetError ) throw targetError
382+
383+ if ( document && target ) {
384+ const { result, error } = await mutations . move ( sourcePart , targetPart , targetIndex )
385+ if ( error ) throw error
386+
387+ const { result : sourceSegment } = await segmentsMutations . readOne ( document . segmentId )
388+ const { result : targetSegment } = await segmentsMutations . readOne ( target . segmentId )
389+
390+ if ( result && sourceSegment && targetSegment ) {
391+ try {
392+ await sendSegmentDiffToCore ( sourceSegment , sourceSegment )
393+ await sendSegmentDiffToCore ( targetSegment , targetSegment )
394+ } catch ( error ) {
395+ console . error ( error )
396+ event . sender . send ( 'error' , stringifyError ( error , true ) )
397+ }
398+ } else throw new Error ( 'Cannot find segments while cloning' )
399+ return result
400+ }
401+ } catch ( e ) {
402+ console . error ( e )
403+ return e as Error
404+ }
249405 } else if ( operation . type === IpcOperationType . Read ) {
250406 const { result, error } = await mutations . read ( operation . payload )
251407
@@ -264,6 +420,33 @@ export async function init(): Promise<void> {
264420 }
265421
266422 return error || result
423+ } else if ( operation . type === IpcOperationType . Reorder ) {
424+ const { result : sourceDocument } = await mutations . read ( { id : operation . payload . part . id } )
425+ const { result : reorderedParts , error } = await mutations . reorder ( operation . payload )
426+
427+ if (
428+ ! error &&
429+ sourceDocument &&
430+ ! Array . isArray ( sourceDocument ) &&
431+ Array . isArray ( reorderedParts )
432+ ) {
433+ const { result : rundown } = await rundownMutations . read ( { id : sourceDocument . rundownId } )
434+ if ( rundown && ! Array . isArray ( rundown ) && rundown . sync ) {
435+ try {
436+ const { result : segment , error : segmentError } = await segmentsMutations . readOne (
437+ sourceDocument . segmentId
438+ )
439+ // We need to update the entire segment, because otherwise core also reorders the parts in some cases.
440+ if ( segment && ! segmentError ) {
441+ await sendSegmentDiffToCore ( segment , segment )
442+ return reorderedParts
443+ }
444+ } catch ( error ) {
445+ console . error ( error )
446+ event . sender . send ( 'error' , stringifyError ( error , true ) )
447+ }
448+ }
449+ }
267450 } else if ( operation . type === IpcOperationType . Delete ) {
268451 const { result : document } = await mutations . read ( { id : operation . payload . id } )
269452 const { error } = await mutations . delete ( operation . payload )
0 commit comments