From 4e981d00706a3216388dee594c7fa18266d7e149 Mon Sep 17 00:00:00 2001 From: Mint de Wit Date: Fri, 18 Oct 2024 11:03:49 +0000 Subject: [PATCH 1/2] feat: allow drag from mos-plugin to part --- .../expectedPackages/generate.ts | 3 - packages/blueprints-integration/src/ingest.ts | 7 +- .../blueprints-integration/src/userEditing.ts | 17 +++- .../src/dataModel/UserEditingDefinitions.ts | 18 +++- .../job-worker/src/blueprints/context/lib.ts | 12 +++ packages/shared-lib/src/core/model/ingest.ts | 18 ++++ .../webui/src/client/styles/rundownView.scss | 24 +++-- .../Parts/SegmentTimelinePart.tsx | 34 +++++++- .../ui/SegmentTimeline/SegmentTimeline.tsx | 2 +- .../client/ui/Shelf/ExternalFramePanel.tsx | 87 +++++++++++++++++-- .../RenderUserEditOperations.tsx | 2 + 11 files changed, 200 insertions(+), 24 deletions(-) create mode 100644 packages/shared-lib/src/core/model/ingest.ts diff --git a/meteor/server/publications/packageManager/expectedPackages/generate.ts b/meteor/server/publications/packageManager/expectedPackages/generate.ts index 3320f89d87..cdc21bf353 100644 --- a/meteor/server/publications/packageManager/expectedPackages/generate.ts +++ b/meteor/server/publications/packageManager/expectedPackages/generate.ts @@ -17,7 +17,6 @@ import { CustomPublishCollection } from '../../../lib/customPublication' import { logger } from '../../../logging' import { ExpectedPackagesContentCache } from './contentCache' import type { StudioFields } from './publication' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' /** * Regenerate the output for the provided ExpectedPackage `regenerateIds`, updating the data in `collection` as needed @@ -39,7 +38,6 @@ export async function updateCollectionForExpectedPackageIds( ): Promise { const updatedDocIds = new Set() const missingExpectedPackageIds = new Set() - const packageContainers = applyAndValidateOverrides(studio.packageContainersWithOverrides).obj for (const packageId of regenerateIds) { const packageDoc = contentCache.ExpectedPackages.findOne(packageId) @@ -110,7 +108,6 @@ export async function updateCollectionForPieceInstanceIds( ): Promise { const updatedDocIds = new Set() const missingPieceInstanceIds = new Set() - const packageContainers = applyAndValidateOverrides(studio.packageContainersWithOverrides).obj for (const pieceInstanceId of regenerateIds) { const pieceInstanceDoc = contentCache.PieceInstances.findOne(pieceInstanceId) diff --git a/packages/blueprints-integration/src/ingest.ts b/packages/blueprints-integration/src/ingest.ts index 057a20a9da..351632eae5 100644 --- a/packages/blueprints-integration/src/ingest.ts +++ b/packages/blueprints-integration/src/ingest.ts @@ -1,4 +1,5 @@ import { IngestPart, IngestSegment } from '@sofie-automation/shared-lib/dist/peripheralDevice/ingest' +import { DefaultUserOperations } from '@sofie-automation/shared-lib/dist/core/model/ingest' import { IBlueprintRundownDBData } from './documents' import { ReadonlyDeep } from 'type-fest' import { SofieIngestRundown } from './ingest-types' @@ -10,6 +11,7 @@ export { IngestSegment, IngestAdlib, } from '@sofie-automation/shared-lib/dist/peripheralDevice/ingest' +export * from '@sofie-automation/shared-lib/dist/core/model/ingest' /** The IngestRundown is extended with data from Core */ export interface ExtendedIngestRundown @@ -124,11 +126,6 @@ export interface UserOperationTarget { pieceExternalId: string | undefined } -export type DefaultUserOperations = { - id: '__sofie-move-segment' // Future: define properly - payload: Record -} - export interface UserOperationChange { /** Indicate that this change is from user operations */ source: IngestChangeType.User diff --git a/packages/blueprints-integration/src/userEditing.ts b/packages/blueprints-integration/src/userEditing.ts index b199341768..f3e0978e73 100644 --- a/packages/blueprints-integration/src/userEditing.ts +++ b/packages/blueprints-integration/src/userEditing.ts @@ -1,11 +1,15 @@ import { 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 { DefaultUserOperationsIDs } 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 @@ -37,9 +41,20 @@ export interface UserEditingDefinitionForm { currentValues: Record } +/** + * A simple form based operation + */ +export interface UserEditingDefinitionSofieDefault { + type: UserEditingType.SOFIE + /** Id of this operation */ + id: DefaultUserOperationsIDs +} + export enum UserEditingType { /** Action */ ACTION = 'action', /** Form of selections */ FORM = 'form', + /** Operation for the Built-in Sofie Rich Editing UI */ + SOFIE = 'sofie', } diff --git a/packages/corelib/src/dataModel/UserEditingDefinitions.ts b/packages/corelib/src/dataModel/UserEditingDefinitions.ts index 4930fbfbda..4b54c1a3aa 100644 --- a/packages/corelib/src/dataModel/UserEditingDefinitions.ts +++ b/packages/corelib/src/dataModel/UserEditingDefinitions.ts @@ -1,7 +1,15 @@ -import type { UserEditingType, JSONBlob, JSONSchema } from '@sofie-automation/blueprints-integration' +import type { + UserEditingType, + JSONBlob, + JSONSchema, + DefaultUserOperationsIDs, +} 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 @@ -28,3 +36,9 @@ export interface CoreUserEditingDefinitionForm { /** Translation namespaces to use when rendering this form */ translationNamespaces: string[] } + +export interface CoreUserEditingDefinitionSofie { + type: UserEditingType.SOFIE + /** Id of this operation */ + id: DefaultUserOperationsIDs +} diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index 13cb6a4509..ab5f3812b0 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -13,6 +13,7 @@ import { CoreUserEditingDefinition, CoreUserEditingDefinitionAction, CoreUserEditingDefinitionForm, + 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' @@ -56,6 +57,7 @@ import { UserEditingDefinition, UserEditingDefinitionAction, UserEditingDefinitionForm, + UserEditingDefinitionSofieDefault, UserEditingType, } from '@sofie-automation/blueprints-integration/dist/userEditing' import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel' @@ -520,6 +522,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 @@ -554,6 +561,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/shared-lib/src/core/model/ingest.ts b/packages/shared-lib/src/core/model/ingest.ts new file mode 100644 index 0000000000..44c3577583 --- /dev/null +++ b/packages/shared-lib/src/core/model/ingest.ts @@ -0,0 +1,18 @@ +export enum DefaultUserOperationsIDs { + MoveSegment = '__sofie-move-segment', + ImportMOSItem = '__sofie-import-mos', +} + +export type DefaultUserOperations = MoveSegmentUserOperations | ImportMOSItemUserOperation + +export type MoveSegmentUserOperations = { + id: DefaultUserOperationsIDs.MoveSegment + payload: Record // Future: define properly +} + +export type ImportMOSItemUserOperation = { + id: DefaultUserOperationsIDs.ImportMOSItem + + payloadType: string + payload: any +} diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index 18e9253a41..f21ce2dd7b 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -1399,12 +1399,13 @@ svg.icon { &.quickloop-start { left: -2px; - &::before, &::after { + &::before, + &::after { z-index: 1; margin-left: 5px; border-left-color: white; } - + .segment-timeline__part__nextline__label { z-index: 5; left: 5px; @@ -1571,6 +1572,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: ' '; @@ -1586,7 +1600,6 @@ svg.icon { } } - &:not(.live) { .segment-timeline__part__nextline.auto-next:not(.segment-timeline__part__nextline--endline), .segment-timeline__part__nextline.invalid:not(.segment-timeline__part__nextline--endline) { @@ -1832,7 +1845,8 @@ svg.icon { right: 0; } - .segment-timeline__part__quickloop-start, .segment-timeline__part__quickloop-end { + .segment-timeline__part__quickloop-start, + .segment-timeline__part__quickloop-end { background: $segment-background-color; padding: 0 0.3em; margin-bottom: -2px; @@ -1849,7 +1863,7 @@ svg.icon { .segment-timeline__part__quickloop-end { padding-right: 0.6em; - margin-left: auto + margin-left: auto; } } diff --git a/packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx b/packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx index 1d8f31ce31..a767c2f581 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 { DefaultUserOperationsIDs, 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 === DefaultUserOperationsIDs.ImportMOSItem + ) + if (!supportsDrop) return + + this.setState({ + dropActive: true, + }) + } + + onDragLeave = () => { + 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/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index d93b94ce03..9cba526e05 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -1248,7 +1248,7 @@ function HeaderEditStates({ userEditOperations }: HeaderEditStatesProps) {
{userEditOperations && userEditOperations.map((operation) => { - if (operation.type === UserEditingType.FORM || !operation.svgIcon || !operation.isActive) return null + if (operation.type !== UserEditingType.ACTION || !operation.svgIcon || !operation.isActive) return null return (
op.type === UserEditingType.SOFIE && op.id === DefaultUserOperationsIDs.ImportMOSItem + ) + + 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 +277,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: DefaultUserOperationsIDs.ImportMOSItem, + + 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 6b0577d65d..989fe4713d 100644 --- a/packages/webui/src/client/ui/UserEditOperations/RenderUserEditOperations.tsx +++ b/packages/webui/src/client/ui/UserEditOperations/RenderUserEditOperations.tsx @@ -80,6 +80,8 @@ export function RenderUserEditOperations( {translateMessage(userEditOperation.label, i18nTranslator)} ) + case UserEditingType.SOFIE: + return null default: assertNever(userEditOperation) return null From 41fa33656774b5c34d23d35004389feb216c898a Mon Sep 17 00:00:00 2001 From: Mint de Wit Date: Mon, 21 Oct 2024 08:45:44 +0000 Subject: [PATCH 2/2] chore: remove stray import --- packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx b/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx index a7f995d69b..150d865366 100644 --- a/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx +++ b/packages/webui/src/client/ui/Shelf/ExternalFramePanel.tsx @@ -25,7 +25,7 @@ import { Translated } from '../../lib/ReactMeteorData/ReactMeteorData' import { IngestAdlib, UserEditingType } from '@sofie-automation/blueprints-integration' import { MeteorCall } from '../../lib/meteorApi' import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' -import { Buckets, Parts, Rundowns, Segments } from '../../collections' +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'