Skip to content

Commit 8220392

Browse files
committed
feat: properties panel commit button for pending changes
1 parent 48521c6 commit 8220392

File tree

2 files changed

+140
-93
lines changed

2 files changed

+140
-93
lines changed

packages/webui/src/client/styles/propertiesPanel.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@
4949
display: flex;
5050
justify-content: center;
5151
align-items: center;
52+
margin-right: 50px;
5253

5354
> .propertiespanel-pop-up__button {
54-
margin-top: 10px;
5555
background: #636363;
5656
padding: 5px 5px 5px 5px;
5757
gap: 10px;

packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx

Lines changed: 139 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { i18nTranslator } from '../i18n'
33
import { translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
44
import { doUserAction, UserAction } from '../../lib/clientUserAction'
55
import { MeteorCall } from '../../lib/meteorApi'
6-
import { t } from 'i18next'
76
import {
87
DefaultUserOperationsTypes,
98
JSONBlobParse,
@@ -26,14 +25,21 @@ import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
2625
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
2726
import { 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+
2935
export 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

Comments
 (0)