Skip to content

Commit 80ae9d8

Browse files
author
Mint de Wit
committed
feat: allow editing piece properties
1 parent ffdde73 commit 80ae9d8

File tree

9 files changed

+100
-31
lines changed

9 files changed

+100
-31
lines changed

packages/blueprints-integration/src/documents/piece.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { UserEditingDefinition } from '../userEditing'
1+
import { UserEditingDefinition, UserEditingProperties } from '../userEditing'
22
import type { IBlueprintPieceGeneric } from './pieceGeneric'
33

44
/** Special types of pieces. Some are not always used in all circumstances */
@@ -35,6 +35,12 @@ export interface IBlueprintPiece<TPrivateData = unknown, TPublicData = unknown>
3535
* User editing definitions for this piece
3636
*/
3737
userEditOperations?: UserEditingDefinition[]
38+
39+
/**
40+
* Properties that are user editable from the properties panel in the Sofie UI, if the user saves changes to these
41+
* it will trigger a user edit operation of type DefaultUserOperationEditProperties
42+
*/
43+
userEditProperties?: UserEditingProperties
3844
}
3945
export interface IBlueprintPieceDB<TPrivateData = unknown, TPublicData = unknown>
4046
extends IBlueprintPiece<TPrivateData, TPublicData> {

packages/corelib/src/dataModel/Piece.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@sofie-automation/blueprints-integration'
88
import { ProtectedString, protectString, unprotectString } from '../protectedString'
99
import { PieceId, RundownId, SegmentId, PartId } from './Ids'
10-
import { CoreUserEditingDefinition } from './UserEditingDefinitions'
10+
import { CoreUserEditingDefinition, CoreUserEditingProperties } from './UserEditingDefinitions'
1111

1212
/** A generic list of playback availability statuses for a Piece */
1313
export enum PieceStatusCode {
@@ -50,7 +50,9 @@ export interface PieceGeneric extends Omit<IBlueprintPieceGeneric, 'content'> {
5050
/** Stringified timelineObjects */
5151
timelineObjectsString: PieceTimelineObjectsBlob
5252
}
53-
export interface Piece extends PieceGeneric, Omit<IBlueprintPieceDB, '_id' | 'content' | 'userEditOperations'> {
53+
export interface Piece
54+
extends PieceGeneric,
55+
Omit<IBlueprintPieceDB, '_id' | 'content' | 'userEditOperations' | 'userEditProperties'> {
5456
/**
5557
* This is the id of the rundown this piece starts playing in.
5658
* Currently this is the only rundown the piece could be playing in
@@ -77,6 +79,12 @@ export interface Piece extends PieceGeneric, Omit<IBlueprintPieceDB, '_id' | 'co
7779
* User editing definitions for this piece
7880
*/
7981
userEditOperations?: CoreUserEditingDefinition[]
82+
83+
/**
84+
* Properties that are user editable from the properties panel in the Sofie UI, if the user saves changes to these
85+
* it will trigger a user edit operation of type DefaultUserOperationEditProperties
86+
*/
87+
userEditProperties?: CoreUserEditingProperties
8088
}
8189

8290
export type PieceTimelineObjectsBlob = ProtectedString<'PieceTimelineObjectsBlob'>

packages/job-worker/src/blueprints/context/lib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export const IBlueprintPieceObjectsSampleKeys = allKeysOfObject<IBlueprintPiece>
100100
notInVision: true,
101101
abSessions: true,
102102
userEditOperations: true,
103+
userEditProperties: true,
103104
})
104105

105106
// Compile a list of the keys which are allowed to be set
@@ -243,6 +244,7 @@ export function convertPieceToBlueprints(piece: ReadonlyDeep<PieceInstancePiece>
243244
extendOnHold: piece.extendOnHold,
244245
notInVision: piece.notInVision,
245246
userEditOperations: translateUserEditsToBlueprint(piece.userEditOperations),
247+
userEditProperties: translateUserEditPropertiesToBlueprint(piece.userEditProperties),
246248
}
247249

248250
return obj

packages/job-worker/src/blueprints/postProcess.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { setDefaultIdOnExpectedPackages } from '../ingest/expectedPackages'
4444
import { logger } from '../logging'
4545
import { validateTimeline } from 'superfly-timeline'
4646
import { ReadonlyDeep } from 'type-fest'
47-
import { translateUserEditsFromBlueprint } from './context/lib'
47+
import { translateUserEditPropertiesFromBlueprint, translateUserEditsFromBlueprint } from './context/lib'
4848

4949
function getIdHash(docType: string, usedIds: Map<string, number>, uniqueId: string): string {
5050
const count = usedIds.get(uniqueId)
@@ -110,6 +110,7 @@ export function postProcessPieces(
110110
invalid: setInvalid ?? false,
111111
timelineObjectsString: EmptyPieceTimelineObjectsBlob,
112112
userEditOperations: translateUserEditsFromBlueprint(orgPiece.userEditOperations, [blueprintId]),
113+
userEditProperties: translateUserEditPropertiesFromBlueprint(orgPiece.userEditProperties, [blueprintId]),
113114
}
114115

115116
if (piece.pieceType !== IBlueprintPieceType.Normal) {

packages/webui/src/client/lib/ui/pieceUiClassNames.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function pieceUiClassNames(
1111
pieceInstance: PieceUi,
1212
contentStatus: ReadonlyDeep<PieceContentStatusObj> | undefined,
1313
baseClassName: string,
14+
selected: boolean,
1415
layerType?: SourceLayerType,
1516
partId?: PartId,
1617
highlight?: boolean,
@@ -54,5 +55,7 @@ export function pieceUiClassNames(
5455
disabled: pieceInstance.instance.disabled,
5556

5657
'invert-flash': highlight,
58+
59+
'element-selected': selected,
5760
})
5861
}

packages/webui/src/client/ui/RundownView.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,9 +3132,16 @@ const RundownViewContent = translateWithTracker<IPropsWithReady, IState, ITracke
31323132
onQueueNextSegment={this.onQueueNextSegment}
31333133
onSetQuickLoopStart={this.onSetQuickLoopStart}
31343134
onSetQuickLoopEnd={this.onSetQuickLoopEnd}
3135+
onEditSegmentProps={(id) =>
3136+
selectionContext.clearAndSetSelection({ type: 'segment', elementId: id })
3137+
}
3138+
onEditPartProps={(id) =>
3139+
selectionContext.clearAndSetSelection({ type: 'part', elementId: id })
3140+
}
31353141
studioMode={this.state.studioMode}
31363142
enablePlayFromAnywhere={!!studio.settings.enablePlayFromAnywhere}
31373143
enableQuickLoop={!!studio.settings.enableQuickLoop}
3144+
enableUserEdits={!!studio.settings.enableUserEdits}
31383145
/>
31393146
</ErrorBoundary>
31403147
<ErrorBoundary>

packages/webui/src/client/ui/SegmentTimeline/SegmentContextMenu.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Translated } from '../../lib/ReactMeteorData/ReactMeteorData'
1212
import { RundownUtils } from '../../lib/rundown'
1313
import { IContextMenuContext } from '../RundownView'
1414
import { PartUi, SegmentUi } from './SegmentTimelineContainer'
15-
import { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
15+
import { PartId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1616
import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment'
1717
import { RenderUserEditOperations } from '../UserEditOperations/RenderUserEditOperations'
1818
import * as RundownResolver from '../../lib/RundownResolver'
@@ -23,11 +23,14 @@ interface IProps {
2323
onQueueNextSegment: (segmentId: SegmentId | null, e: any) => void
2424
onSetQuickLoopStart: (marker: QuickLoopMarker | null, e: any) => void
2525
onSetQuickLoopEnd: (marker: QuickLoopMarker | null, e: any) => void
26+
onEditSegmentProps: (id: SegmentId) => void
27+
onEditPartProps: (id: PartId) => void
2628
playlist?: DBRundownPlaylist
2729
studioMode: boolean
2830
contextMenuContext: IContextMenuContext | null
2931
enablePlayFromAnywhere: boolean
3032
enableQuickLoop: boolean
33+
enableUserEdits: boolean
3134
}
3235
interface IState {}
3336

@@ -96,7 +99,15 @@ export const SegmentContextMenu = withTranslation()(
9699
pieceExternalId: undefined,
97100
}
98101
)}
99-
<hr />
102+
103+
{this.props.enableUserEdits && (
104+
<>
105+
<hr />
106+
<MenuItem onClick={(e) => this.props.onEditSegmentProps(part.instance.segmentId)}>
107+
<span>{t('Edit Segment Properties')}</span>
108+
</MenuItem>
109+
</>
110+
)}
100111
</>
101112
)}
102113
{part && !part.instance.part.invalid && timecode !== null && (
@@ -177,6 +188,18 @@ export const SegmentContextMenu = withTranslation()(
177188
pieceExternalId: undefined,
178189
}
179190
)}
191+
192+
{this.props.enableUserEdits && (
193+
<>
194+
<hr />
195+
<MenuItem onClick={(e) => this.props.onEditSegmentProps(part.instance.segmentId)}>
196+
<span>{t('Edit Segment Properties')}</span>
197+
</MenuItem>
198+
<MenuItem onClick={(e) => this.props.onEditPartProps(part.instance.part._id)}>
199+
<span>{t('Edit Part Properties')}</span>
200+
</MenuItem>
201+
</>
202+
)}
180203
</>
181204
)}
182205
</ContextMenu>

packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
2424
import { ReadonlyDeep } from 'type-fest'
2525
import { PieceContentStatusObj } from '@sofie-automation/meteor-lib/dist/api/pieceContentStatus'
2626
import { SelectedElementsContext } from '../RundownView/SelectedElementsContext'
27-
import classNames from 'classnames'
2827
const LEFT_RIGHT_ANCHOR_SPACER = 15
2928
const MARGINAL_ANCHORED_WIDTH = 5
3029

@@ -672,33 +671,38 @@ export const SourceLayerItem = withTranslation()(
672671
<SelectedElementsContext.Consumer>
673672
{(selectElementContext) => (
674673
<div
675-
className={classNames(
676-
pieceUiClassNames(
677-
piece,
678-
this.props.contentStatus,
679-
'segment-timeline__piece',
680-
this.props.layer.type,
681-
this.props.part.partId,
682-
this.state.highlight,
683-
elementWidth,
684-
this.state
685-
),
686-
selectElementContext.isSelected(this.props.part.instance.part._id) ? 'element-selected' : ''
674+
className={pieceUiClassNames(
675+
piece,
676+
this.props.contentStatus,
677+
'segment-timeline__piece',
678+
selectElementContext.isSelected(this.props.piece.instance.piece._id) ||
679+
selectElementContext.isSelected(this.props.part.instance.part._id),
680+
this.props.layer.type,
681+
this.props.part.partId,
682+
this.state.highlight,
683+
elementWidth,
684+
this.state
687685
)}
688686
data-obj-id={piece.instance._id}
689687
ref={this.setRef}
690688
onClick={this.itemClick}
691-
onDoubleClick={() => {
689+
onDoubleClick={(e) => {
692690
if (this.props.studio?.settings.enableUserEdits) {
693-
// Until a proper data structure, the only reference is a part.
694-
const partId = this.props.part.instance.part._id
695-
if (!selectElementContext.isSelected(partId)) {
696-
selectElementContext.clearAndSetSelection({ type: 'part', elementId: partId })
691+
const pieceId = this.props.piece.instance.piece._id
692+
if (!selectElementContext.isSelected(pieceId)) {
693+
selectElementContext.clearAndSetSelection({ type: 'piece', elementId: pieceId })
697694
} else {
698695
selectElementContext.clearSelections()
699696
}
697+
// Until a proper data structure, the only reference is a part.
698+
// const partId = this.props.part.instance.part._id
699+
// if (!selectElementContext.isSelected(partId)) {
700+
// selectElementContext.clearAndSetSelection({ type: 'part', elementId: partId })
701+
// } else {
702+
// selectElementContext.clearSelections()
703+
// }
700704
} else {
701-
this.itemDblClick
705+
this.itemDblClick(e)
702706
}
703707
}}
704708
onMouseUp={this.itemMouseUp}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { literal } from '@sofie-automation/corelib/dist/lib'
1616
import classNames from 'classnames'
1717
import { useTranslation } from 'react-i18next'
1818
import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData'
19-
import { Segments } from '../../collections'
19+
import { Pieces, Segments } from '../../collections'
2020
import { UIParts } from '../Collections'
2121
import { useSelection } from '../RundownView/SelectedElementsContext'
2222
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
@@ -54,6 +54,11 @@ export function PropertiesPanel(): JSX.Element {
5454
}
5555
}, [])
5656

57+
const piece = useTracker(() => {
58+
setPendingChange(undefined)
59+
return Pieces.findOne(selectedElement?.elementId)
60+
}, [selectedElement?.elementId])
61+
5762
const part = useTracker(() => {
5863
setPendingChange(undefined)
5964
return UIParts.findOne({ _id: selectedElement?.elementId })
@@ -63,7 +68,7 @@ export function PropertiesPanel(): JSX.Element {
6368
() => Segments.findOne({ _id: part ? part.segmentId : selectedElement?.elementId }),
6469
[selectedElement?.elementId, part?.segmentId]
6570
)
66-
const rundownId = part ? part.rundownId : segment?.rundownId
71+
const rundownId = piece ? piece.startRundownId : part ? part.rundownId : segment?.rundownId
6772

6873
const handleCommitChanges = async (e: React.MouseEvent) => {
6974
if (!rundownId || !selectedElement || !pendingChange) return
@@ -129,7 +134,7 @@ export function PropertiesPanel(): JSX.Element {
129134
{
130135
segmentExternalId: segment?.externalId,
131136
partExternalId: part?.externalId,
132-
pieceExternalId: undefined,
137+
pieceExternalId: piece?.externalId,
133138
},
134139
{
135140
id,
@@ -139,13 +144,17 @@ export function PropertiesPanel(): JSX.Element {
139144
}
140145

141146
const userEditOperations =
142-
selectedElement?.type === 'part'
147+
selectedElement?.type === 'piece'
148+
? piece?.userEditOperations
149+
: selectedElement?.type === 'part'
143150
? part?.userEditOperations
144151
: selectedElement?.type === 'segment'
145152
? segment?.userEditOperations
146153
: undefined
147154
const userEditProperties =
148-
selectedElement?.type === 'part'
155+
selectedElement?.type === 'piece'
156+
? piece?.userEditProperties
157+
: selectedElement?.type === 'part'
149158
? part?.userEditProperties
150159
: selectedElement?.type === 'segment'
151160
? segment?.userEditProperties
@@ -156,7 +165,13 @@ export function PropertiesPanel(): JSX.Element {
156165
}
157166

158167
const title =
159-
selectedElement?.type === 'part' ? part?.title : selectedElement?.type === 'segment' ? segment?.name : undefined
168+
selectedElement?.type === 'piece'
169+
? piece?.name
170+
: selectedElement?.type === 'part'
171+
? part?.title
172+
: selectedElement?.type === 'segment'
173+
? segment?.name
174+
: undefined
160175

161176
return (
162177
<div className={classNames('properties-panel', isAnimatedIn && 'is-mounted')}>

0 commit comments

Comments
 (0)