Skip to content

Commit d51ba2f

Browse files
author
Mint de Wit
committed
feat: allow drag from mos-plugin to part
chore: remove stray import
1 parent 054dcb1 commit d51ba2f

File tree

8 files changed

+178
-9
lines changed

8 files changed

+178
-9
lines changed

packages/blueprints-integration/src/ingest.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export enum DefaultUserOperationsTypes {
129129
REVERT_PART = '__sofie-revert-part',
130130
REVERT_RUNDOWN = '__sofie-revert-rundown',
131131
UPDATE_PROPS = '__sofie-update-props',
132+
IMPORT_MOS_ITEM = '__sofie-import-mos',
132133
}
133134

134135
export interface DefaultUserOperationRevertRundown {
@@ -153,11 +154,19 @@ export interface DefaultUserOperationEditProperties {
153154
}
154155
}
155156

157+
export type DefaultUserOperationImportMOSItem = {
158+
id: DefaultUserOperationsTypes.IMPORT_MOS_ITEM
159+
160+
payloadType: string
161+
payload: any
162+
}
163+
156164
export type DefaultUserOperations =
157165
| DefaultUserOperationRevertRundown
158166
| DefaultUserOperationRevertSegment
159167
| DefaultUserOperationRevertPart
160168
| DefaultUserOperationEditProperties
169+
| DefaultUserOperationImportMOSItem
161170

162171
export interface UserOperationChange<TCustomBlueprintOperations extends { id: string } = never> {
163172
/** Indicate that this change is from user operations */

packages/blueprints-integration/src/userEditing.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import type { JSONBlob } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
22
import type { ITranslatableMessage } from './translations'
33
import { JSONSchema } from '@sofie-automation/shared-lib/dist/lib/JSONSchemaTypes'
44
import { SourceLayerType } from './content'
5+
import { DefaultUserOperationsTypes } from './ingest'
56

67
/**
78
* Description of a user performed editing operation allowed on an document
89
*/
9-
export type UserEditingDefinition = UserEditingDefinitionAction | UserEditingDefinitionForm
10+
export type UserEditingDefinition =
11+
| UserEditingDefinitionAction
12+
| UserEditingDefinitionForm
13+
| UserEditingDefinitionSofieDefault
1014

1115
/**
1216
* A simple 'action' that can be performed
@@ -40,11 +44,22 @@ export interface UserEditingDefinitionForm {
4044
currentValues: Record<string, any>
4145
}
4246

47+
/**
48+
* A built in Sofie User operation
49+
*/
50+
export interface UserEditingDefinitionSofieDefault {
51+
type: UserEditingType.SOFIE
52+
/** Id of this operation */
53+
id: DefaultUserOperationsTypes
54+
}
55+
4356
export enum UserEditingType {
4457
/** Action */
4558
ACTION = 'action',
4659
/** Form */
4760
FORM = 'form',
61+
/** Operation for the Built-in Sofie Rich Editing UI */
62+
SOFIE = 'sofie',
4863
}
4964

5065
export interface UserEditingSourceLayer {

packages/corelib/src/dataModel/UserEditingDefinitions.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import type {
33
JSONBlob,
44
JSONSchema,
55
UserEditingSourceLayer,
6+
DefaultUserOperationsTypes,
67
} from '@sofie-automation/blueprints-integration'
78
import type { ITranslatableMessage } from '../TranslatableMessage'
89

9-
export type CoreUserEditingDefinition = CoreUserEditingDefinitionAction | CoreUserEditingDefinitionForm
10+
export type CoreUserEditingDefinition =
11+
| CoreUserEditingDefinitionAction
12+
| CoreUserEditingDefinitionForm
13+
| CoreUserEditingDefinitionSofie
1014

1115
export interface CoreUserEditingDefinitionAction {
1216
type: UserEditingType.ACTION
@@ -83,3 +87,9 @@ export interface CoreUserEditingProperties {
8387
/** Translation namespaces to use when rendering this form */
8488
translationNamespaces: string[]
8589
}
90+
91+
export interface CoreUserEditingDefinitionSofie {
92+
type: UserEditingType.SOFIE
93+
/** Id of this operation */
94+
id: DefaultUserOperationsTypes
95+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
CoreUserEditingDefinitionAction,
1515
CoreUserEditingDefinitionForm,
1616
CoreUserEditingProperties,
17+
CoreUserEditingDefinitionSofie,
1718
} from '@sofie-automation/corelib/dist/dataModel/UserEditingDefinitions'
1819
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
1920
import { assertNever, clone, Complete, literal, omit } from '@sofie-automation/corelib/dist/lib'
@@ -58,6 +59,7 @@ import {
5859
UserEditingDefinitionAction,
5960
UserEditingDefinitionForm,
6061
UserEditingProperties,
62+
UserEditingDefinitionSofieDefault,
6163
UserEditingType,
6264
} from '@sofie-automation/blueprints-integration/dist/userEditing'
6365
import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel'
@@ -530,6 +532,11 @@ function translateUserEditsToBlueprint(
530532
schema: clone(userEdit.schema),
531533
currentValues: clone(userEdit.currentValues),
532534
} satisfies Complete<UserEditingDefinitionForm>
535+
case UserEditingType.SOFIE:
536+
return {
537+
type: UserEditingType.SOFIE,
538+
id: userEdit.id,
539+
} satisfies Complete<UserEditingDefinitionSofieDefault>
533540
default:
534541
assertNever(userEdit)
535542
return undefined
@@ -588,6 +595,11 @@ export function translateUserEditsFromBlueprint(
588595
currentValues: clone(userEdit.currentValues),
589596
translationNamespaces: unprotectStringArray(blueprintIds),
590597
} satisfies Complete<CoreUserEditingDefinitionForm>
598+
case UserEditingType.SOFIE:
599+
return {
600+
type: UserEditingType.SOFIE,
601+
id: userEdit.id,
602+
} satisfies Complete<CoreUserEditingDefinitionSofie>
591603
default:
592604
assertNever(userEdit)
593605
return undefined

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,19 @@ svg.icon {
15801580
z-index: 1;
15811581
}
15821582

1583+
&.drop-active {
1584+
&::after {
1585+
content: ' ';
1586+
display: block;
1587+
background-color: rgba(0, 183, 255, 0.5);
1588+
position: absolute;
1589+
top: 0;
1590+
left: 0;
1591+
bottom: 0;
1592+
right: 0;
1593+
}
1594+
}
1595+
15831596
&.outside-quickloop {
15841597
&::before {
15851598
content: ' ';

packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
3232
import { getPartInstanceTimingId, getPartInstanceTimingValue, RundownTimingContext } from '../../../lib/rundownTiming'
3333
import { OutputGroup } from './OutputGroup'
3434
import { InvalidPartCover } from './InvalidPartCover'
35-
import { ISourceLayer } from '@sofie-automation/blueprints-integration'
35+
import { DefaultUserOperationsTypes, ISourceLayer, UserEditingType } from '@sofie-automation/blueprints-integration'
3636
import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
3737
import { LIVE_LINE_TIME_PADDING } from '../Constants'
3838
import * as RundownResolver from '../../../lib/RundownResolver'
39+
import { Events as MOSEvents } from '../../../lib/data/mos/plugin-support'
3940

4041
export const SegmentTimelineLineElementId = 'rundown__segment__line__'
4142
export const SegmentTimelinePartElementId = 'rundown__segment__part__'
@@ -99,6 +100,8 @@ interface IState {
99100
isTooSmallForText: boolean
100101
isTooSmallForDisplay: boolean
101102
highlight: boolean
103+
104+
dropActive: boolean
102105
}
103106
export class SegmentTimelinePartClass extends React.Component<Translated<WithTiming<IProps>>, IState> {
104107
constructor(props: Readonly<Translated<WithTiming<IProps>>>) {
@@ -137,6 +140,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
137140
: 0
138141
)
139142
: 0,
143+
dropActive: false,
140144
}
141145
}
142146

@@ -263,6 +267,10 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
263267

264268
componentDidMount(): void {
265269
super.componentDidMount && super.componentDidMount()
270+
271+
window.addEventListener(MOSEvents.dragenter, this.onDragEnter)
272+
window.addEventListener(MOSEvents.dragleave, this.onDragLeave)
273+
266274
RundownViewEventBus.on(RundownViewEvents.HIGHLIGHT, this.onHighlight)
267275
const tooSmallState = this.state.isTooSmallForDisplay || this.state.isTooSmallForText
268276
if (tooSmallState) {
@@ -282,6 +290,10 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
282290

283291
componentWillUnmount(): void {
284292
super.componentWillUnmount && super.componentWillUnmount()
293+
294+
window.removeEventListener(MOSEvents.dragenter, this.onDragEnter)
295+
window.removeEventListener(MOSEvents.dragleave, this.onDragLeave)
296+
285297
RundownViewEventBus.off(RundownViewEvents.HIGHLIGHT, this.onHighlight)
286298
this.highlightTimeout && clearTimeout(this.highlightTimeout)
287299
}
@@ -622,6 +634,23 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
622634
return { red, green, blue }
623635
}
624636

637+
onDragEnter = (): void => {
638+
const supportsDrop = this.props.part.instance.part.userEditOperations?.find(
639+
(op) => op.type === UserEditingType.SOFIE && op.id === DefaultUserOperationsTypes.IMPORT_MOS_ITEM
640+
)
641+
if (!supportsDrop) return
642+
643+
this.setState({
644+
dropActive: true,
645+
})
646+
}
647+
648+
onDragLeave = (): void => {
649+
this.setState({
650+
dropActive: false,
651+
})
652+
}
653+
625654
render(): JSX.Element | null {
626655
// optimize early, if not inside viewport
627656
if (!this.state.isInsideViewport) {
@@ -681,6 +710,8 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
681710
'outside-quickloop': isOutsideActiveQuickLoop,
682711
'quickloop-start': isQuickLoopStart,
683712
'quickloop-end': isQuickLoopEnd,
713+
714+
'drop-active': this.state.dropActive,
684715
},
685716
this.props.className
686717
)}
@@ -690,6 +721,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
690721
role="region"
691722
aria-roledescription={t('part')}
692723
aria-label={this.props.part.instance.part.title}
724+
data-part-id={this.props.part.partId}
693725
>
694726
{DEBUG_MODE && (
695727
<div className="segment-timeline__debug-info">

packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@ import {
2222
import { doUserAction, UserAction } from '../../lib/clientUserAction'
2323
import { withTranslation } from 'react-i18next'
2424
import { Translated } from '../../lib/ReactMeteorData/ReactMeteorData'
25-
import { IngestAdlib } from '@sofie-automation/blueprints-integration'
25+
import {
26+
DefaultUserOperationImportMOSItem,
27+
DefaultUserOperationsTypes,
28+
IngestAdlib,
29+
UserEditingType,
30+
} from '@sofie-automation/blueprints-integration'
2631
import { MeteorCall } from '../../lib/meteorApi'
2732
import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
28-
import { Buckets, Rundowns } from '../../collections'
29-
import { BucketId, PartInstanceId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
33+
import { Buckets, Rundowns, Segments } from '../../collections'
34+
import { BucketId, PartInstanceId, RundownId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
3035
import { MOS_DATA_IS_STRICT } from '@sofie-automation/meteor-lib/dist/mos'
3136
import { mosTypes, MOS } from '@sofie-automation/meteor-lib/dist/mos'
3237
import { RundownPlaylistCollectionUtil } from '../../collections/rundownPlaylistUtil'
@@ -35,7 +40,7 @@ import RundownViewEventBus, {
3540
ItemDroppedEvent,
3641
RundownViewEvents,
3742
} from '@sofie-automation/meteor-lib/dist/triggers/RundownViewEventBus'
38-
import { UIPartInstances } from '../Collections'
43+
import { UIPartInstances, UIParts } from '../Collections'
3944

4045
interface IProps {
4146
layout: RundownLayoutBase
@@ -210,6 +215,36 @@ export const ExternalFramePanel = withTranslation()(
210215
return undefined
211216
}
212217

218+
private findPartId(el: HTMLElement): {
219+
rundownId: RundownId | undefined
220+
segmentId: string | undefined
221+
partId: string | undefined
222+
} {
223+
while (el.dataset.partId === undefined && el.parentElement) {
224+
el = el.parentElement
225+
}
226+
const partId = el?.dataset.partId
227+
228+
if (partId) {
229+
const part = UIParts.findOne(protectString(partId))
230+
const supportsOp = part?.userEditOperations?.find(
231+
(op) => op.type === UserEditingType.SOFIE && op.id === DefaultUserOperationsTypes.IMPORT_MOS_ITEM
232+
)
233+
234+
if (supportsOp) {
235+
const segment = Segments.findOne(part?.segmentId)
236+
237+
return {
238+
rundownId: part?.rundownId,
239+
segmentId: segment?.externalId,
240+
partId: part?.externalId,
241+
}
242+
}
243+
}
244+
245+
return { rundownId: undefined, partId: undefined, segmentId: undefined }
246+
}
247+
213248
private getShowStyleBaseId() {
214249
const { playlist } = this.props
215250

@@ -243,11 +278,52 @@ export const ExternalFramePanel = withTranslation()(
243278
}
244279

245280
receiveMOSItem(e: any, mosItem: MOS.IMOSItem) {
246-
const { t } = this.props
247-
248281
console.log('Object received, passing onto blueprints', mosItem)
249282

250283
const bucketId = this.findBucketId(e.target)
284+
if (bucketId) {
285+
this.receiveMOSItemBucket(e, bucketId, mosItem)
286+
return
287+
}
288+
289+
const { rundownId, segmentId, partId } = this.findPartId(e.target)
290+
if (rundownId && partId) {
291+
console.log('pass to part', partId)
292+
this.receiveMOSItemUserOp(e, rundownId, partId, segmentId, mosItem)
293+
return
294+
}
295+
}
296+
297+
receiveMOSItemUserOp(
298+
e: any,
299+
rundownId: RundownId,
300+
partId: string,
301+
segmentId: string | undefined,
302+
mosItem: MOS.IMOSItem
303+
) {
304+
const { t } = this.props
305+
306+
const operationTarget = { segmentExternalId: segmentId, partExternalId: partId, pieceExternalId: undefined }
307+
308+
doUserAction(t, e, UserAction.EXECUTE_USER_OPERATION, (e, ts) =>
309+
MeteorCall.userAction.executeUserChangeOperation(
310+
e,
311+
ts,
312+
rundownId,
313+
operationTarget,
314+
literal<DefaultUserOperationImportMOSItem>({
315+
id: DefaultUserOperationsTypes.IMPORT_MOS_ITEM,
316+
317+
payloadType: 'MOS',
318+
payload: MOS.stringifyMosObject(mosItem, MOS_DATA_IS_STRICT),
319+
})
320+
)
321+
)
322+
}
323+
324+
receiveMOSItemBucket(e: any, bucketId: BucketId, mosItem: MOS.IMOSItem) {
325+
const { t } = this.props
326+
251327
const targetBucket = bucketId ? Buckets.findOne(bucketId) : Buckets.findOne()
252328

253329
const showStyleBaseId = this.getShowStyleBaseId()

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ export function UserEditOperationMenuItems({
8686
<span>{translateMessage(userEditOperation.label, t)}</span>
8787
</MenuItem>
8888
)
89+
case UserEditingType.SOFIE:
90+
return null
8991
default:
9092
assertNever(userEditOperation)
9193
return null

0 commit comments

Comments
 (0)