Skip to content

Commit

Permalink
feat: allow editing piece properties
Browse files Browse the repository at this point in the history
  • Loading branch information
mint-dewit committed Dec 6, 2024
1 parent ffdde73 commit 80ae9d8
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 31 deletions.
8 changes: 7 additions & 1 deletion packages/blueprints-integration/src/documents/piece.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserEditingDefinition } from '../userEditing'
import { UserEditingDefinition, UserEditingProperties } from '../userEditing'
import type { IBlueprintPieceGeneric } from './pieceGeneric'

/** Special types of pieces. Some are not always used in all circumstances */
Expand Down Expand Up @@ -35,6 +35,12 @@ export interface IBlueprintPiece<TPrivateData = unknown, TPublicData = unknown>
* User editing definitions for this piece
*/
userEditOperations?: UserEditingDefinition[]

/**
* Properties that are user editable from the properties panel in the Sofie UI, if the user saves changes to these
* it will trigger a user edit operation of type DefaultUserOperationEditProperties
*/
userEditProperties?: UserEditingProperties
}
export interface IBlueprintPieceDB<TPrivateData = unknown, TPublicData = unknown>
extends IBlueprintPiece<TPrivateData, TPublicData> {
Expand Down
12 changes: 10 additions & 2 deletions packages/corelib/src/dataModel/Piece.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@sofie-automation/blueprints-integration'
import { ProtectedString, protectString, unprotectString } from '../protectedString'
import { PieceId, RundownId, SegmentId, PartId } from './Ids'
import { CoreUserEditingDefinition } from './UserEditingDefinitions'
import { CoreUserEditingDefinition, CoreUserEditingProperties } from './UserEditingDefinitions'

/** A generic list of playback availability statuses for a Piece */
export enum PieceStatusCode {
Expand Down Expand Up @@ -50,7 +50,9 @@ export interface PieceGeneric extends Omit<IBlueprintPieceGeneric, 'content'> {
/** Stringified timelineObjects */
timelineObjectsString: PieceTimelineObjectsBlob
}
export interface Piece extends PieceGeneric, Omit<IBlueprintPieceDB, '_id' | 'content' | 'userEditOperations'> {
export interface Piece
extends PieceGeneric,
Omit<IBlueprintPieceDB, '_id' | 'content' | 'userEditOperations' | 'userEditProperties'> {
/**
* This is the id of the rundown this piece starts playing in.
* Currently this is the only rundown the piece could be playing in
Expand All @@ -77,6 +79,12 @@ export interface Piece extends PieceGeneric, Omit<IBlueprintPieceDB, '_id' | 'co
* User editing definitions for this piece
*/
userEditOperations?: CoreUserEditingDefinition[]

/**
* Properties that are user editable from the properties panel in the Sofie UI, if the user saves changes to these
* it will trigger a user edit operation of type DefaultUserOperationEditProperties
*/
userEditProperties?: CoreUserEditingProperties
}

export type PieceTimelineObjectsBlob = ProtectedString<'PieceTimelineObjectsBlob'>
Expand Down
2 changes: 2 additions & 0 deletions packages/job-worker/src/blueprints/context/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export const IBlueprintPieceObjectsSampleKeys = allKeysOfObject<IBlueprintPiece>
notInVision: true,
abSessions: true,
userEditOperations: true,
userEditProperties: true,
})

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

return obj
Expand Down
3 changes: 2 additions & 1 deletion packages/job-worker/src/blueprints/postProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { setDefaultIdOnExpectedPackages } from '../ingest/expectedPackages'
import { logger } from '../logging'
import { validateTimeline } from 'superfly-timeline'
import { ReadonlyDeep } from 'type-fest'
import { translateUserEditsFromBlueprint } from './context/lib'
import { translateUserEditPropertiesFromBlueprint, translateUserEditsFromBlueprint } from './context/lib'

function getIdHash(docType: string, usedIds: Map<string, number>, uniqueId: string): string {
const count = usedIds.get(uniqueId)
Expand Down Expand Up @@ -110,6 +110,7 @@ export function postProcessPieces(
invalid: setInvalid ?? false,
timelineObjectsString: EmptyPieceTimelineObjectsBlob,
userEditOperations: translateUserEditsFromBlueprint(orgPiece.userEditOperations, [blueprintId]),
userEditProperties: translateUserEditPropertiesFromBlueprint(orgPiece.userEditProperties, [blueprintId]),
}

if (piece.pieceType !== IBlueprintPieceType.Normal) {
Expand Down
3 changes: 3 additions & 0 deletions packages/webui/src/client/lib/ui/pieceUiClassNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function pieceUiClassNames(
pieceInstance: PieceUi,
contentStatus: ReadonlyDeep<PieceContentStatusObj> | undefined,
baseClassName: string,
selected: boolean,
layerType?: SourceLayerType,
partId?: PartId,
highlight?: boolean,
Expand Down Expand Up @@ -54,5 +55,7 @@ export function pieceUiClassNames(
disabled: pieceInstance.instance.disabled,

'invert-flash': highlight,

'element-selected': selected,
})
}
7 changes: 7 additions & 0 deletions packages/webui/src/client/ui/RundownView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3132,9 +3132,16 @@ const RundownViewContent = translateWithTracker<IPropsWithReady, IState, ITracke
onQueueNextSegment={this.onQueueNextSegment}
onSetQuickLoopStart={this.onSetQuickLoopStart}
onSetQuickLoopEnd={this.onSetQuickLoopEnd}
onEditSegmentProps={(id) =>
selectionContext.clearAndSetSelection({ type: 'segment', elementId: id })
}
onEditPartProps={(id) =>
selectionContext.clearAndSetSelection({ type: 'part', elementId: id })
}
studioMode={this.state.studioMode}
enablePlayFromAnywhere={!!studio.settings.enablePlayFromAnywhere}
enableQuickLoop={!!studio.settings.enableQuickLoop}
enableUserEdits={!!studio.settings.enableUserEdits}
/>
</ErrorBoundary>
<ErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Translated } from '../../lib/ReactMeteorData/ReactMeteorData'
import { RundownUtils } from '../../lib/rundown'
import { IContextMenuContext } from '../RundownView'
import { PartUi, SegmentUi } from './SegmentTimelineContainer'
import { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { PartId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment'
import { RenderUserEditOperations } from '../UserEditOperations/RenderUserEditOperations'
import * as RundownResolver from '../../lib/RundownResolver'
Expand All @@ -23,11 +23,14 @@ interface IProps {
onQueueNextSegment: (segmentId: SegmentId | null, e: any) => void
onSetQuickLoopStart: (marker: QuickLoopMarker | null, e: any) => void
onSetQuickLoopEnd: (marker: QuickLoopMarker | null, e: any) => void
onEditSegmentProps: (id: SegmentId) => void
onEditPartProps: (id: PartId) => void
playlist?: DBRundownPlaylist
studioMode: boolean
contextMenuContext: IContextMenuContext | null
enablePlayFromAnywhere: boolean
enableQuickLoop: boolean
enableUserEdits: boolean
}
interface IState {}

Expand Down Expand Up @@ -96,7 +99,15 @@ export const SegmentContextMenu = withTranslation()(
pieceExternalId: undefined,
}
)}
<hr />

{this.props.enableUserEdits && (
<>
<hr />
<MenuItem onClick={(e) => this.props.onEditSegmentProps(part.instance.segmentId)}>
<span>{t('Edit Segment Properties')}</span>
</MenuItem>
</>
)}
</>
)}
{part && !part.instance.part.invalid && timecode !== null && (
Expand Down Expand Up @@ -177,6 +188,18 @@ export const SegmentContextMenu = withTranslation()(
pieceExternalId: undefined,
}
)}

{this.props.enableUserEdits && (
<>
<hr />
<MenuItem onClick={(e) => this.props.onEditSegmentProps(part.instance.segmentId)}>
<span>{t('Edit Segment Properties')}</span>
</MenuItem>
<MenuItem onClick={(e) => this.props.onEditPartProps(part.instance.part._id)}>
<span>{t('Edit Part Properties')}</span>
</MenuItem>
</>
)}
</>
)}
</ContextMenu>
Expand Down
42 changes: 23 additions & 19 deletions packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
import { ReadonlyDeep } from 'type-fest'
import { PieceContentStatusObj } from '@sofie-automation/meteor-lib/dist/api/pieceContentStatus'
import { SelectedElementsContext } from '../RundownView/SelectedElementsContext'
import classNames from 'classnames'
const LEFT_RIGHT_ANCHOR_SPACER = 15
const MARGINAL_ANCHORED_WIDTH = 5

Expand Down Expand Up @@ -672,33 +671,38 @@ export const SourceLayerItem = withTranslation()(
<SelectedElementsContext.Consumer>
{(selectElementContext) => (
<div
className={classNames(
pieceUiClassNames(
piece,
this.props.contentStatus,
'segment-timeline__piece',
this.props.layer.type,
this.props.part.partId,
this.state.highlight,
elementWidth,
this.state
),
selectElementContext.isSelected(this.props.part.instance.part._id) ? 'element-selected' : ''
className={pieceUiClassNames(
piece,
this.props.contentStatus,
'segment-timeline__piece',
selectElementContext.isSelected(this.props.piece.instance.piece._id) ||
selectElementContext.isSelected(this.props.part.instance.part._id),
this.props.layer.type,
this.props.part.partId,
this.state.highlight,
elementWidth,
this.state
)}
data-obj-id={piece.instance._id}
ref={this.setRef}
onClick={this.itemClick}
onDoubleClick={() => {
onDoubleClick={(e) => {
if (this.props.studio?.settings.enableUserEdits) {
// Until a proper data structure, the only reference is a part.
const partId = this.props.part.instance.part._id
if (!selectElementContext.isSelected(partId)) {
selectElementContext.clearAndSetSelection({ type: 'part', elementId: partId })
const pieceId = this.props.piece.instance.piece._id
if (!selectElementContext.isSelected(pieceId)) {
selectElementContext.clearAndSetSelection({ type: 'piece', elementId: pieceId })
} else {
selectElementContext.clearSelections()
}
// Until a proper data structure, the only reference is a part.
// const partId = this.props.part.instance.part._id
// if (!selectElementContext.isSelected(partId)) {
// selectElementContext.clearAndSetSelection({ type: 'part', elementId: partId })
// } else {
// selectElementContext.clearSelections()
// }
} else {
this.itemDblClick
this.itemDblClick(e)
}
}}
onMouseUp={this.itemMouseUp}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { literal } from '@sofie-automation/corelib/dist/lib'
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData'
import { Segments } from '../../collections'
import { Pieces, Segments } from '../../collections'
import { UIParts } from '../Collections'
import { useSelection } from '../RundownView/SelectedElementsContext'
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
Expand Down Expand Up @@ -54,6 +54,11 @@ export function PropertiesPanel(): JSX.Element {
}
}, [])

const piece = useTracker(() => {
setPendingChange(undefined)
return Pieces.findOne(selectedElement?.elementId)
}, [selectedElement?.elementId])

const part = useTracker(() => {
setPendingChange(undefined)
return UIParts.findOne({ _id: selectedElement?.elementId })
Expand All @@ -63,7 +68,7 @@ export function PropertiesPanel(): JSX.Element {
() => Segments.findOne({ _id: part ? part.segmentId : selectedElement?.elementId }),
[selectedElement?.elementId, part?.segmentId]
)
const rundownId = part ? part.rundownId : segment?.rundownId
const rundownId = piece ? piece.startRundownId : part ? part.rundownId : segment?.rundownId

const handleCommitChanges = async (e: React.MouseEvent) => {
if (!rundownId || !selectedElement || !pendingChange) return
Expand Down Expand Up @@ -129,7 +134,7 @@ export function PropertiesPanel(): JSX.Element {
{
segmentExternalId: segment?.externalId,
partExternalId: part?.externalId,
pieceExternalId: undefined,
pieceExternalId: piece?.externalId,
},
{
id,
Expand All @@ -139,13 +144,17 @@ export function PropertiesPanel(): JSX.Element {
}

const userEditOperations =
selectedElement?.type === 'part'
selectedElement?.type === 'piece'
? piece?.userEditOperations
: selectedElement?.type === 'part'
? part?.userEditOperations
: selectedElement?.type === 'segment'
? segment?.userEditOperations
: undefined
const userEditProperties =
selectedElement?.type === 'part'
selectedElement?.type === 'piece'
? piece?.userEditProperties
: selectedElement?.type === 'part'
? part?.userEditProperties
: selectedElement?.type === 'segment'
? segment?.userEditProperties
Expand All @@ -156,7 +165,13 @@ export function PropertiesPanel(): JSX.Element {
}

const title =
selectedElement?.type === 'part' ? part?.title : selectedElement?.type === 'segment' ? segment?.name : undefined
selectedElement?.type === 'piece'
? piece?.name
: selectedElement?.type === 'part'
? part?.title
: selectedElement?.type === 'segment'
? segment?.name
: undefined

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

0 comments on commit 80ae9d8

Please sign in to comment.