diff --git a/packages/blueprints-integration/src/ingest.ts b/packages/blueprints-integration/src/ingest.ts index 7ca21420af..6051082eb4 100644 --- a/packages/blueprints-integration/src/ingest.ts +++ b/packages/blueprints-integration/src/ingest.ts @@ -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 { @@ -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 { /** Indicate that this change is from user operations */ diff --git a/packages/blueprints-integration/src/userEditing.ts b/packages/blueprints-integration/src/userEditing.ts index 607998cef7..e8799d0c97 100644 --- a/packages/blueprints-integration/src/userEditing.ts +++ b/packages/blueprints-integration/src/userEditing.ts @@ -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 @@ -40,11 +44,22 @@ export interface UserEditingDefinitionForm { currentValues: Record } +/** + * 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 { diff --git a/packages/corelib/src/dataModel/UserEditingDefinitions.ts b/packages/corelib/src/dataModel/UserEditingDefinitions.ts index 194b604054..b54b4f7ed8 100644 --- a/packages/corelib/src/dataModel/UserEditingDefinitions.ts +++ b/packages/corelib/src/dataModel/UserEditingDefinitions.ts @@ -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 @@ -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 +} diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index 60539616ec..62214063c7 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -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' @@ -58,6 +59,7 @@ import { UserEditingDefinitionAction, UserEditingDefinitionForm, UserEditingProperties, + UserEditingDefinitionSofieDefault, UserEditingType, } from '@sofie-automation/blueprints-integration/dist/userEditing' import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel' @@ -532,6 +534,11 @@ function translateUserEditsToBlueprint( schema: clone(userEdit.schema), currentValues: clone(userEdit.currentValues), } satisfies Complete + case UserEditingType.SOFIE: + return { + type: UserEditingType.SOFIE, + id: userEdit.id, + } satisfies Complete default: assertNever(userEdit) return undefined @@ -590,6 +597,11 @@ export function translateUserEditsFromBlueprint( currentValues: clone(userEdit.currentValues), translationNamespaces: unprotectStringArray(blueprintIds), } satisfies Complete + case UserEditingType.SOFIE: + return { + type: UserEditingType.SOFIE, + id: userEdit.id, + } satisfies Complete default: assertNever(userEdit) return undefined diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index d73a06c36f..78d574d9c2 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -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: ' '; diff --git a/packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx b/packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx index a34cffc5a0..70af5491e0 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx @@ -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__' @@ -99,6 +100,8 @@ interface IState { isTooSmallForText: boolean isTooSmallForDisplay: boolean highlight: boolean + + dropActive: boolean } export class SegmentTimelinePartClass extends React.Component>, IState> { constructor(props: Readonly>>) { @@ -137,6 +140,7 @@ export class SegmentTimelinePartClass extends React.Component { + 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) { @@ -681,6 +710,8 @@ export class SegmentTimelinePartClass extends React.Component {DEBUG_MODE && (
diff --git a/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx b/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx index ad488d0d13..fb3bce85d1 100644 --- a/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx +++ b/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx @@ -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' @@ -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 @@ -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 @@ -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({ + 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() diff --git a/packages/webui/src/client/ui/UserEditOperations/RenderUserEditOperations.tsx b/packages/webui/src/client/ui/UserEditOperations/RenderUserEditOperations.tsx index f33ab76b4e..57dcbe8aab 100644 --- a/packages/webui/src/client/ui/UserEditOperations/RenderUserEditOperations.tsx +++ b/packages/webui/src/client/ui/UserEditOperations/RenderUserEditOperations.tsx @@ -86,6 +86,8 @@ export function UserEditOperationMenuItems({ {translateMessage(userEditOperation.label, t)} ) + case UserEditingType.SOFIE: + return null default: assertNever(userEditOperation) return null