Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow drag from mos-plugin to part #42

Open
wants to merge 1 commit into
base: bbc-release52
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/blueprints-integration/src/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export enum DefaultUserOperationsTypes {
REVERT_PART = '__sofie-revert-part',
REVERT_RUNDOWN = '__sofie-revert-rundown',
UPDATE_PROPS = '__sofie-update-props',
IMPORT_MOS_ITEM = '__sofie-import-mos',
}

export interface DefaultUserOperationRevertRundown {
Expand All @@ -153,11 +154,19 @@ export interface DefaultUserOperationEditProperties {
}
}

export type DefaultUserOperationImportMOSItem = {
id: DefaultUserOperationsTypes.IMPORT_MOS_ITEM

payloadType: string
payload: any
}

export type DefaultUserOperations =
| DefaultUserOperationRevertRundown
| DefaultUserOperationRevertSegment
| DefaultUserOperationRevertPart
| DefaultUserOperationEditProperties
| DefaultUserOperationImportMOSItem

export interface UserOperationChange<TCustomBlueprintOperations extends { id: string } = never> {
/** Indicate that this change is from user operations */
Expand Down
17 changes: 16 additions & 1 deletion packages/blueprints-integration/src/userEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import type { JSONBlob } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
import type { ITranslatableMessage } from './translations'
import { JSONSchema } from '@sofie-automation/shared-lib/dist/lib/JSONSchemaTypes'
import { SourceLayerType } from './content'
import { DefaultUserOperationsTypes } from './ingest'

/**
* Description of a user performed editing operation allowed on an document
*/
export type UserEditingDefinition = UserEditingDefinitionAction | UserEditingDefinitionForm
export type UserEditingDefinition =
| UserEditingDefinitionAction
| UserEditingDefinitionForm
| UserEditingDefinitionSofieDefault

/**
* A simple 'action' that can be performed
Expand Down Expand Up @@ -40,11 +44,22 @@ export interface UserEditingDefinitionForm {
currentValues: Record<string, any>
}

/**
* A built in Sofie User operation
*/
export interface UserEditingDefinitionSofieDefault {
type: UserEditingType.SOFIE
/** Id of this operation */
id: DefaultUserOperationsTypes
}

export enum UserEditingType {
/** Action */
ACTION = 'action',
/** Form */
FORM = 'form',
/** Operation for the Built-in Sofie Rich Editing UI */
SOFIE = 'sofie',
}

export interface UserEditingSourceLayer {
Expand Down
12 changes: 11 additions & 1 deletion packages/corelib/src/dataModel/UserEditingDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import type {
JSONBlob,
JSONSchema,
UserEditingSourceLayer,
DefaultUserOperationsTypes,
} from '@sofie-automation/blueprints-integration'
import type { ITranslatableMessage } from '../TranslatableMessage'

export type CoreUserEditingDefinition = CoreUserEditingDefinitionAction | CoreUserEditingDefinitionForm
export type CoreUserEditingDefinition =
| CoreUserEditingDefinitionAction
| CoreUserEditingDefinitionForm
| CoreUserEditingDefinitionSofie

export interface CoreUserEditingDefinitionAction {
type: UserEditingType.ACTION
Expand Down Expand Up @@ -83,3 +87,9 @@ export interface CoreUserEditingProperties {
/** Translation namespaces to use when rendering this form */
translationNamespaces: string[]
}

export interface CoreUserEditingDefinitionSofie {
type: UserEditingType.SOFIE
/** Id of this operation */
id: DefaultUserOperationsTypes
}
12 changes: 12 additions & 0 deletions packages/job-worker/src/blueprints/context/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CoreUserEditingDefinitionAction,
CoreUserEditingDefinitionForm,
CoreUserEditingProperties,
CoreUserEditingDefinitionSofie,
} from '@sofie-automation/corelib/dist/dataModel/UserEditingDefinitions'
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
import { assertNever, clone, Complete, literal, omit } from '@sofie-automation/corelib/dist/lib'
Expand Down Expand Up @@ -58,6 +59,7 @@ import {
UserEditingDefinitionAction,
UserEditingDefinitionForm,
UserEditingProperties,
UserEditingDefinitionSofieDefault,
UserEditingType,
} from '@sofie-automation/blueprints-integration/dist/userEditing'
import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel'
Expand Down Expand Up @@ -532,6 +534,11 @@ function translateUserEditsToBlueprint(
schema: clone(userEdit.schema),
currentValues: clone(userEdit.currentValues),
} satisfies Complete<UserEditingDefinitionForm>
case UserEditingType.SOFIE:
return {
type: UserEditingType.SOFIE,
id: userEdit.id,
} satisfies Complete<UserEditingDefinitionSofieDefault>
default:
assertNever(userEdit)
return undefined
Expand Down Expand Up @@ -590,6 +597,11 @@ export function translateUserEditsFromBlueprint(
currentValues: clone(userEdit.currentValues),
translationNamespaces: unprotectStringArray(blueprintIds),
} satisfies Complete<CoreUserEditingDefinitionForm>
case UserEditingType.SOFIE:
return {
type: UserEditingType.SOFIE,
id: userEdit.id,
} satisfies Complete<CoreUserEditingDefinitionSofie>
default:
assertNever(userEdit)
return undefined
Expand Down
13 changes: 13 additions & 0 deletions packages/webui/src/client/styles/rundownView.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,19 @@ svg.icon {
z-index: 1;
}

&.drop-active {
&::after {
content: ' ';
display: block;
background-color: rgba(0, 183, 255, 0.5);
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}

&.outside-quickloop {
&::before {
content: ' ';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
import { getPartInstanceTimingId, getPartInstanceTimingValue, RundownTimingContext } from '../../../lib/rundownTiming'
import { OutputGroup } from './OutputGroup'
import { InvalidPartCover } from './InvalidPartCover'
import { ISourceLayer } from '@sofie-automation/blueprints-integration'
import { DefaultUserOperationsTypes, ISourceLayer, UserEditingType } from '@sofie-automation/blueprints-integration'
import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
import { LIVE_LINE_TIME_PADDING } from '../Constants'
import * as RundownResolver from '../../../lib/RundownResolver'
import { Events as MOSEvents } from '../../../lib/data/mos/plugin-support'

export const SegmentTimelineLineElementId = 'rundown__segment__line__'
export const SegmentTimelinePartElementId = 'rundown__segment__part__'
Expand Down Expand Up @@ -99,6 +100,8 @@ interface IState {
isTooSmallForText: boolean
isTooSmallForDisplay: boolean
highlight: boolean

dropActive: boolean
}
export class SegmentTimelinePartClass extends React.Component<Translated<WithTiming<IProps>>, IState> {
constructor(props: Readonly<Translated<WithTiming<IProps>>>) {
Expand Down Expand Up @@ -137,6 +140,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
: 0
)
: 0,
dropActive: false,
}
}

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

componentDidMount(): void {
super.componentDidMount && super.componentDidMount()

window.addEventListener(MOSEvents.dragenter, this.onDragEnter)
window.addEventListener(MOSEvents.dragleave, this.onDragLeave)

RundownViewEventBus.on(RundownViewEvents.HIGHLIGHT, this.onHighlight)
const tooSmallState = this.state.isTooSmallForDisplay || this.state.isTooSmallForText
if (tooSmallState) {
Expand All @@ -282,6 +290,10 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim

componentWillUnmount(): void {
super.componentWillUnmount && super.componentWillUnmount()

window.removeEventListener(MOSEvents.dragenter, this.onDragEnter)
window.removeEventListener(MOSEvents.dragleave, this.onDragLeave)

RundownViewEventBus.off(RundownViewEvents.HIGHLIGHT, this.onHighlight)
this.highlightTimeout && clearTimeout(this.highlightTimeout)
}
Expand Down Expand Up @@ -622,6 +634,23 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
return { red, green, blue }
}

onDragEnter = (): void => {
const supportsDrop = this.props.part.instance.part.userEditOperations?.find(
(op) => op.type === UserEditingType.SOFIE && op.id === DefaultUserOperationsTypes.IMPORT_MOS_ITEM
)
if (!supportsDrop) return

this.setState({
dropActive: true,
})
}

onDragLeave = (): void => {
this.setState({
dropActive: false,
})
}

render(): JSX.Element | null {
// optimize early, if not inside viewport
if (!this.state.isInsideViewport) {
Expand Down Expand Up @@ -681,6 +710,8 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
'outside-quickloop': isOutsideActiveQuickLoop,
'quickloop-start': isQuickLoopStart,
'quickloop-end': isQuickLoopEnd,

'drop-active': this.state.dropActive,
},
this.props.className
)}
Expand All @@ -690,6 +721,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
role="region"
aria-roledescription={t('part')}
aria-label={this.props.part.instance.part.title}
data-part-id={this.props.part.partId}
>
{DEBUG_MODE && (
<div className="segment-timeline__debug-info">
Expand Down
88 changes: 82 additions & 6 deletions packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ import {
import { doUserAction, UserAction } from '../../lib/clientUserAction'
import { withTranslation } from 'react-i18next'
import { Translated } from '../../lib/ReactMeteorData/ReactMeteorData'
import { IngestAdlib } from '@sofie-automation/blueprints-integration'
import {
DefaultUserOperationImportMOSItem,
DefaultUserOperationsTypes,
IngestAdlib,
UserEditingType,
} from '@sofie-automation/blueprints-integration'
import { MeteorCall } from '../../lib/meteorApi'
import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
import { Buckets, Rundowns } from '../../collections'
import { BucketId, PartInstanceId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { Buckets, Rundowns, Segments } from '../../collections'
import { BucketId, PartInstanceId, RundownId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { MOS_DATA_IS_STRICT } from '@sofie-automation/meteor-lib/dist/mos'
import { mosTypes, MOS } from '@sofie-automation/meteor-lib/dist/mos'
import { RundownPlaylistCollectionUtil } from '../../collections/rundownPlaylistUtil'
Expand All @@ -35,7 +40,7 @@ import RundownViewEventBus, {
ItemDroppedEvent,
RundownViewEvents,
} from '@sofie-automation/meteor-lib/dist/triggers/RundownViewEventBus'
import { UIPartInstances } from '../Collections'
import { UIPartInstances, UIParts } from '../Collections'

interface IProps {
layout: RundownLayoutBase
Expand Down Expand Up @@ -210,6 +215,36 @@ export const ExternalFramePanel = withTranslation()(
return undefined
}

private findPartId(el: HTMLElement): {
rundownId: RundownId | undefined
segmentId: string | undefined
partId: string | undefined
} {
while (el.dataset.partId === undefined && el.parentElement) {
el = el.parentElement
}
const partId = el?.dataset.partId

if (partId) {
const part = UIParts.findOne(protectString(partId))
const supportsOp = part?.userEditOperations?.find(
(op) => op.type === UserEditingType.SOFIE && op.id === DefaultUserOperationsTypes.IMPORT_MOS_ITEM
)

if (supportsOp) {
const segment = Segments.findOne(part?.segmentId)

return {
rundownId: part?.rundownId,
segmentId: segment?.externalId,
partId: part?.externalId,
}
}
}

return { rundownId: undefined, partId: undefined, segmentId: undefined }
}

private getShowStyleBaseId() {
const { playlist } = this.props

Expand Down Expand Up @@ -243,11 +278,52 @@ export const ExternalFramePanel = withTranslation()(
}

receiveMOSItem(e: any, mosItem: MOS.IMOSItem) {
const { t } = this.props

console.log('Object received, passing onto blueprints', mosItem)

const bucketId = this.findBucketId(e.target)
if (bucketId) {
this.receiveMOSItemBucket(e, bucketId, mosItem)
return
}

const { rundownId, segmentId, partId } = this.findPartId(e.target)
if (rundownId && partId) {
console.log('pass to part', partId)
this.receiveMOSItemUserOp(e, rundownId, partId, segmentId, mosItem)
return
}
}

receiveMOSItemUserOp(
e: any,
rundownId: RundownId,
partId: string,
segmentId: string | undefined,
mosItem: MOS.IMOSItem
) {
const { t } = this.props

const operationTarget = { segmentExternalId: segmentId, partExternalId: partId, pieceExternalId: undefined }

doUserAction(t, e, UserAction.EXECUTE_USER_OPERATION, (e, ts) =>
MeteorCall.userAction.executeUserChangeOperation(
e,
ts,
rundownId,
operationTarget,
literal<DefaultUserOperationImportMOSItem>({
id: DefaultUserOperationsTypes.IMPORT_MOS_ITEM,

payloadType: 'MOS',
payload: MOS.stringifyMosObject(mosItem, MOS_DATA_IS_STRICT),
})
)
)
}

receiveMOSItemBucket(e: any, bucketId: BucketId, mosItem: MOS.IMOSItem) {
const { t } = this.props

const targetBucket = bucketId ? Buckets.findOne(bucketId) : Buckets.findOne()

const showStyleBaseId = this.getShowStyleBaseId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export function UserEditOperationMenuItems({
<span>{translateMessage(userEditOperation.label, t)}</span>
</MenuItem>
)
case UserEditingType.SOFIE:
return null
default:
assertNever(userEditOperation)
return null
Expand Down
Loading