@@ -3,7 +3,6 @@ import { i18nTranslator } from '../i18n'
33import { translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
44import { doUserAction , UserAction } from '../../lib/clientUserAction'
55import { MeteorCall } from '../../lib/meteorApi'
6- import { t } from 'i18next'
76import {
87 DefaultUserOperationsTypes ,
98 JSONBlobParse ,
@@ -26,14 +25,21 @@ import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
2625import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
2726import { RundownId } from '@sofie-automation/corelib/dist/dataModel/Ids'
2827
28+ interface PendingChange {
29+ operationId : string
30+ type : 'action' | 'form'
31+ values ?: any
32+ switchState ?: boolean
33+ }
34+
2935export function PropertiesPanel ( ) : JSX . Element {
3036 const { listSelectedElements } = useSelection ( )
31- console . log ( 'listSelectedElements' , listSelectedElements ( ) )
3237 const selectedElement = listSelectedElements ( ) ?. [ 0 ]
33-
38+ const { t } = useTranslation ( )
3439 if ( ! selectedElement ) return < > </ >
3540
36- const { t } = useTranslation ( )
41+ const [ pendingChanges , setPendingChanges ] = React . useState < PendingChange [ ] > ( [ ] )
42+ const hasPendingChanges = pendingChanges . length > 0
3743
3844 React . useEffect ( ( ) => {
3945 return ( ) => {
@@ -45,18 +51,65 @@ export function PropertiesPanel(): JSX.Element {
4551 }
4652 } , [ ] )
4753
48- //const piece = useTracker(() => Pieces.findOne({ _id: selectedElement.elementId }), [selectedElement?.elementId])
49-
50- const part = useTracker ( ( ) => UIParts . findOne ( { _id : selectedElement . elementId } ) , [ selectedElement ?. elementId ] )
54+ const part = useTracker ( ( ) => {
55+ const foundPart = UIParts . findOne ( { _id : selectedElement . elementId } )
56+ setPendingChanges ( [ ] )
57+ return foundPart
58+ } , [ selectedElement . elementId ] )
5159
5260 const segment : DBSegment | undefined = useTracker (
5361 ( ) => Segments . findOne ( { _id : part ? part . segmentId : selectedElement . elementId } ) ,
54- [ selectedElement ? .elementId ]
62+ [ selectedElement . elementId ]
5563 )
5664 const rundownId = part ? part . rundownId : segment ?. rundownId
5765
5866 if ( ! rundownId ) return < > </ >
5967
68+ const handleCommitChanges = async ( e : React . MouseEvent ) => {
69+ for ( const change of pendingChanges ) {
70+ doUserAction ( t , e , UserAction . EXECUTE_USER_OPERATION , ( e , ts ) =>
71+ MeteorCall . userAction . executeUserChangeOperation (
72+ e ,
73+ ts ,
74+ rundownId ,
75+ {
76+ segmentExternalId : segment ?. externalId ,
77+ partExternalId : part ?. externalId ,
78+ pieceExternalId : undefined ,
79+ } ,
80+ {
81+ id : change . operationId ,
82+ values : change . values ,
83+ }
84+ )
85+ )
86+ }
87+ setPendingChanges ( [ ] )
88+ }
89+
90+ const handleRevertChanges = ( e : React . MouseEvent ) => {
91+ setPendingChanges ( [ ] )
92+ rundownId &&
93+ doUserAction ( t , e , UserAction . EXECUTE_USER_OPERATION , ( e , ts ) =>
94+ MeteorCall . userAction . executeUserChangeOperation (
95+ e ,
96+ ts ,
97+ rundownId ,
98+ {
99+ segmentExternalId : segment ?. externalId ,
100+ partExternalId : part ?. externalId ,
101+ pieceExternalId : undefined ,
102+ } ,
103+ {
104+ id :
105+ selectedElement . type === 'partInstance'
106+ ? DefaultUserOperationsTypes . REVERT_PART
107+ : DefaultUserOperationsTypes . REVERT_SEGMENT ,
108+ }
109+ )
110+ )
111+ }
112+
60113 return (
61114 < div className = "propertiespanel-pop-up" >
62115 { selectedElement . type === 'part' && (
@@ -65,7 +118,6 @@ export function PropertiesPanel(): JSX.Element {
65118 { part ?. userEditOperations &&
66119 part . userEditOperations . map ( ( operation ) => {
67120 if ( operation . type === UserEditingType . FORM || ! operation . svgIcon || ! operation . isActive ) return null
68-
69121 return (
70122 < div
71123 key = { operation . id }
@@ -91,6 +143,8 @@ export function PropertiesPanel(): JSX.Element {
91143 segment = { segment }
92144 part = { part }
93145 rundownId = { rundownId }
146+ pendingChanges = { pendingChanges }
147+ setPendingChanges = { setPendingChanges }
94148 />
95149 )
96150 case UserEditingType . FORM :
@@ -101,6 +155,8 @@ export function PropertiesPanel(): JSX.Element {
101155 segment = { segment }
102156 part = { part }
103157 rundownId = { rundownId }
158+ pendingChanges = { pendingChanges }
159+ setPendingChanges = { setPendingChanges }
104160 />
105161 )
106162 default :
@@ -144,6 +200,8 @@ export function PropertiesPanel(): JSX.Element {
144200 segment = { segment }
145201 part = { part }
146202 rundownId = { rundownId }
203+ pendingChanges = { pendingChanges }
204+ setPendingChanges = { setPendingChanges }
147205 />
148206 )
149207 case UserEditingType . FORM :
@@ -154,6 +212,8 @@ export function PropertiesPanel(): JSX.Element {
154212 segment = { segment }
155213 part = { part }
156214 rundownId = { rundownId }
215+ pendingChanges = { pendingChanges }
216+ setPendingChanges = { setPendingChanges }
157217 />
158218 )
159219 default :
@@ -165,32 +225,12 @@ export function PropertiesPanel(): JSX.Element {
165225 </ >
166226 ) }
167227 < div className = "propertiespanel-pop-up__footer" >
168- < button
169- className = "propertiespanel-pop-up__button"
170- onClick = { ( e ) => {
171- rundownId &&
172- doUserAction ( t , e , UserAction . EXECUTE_USER_OPERATION , ( e , ts ) =>
173- MeteorCall . userAction . executeUserChangeOperation (
174- e ,
175- ts ,
176- rundownId ,
177- {
178- segmentExternalId : segment ?. externalId ,
179- partExternalId : part ?. externalId ,
180- pieceExternalId : undefined ,
181- } ,
182- {
183- id :
184- selectedElement . type === 'partInstance'
185- ? DefaultUserOperationsTypes . REVERT_PART
186- : DefaultUserOperationsTypes . REVERT_SEGMENT ,
187- }
188- )
189- )
190- } }
191- >
228+ < button className = "propertiespanel-pop-up__button" onClick = { handleRevertChanges } >
192229 < span className = "propertiespanel-pop-up__label" > REVERT CHANGES</ span >
193230 </ button >
231+ < button className = "propertiespanel-pop-up__button" onClick = { handleCommitChanges } disabled = { ! hasPendingChanges } >
232+ < span className = "propertiespanel-pop-up__label" > COMMIT CHANGES</ span >
233+ </ button >
194234 </ div >
195235 </ div >
196236 )
@@ -201,34 +241,52 @@ function EditingTypeAction(props: {
201241 segment : DBSegment | undefined
202242 part : DBPart | undefined
203243 rundownId : RundownId
244+ pendingChanges : PendingChange [ ]
245+ setPendingChanges : React . Dispatch < React . SetStateAction < PendingChange [ ] > >
204246} ) {
205247 if ( ! props . userEditOperation . buttonType ) return null
248+
249+ const getPendingState = ( operationId : string ) => {
250+ const pendingChange = props . pendingChanges . find ( ( change ) => change . operationId === operationId )
251+ return pendingChange ?. switchState
252+ }
253+
254+ const addPendingChange = ( ) => {
255+ props . setPendingChanges ( ( prev ) => {
256+ // Find if there's an existing pending change for this operation
257+ const existingChangeIndex = prev . findIndex ( ( change ) => change . operationId === props . userEditOperation . id )
258+
259+ if ( existingChangeIndex !== - 1 ) {
260+ // If exists, toggle the switch state
261+ const newChanges = [ ...prev ]
262+ newChanges [ existingChangeIndex ] = {
263+ ...newChanges [ existingChangeIndex ] ,
264+ switchState : ! newChanges [ existingChangeIndex ] . switchState ,
265+ }
266+ return newChanges
267+ }
268+
269+ // If doesn't exist, add new change with opposite of current state
270+ return [
271+ ...prev ,
272+ {
273+ operationId : props . userEditOperation . id ,
274+ type : 'action' ,
275+ switchState : ! props . userEditOperation . isActive ,
276+ } ,
277+ ]
278+ } )
279+ }
280+
206281 switch ( props . userEditOperation . buttonType ) {
207282 case UserEditingButtonType . BUTTON :
208283 return (
209284 < button
210285 title = { 'User Action : ' + props . userEditOperation . label }
211286 className = "propertiespanel-pop-up__button"
212- onClick = { ( e ) => {
213- doUserAction ( t , e , UserAction . EXECUTE_USER_OPERATION , ( e , ts ) =>
214- MeteorCall . userAction . executeUserChangeOperation (
215- e ,
216- ts ,
217- props . rundownId ,
218- {
219- segmentExternalId : props . segment ?. externalId ,
220- partExternalId : props . part ?. externalId ,
221- pieceExternalId : undefined ,
222- } ,
223- {
224- id : props . userEditOperation . id ,
225- }
226- )
227- )
228- } }
287+ onClick = { addPendingChange }
229288 >
230289 < span className = "propertiespanel-pop-up__label" >
231- { ' ' }
232290 { translateMessage ( props . userEditOperation . label , i18nTranslator ) }
233291 </ span >
234292 </ button >
@@ -238,26 +296,10 @@ function EditingTypeAction(props: {
238296 < div className = "propertiespanel-pop-up__action" >
239297 < a
240298 className = { classNames ( 'propertiespanel-pop-up__switchbutton' , 'switch-button' , {
241- 'sb-on' : props . userEditOperation . isActive || false ,
299+ 'sb-on' : getPendingState ( props . userEditOperation . id ) ?? ( props . userEditOperation . isActive || false ) ,
242300 } ) }
243301 role = "button"
244- onClick = { ( e ) => {
245- doUserAction ( t , e , UserAction . EXECUTE_USER_OPERATION , ( e , ts ) =>
246- MeteorCall . userAction . executeUserChangeOperation (
247- e ,
248- ts ,
249- props . rundownId ,
250- {
251- segmentExternalId : props . segment ?. externalId ,
252- partExternalId : props . part ?. externalId ,
253- pieceExternalId : undefined ,
254- } ,
255- {
256- id : props . userEditOperation . id ,
257- }
258- )
259- )
260- } }
302+ onClick = { addPendingChange }
261303 tabIndex = { 0 }
262304 >
263305 < div className = "sb-content" >
@@ -287,15 +329,15 @@ function EditingTypeChangeSource(props: {
287329 segment : DBSegment | undefined
288330 part : DBPart | undefined
289331 rundownId : RundownId
332+ pendingChanges : PendingChange [ ]
333+ setPendingChanges : React . Dispatch < React . SetStateAction < PendingChange [ ] > >
290334} ) {
291335 const { t } = useTranslation ( )
292336 const [ selectedSource , setSelectedSource ] = React . useState < Record < string , string > > (
293337 clone ( props . userEditOperation . currentValues )
294338 )
295- const [ selectedGroup , setSelectedGroup ] = React . useState < string | undefined > (
296- // base initial selectedGroup on the first key in slectedSource:
297- Object . keys ( selectedSource ) [ 0 ]
298- )
339+ const [ selectedGroup , setSelectedGroup ] = React . useState < string | undefined > ( Object . keys ( selectedSource ) [ 0 ] )
340+
299341 const jsonSchema = props . userEditOperation . schemas [ selectedGroup || '' ]
300342 const schema = jsonSchema ? JSONBlobParse ( jsonSchema ) : undefined
301343 const sourceList = ( schema ?. properties ? schema ?. properties [ selectedGroup ?? '' ] : [ ] ) as {
@@ -308,6 +350,29 @@ function EditingTypeChangeSource(props: {
308350 groups . push ( { } )
309351 }
310352
353+ const handleSourceChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
354+ const newValue = e . target . value
355+ if ( selectedGroup ) {
356+ const newSelectedSource = { [ selectedGroup ] : newValue }
357+ setSelectedSource ( newSelectedSource )
358+
359+ // Add to pending changes instead of executing immediately
360+ props . setPendingChanges ( ( prev ) => {
361+ const filtered = prev . filter (
362+ ( change ) => ! ( change . operationId === props . userEditOperation . id && change . type === 'form' )
363+ )
364+ return [
365+ ...filtered ,
366+ {
367+ operationId : props . userEditOperation . id ,
368+ type : 'form' ,
369+ values : newValue ,
370+ } ,
371+ ]
372+ } )
373+ }
374+ }
375+
311376 return (
312377 < >
313378 < div className = "propertiespanel-pop-up__groupselector" >
@@ -366,25 +431,7 @@ function EditingTypeChangeSource(props: {
366431 title = "Sources in the selected group"
367432 className = "propertiespanel-pop-up__select"
368433 value = { selectedSource [ selectedGroup ] || '' }
369- onChange = { ( e ) => {
370- setSelectedSource ( { [ selectedGroup ] : e . target . value } )
371- doUserAction ( t , e , UserAction . EXECUTE_USER_OPERATION , ( e , ts ) =>
372- MeteorCall . userAction . executeUserChangeOperation (
373- e ,
374- ts ,
375- props . rundownId ,
376- {
377- segmentExternalId : props . segment ?. externalId ,
378- partExternalId : props . part ?. externalId ,
379- pieceExternalId : undefined ,
380- } ,
381- {
382- values : selectedSource [ selectedGroup ] ,
383- id : props . userEditOperation . id ,
384- }
385- )
386- )
387- } }
434+ onChange = { handleSourceChange }
388435 >
389436 { sourceList . enum . map ( ( source , index ) => (
390437 < option key = { index } value = { source } >
0 commit comments