Skip to content

Commit

Permalink
Merge pull request #1342 from bbc/upstream/feat/properties-panel
Browse files Browse the repository at this point in the history
Properties Panel (RFC #1289)
  • Loading branch information
jstarpl authored Jan 23, 2025
2 parents a5d3101 + 054dcb1 commit b51ee26
Show file tree
Hide file tree
Showing 51 changed files with 2,639 additions and 357 deletions.
2 changes: 2 additions & 0 deletions meteor/server/api/rest/v1/typeConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ export function studioSettingsFrom(apiStudioSettings: APIStudioSettings): Comple
enableQuickLoop: apiStudioSettings.enableQuickLoop,
forceQuickLoopAutoNext: forceQuickLoopAutoNextFrom(apiStudioSettings.forceQuickLoopAutoNext),
fallbackPartDuration: apiStudioSettings.fallbackPartDuration ?? DEFAULT_FALLBACK_PART_DURATION,
enableUserEdits: apiStudioSettings.enableUserEdits,
allowAdlibTestingSegment: apiStudioSettings.allowAdlibTestingSegment,
allowHold: apiStudioSettings.allowHold ?? true, // Backwards compatible
allowPieceDirectPlay: apiStudioSettings.allowPieceDirectPlay ?? true, // Backwards compatible
Expand All @@ -391,6 +392,7 @@ export function APIStudioSettingsFrom(settings: IStudioSettings): Complete<APISt
enableQuickLoop: settings.enableQuickLoop,
forceQuickLoopAutoNext: APIForceQuickLoopAutoNextFrom(settings.forceQuickLoopAutoNext),
fallbackPartDuration: settings.fallbackPartDuration,
enableUserEdits: settings.enableUserEdits,
allowAdlibTestingSegment: settings.allowAdlibTestingSegment,
allowHold: settings.allowHold,
allowPieceDirectPlay: settings.allowPieceDirectPlay,
Expand Down
1 change: 1 addition & 0 deletions meteor/server/lib/rest/v1/studios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export interface APIStudioSettings {
forceQuickLoopAutoNext?: 'disabled' | 'enabled_when_valid_duration' | 'enabled_forcing_min_duration'
minimumTakeSpan?: number
fallbackPartDuration?: number
enableUserEdits?: boolean
allowAdlibTestingSegment?: boolean
allowHold?: boolean
allowPieceDirectPlay?: boolean
Expand Down
8 changes: 7 additions & 1 deletion packages/blueprints-integration/src/documents/part.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UserEditingDefinition } from '../userEditing'
import { UserEditingDefinition, UserEditingProperties } from '../userEditing'
import type { NoteSeverity } from '../lib'
import type { ITranslatableMessage } from '../translations'

Expand Down Expand Up @@ -88,6 +88,12 @@ export interface IBlueprintMutatablePart<TPrivateData = unknown, TPublicData = u
* User editing definitions for this part
*/
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 HackPartMediaObjectSubscription {
Expand Down
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 type { 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
8 changes: 7 additions & 1 deletion packages/blueprints-integration/src/documents/segment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UserEditingDefinition } from '../userEditing'
import { UserEditingDefinition, UserEditingProperties } from '../userEditing'

export enum SegmentDisplayMode {
Timeline = 'timeline',
Expand Down Expand Up @@ -52,6 +52,12 @@ export interface IBlueprintSegment<TPrivateData = unknown, TPublicData = unknown
* User editing definitions for this segment
*/
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
}
/** The Segment sent from Core */
export interface IBlueprintSegmentDB<TPrivateData = unknown, TPublicData = unknown>
Expand Down
34 changes: 32 additions & 2 deletions packages/blueprints-integration/src/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,41 @@ export interface UserOperationTarget {
pieceExternalId: string | undefined
}

export type DefaultUserOperations = {
id: '__sofie-move-segment' // Future: define properly
export enum DefaultUserOperationsTypes {
REVERT_SEGMENT = '__sofie-revert-segment',
REVERT_PART = '__sofie-revert-part',
REVERT_RUNDOWN = '__sofie-revert-rundown',
UPDATE_PROPS = '__sofie-update-props',
}

export interface DefaultUserOperationRevertRundown {
id: DefaultUserOperationsTypes.REVERT_RUNDOWN
payload: Record<string, never>
}

export interface DefaultUserOperationRevertSegment {
id: DefaultUserOperationsTypes.REVERT_SEGMENT
payload: Record<string, never>
}

export interface DefaultUserOperationRevertPart {
id: DefaultUserOperationsTypes.REVERT_PART
}

export interface DefaultUserOperationEditProperties {
id: DefaultUserOperationsTypes.UPDATE_PROPS
payload: {
pieceTypeProperties: { type: string; value: Record<string, any> }
globalProperties: Record<string, any>
}
}

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

export interface UserOperationChange<TCustomBlueprintOperations extends { id: string } = never> {
/** Indicate that this change is from user operations */
source: IngestChangeType.User
Expand Down
56 changes: 53 additions & 3 deletions packages/blueprints-integration/src/userEditing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { JSONBlob } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
import type { ITranslatableMessage } from './translations'
import type { JSONSchema } from '@sofie-automation/shared-lib/dist/lib/JSONSchemaTypes'
import { JSONSchema } from '@sofie-automation/shared-lib/dist/lib/JSONSchemaTypes'
import { SourceLayerType } from './content'

/**
* Description of a user performed editing operation allowed on an document
Expand All @@ -16,8 +17,10 @@ export interface UserEditingDefinitionAction {
id: string
/** Label to show to the user for this operation */
label: ITranslatableMessage
/** Icon to show to when this action is 'active' */
/** Icon to show when this action is 'active' */
svgIcon?: string
/** Icon to show when this action is 'disabled' */
svgIconInactive?: string
/** Whether this action should be indicated as being active */
isActive?: boolean
}
Expand All @@ -40,6 +43,53 @@ export interface UserEditingDefinitionForm {
export enum UserEditingType {
/** Action */
ACTION = 'action',
/** Form of selections */
/** Form */
FORM = 'form',
}

export interface UserEditingSourceLayer {
sourceLayerLabel: string
sourceLayerType: SourceLayerType
schema: JSONBlob<JSONSchema>
defaultValue?: Record<string, any>
}

export interface UserEditingProperties {
/**
* These properties are dependent on the (primary) piece type, the user will get the option
* to select the type of piece (from the SourceLayerTypes i.e. Camera or Split etc.) and then
* be presented the corresponding form
*
* example:
* {
* schema: {
* camera: '{ "type": "object", "properties": { "input": { "type": "number" } } }',
* split: '{ "type": "object", ... }',
* },
* currentValue: {
* type: 'camera',
* value: {
* input: 3
* },
* }
* }
*/
pieceTypeProperties?: {
schema: Record<string, UserEditingSourceLayer>
currentValue: { type: string; value: Record<string, any> }
}

/**
* These are properties that are available to edit regardless of the piece type, examples
* could be whether it an element is locked from NRCS updates
*
* if you do not want the piece type to be changed, then use only this field.
*/
globalProperties?: { schema: JSONBlob<JSONSchema>; currentValue: Record<string, any> }

/**
* A list of id's of operations to be exposed on the properties panel as buttons. These operations
* must be available on the element
*/
operations?: UserEditingDefinitionAction[]
}
8 changes: 7 additions & 1 deletion packages/corelib/src/dataModel/Part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ITranslatableMessage } from '../TranslatableMessage'
import { PartId, RundownId, SegmentId } from './Ids'
import { PartNote } from './Notes'
import { ReadonlyDeep } from 'type-fest'
import { CoreUserEditingDefinition } from './UserEditingDefinitions'
import { CoreUserEditingDefinition, CoreUserEditingProperties } from './UserEditingDefinitions'

export interface PartInvalidReason {
message: ITranslatableMessage
Expand Down Expand Up @@ -41,6 +41,12 @@ export interface DBPart extends Omit<IBlueprintPart, 'userEditOperations'> {
* User editing definitions for this part
*/
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 function isPartPlayable(part: Pick<ReadonlyDeep<DBPart>, 'invalid' | 'floated'>): boolean {
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
8 changes: 7 additions & 1 deletion packages/corelib/src/dataModel/Segment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SegmentDisplayMode, SegmentTimingInfo } from '@sofie-automation/blueprints-integration'
import { SegmentId, RundownId } from './Ids'
import { SegmentNote } from './Notes'
import { CoreUserEditingDefinition } from './UserEditingDefinitions'
import { CoreUserEditingDefinition, CoreUserEditingProperties } from './UserEditingDefinitions'

export enum SegmentOrphanedReason {
/** Segment is deleted from the NRCS but we still need it */
Expand Down Expand Up @@ -51,4 +51,10 @@ export interface DBSegment {
* User editing definitions for this segment
*/
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
}
59 changes: 57 additions & 2 deletions packages/corelib/src/dataModel/UserEditingDefinitions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { UserEditingType, JSONBlob, JSONSchema } from '@sofie-automation/blueprints-integration'
import type {
UserEditingType,
JSONBlob,
JSONSchema,
UserEditingSourceLayer,
} from '@sofie-automation/blueprints-integration'
import type { ITranslatableMessage } from '../TranslatableMessage'

export type CoreUserEditingDefinition = CoreUserEditingDefinitionAction | CoreUserEditingDefinitionForm
Expand All @@ -9,12 +14,17 @@ export interface CoreUserEditingDefinitionAction {
id: string
/** Label to show to the user for this operation */
label: ITranslatableMessage
/** Icon to show to when this action is 'active' */
/** Icon to show when this action is 'active' */
svgIcon?: string
/** Icon to show when this action is 'disabled' */
svgIconInactive?: string
/** Whether this action should be indicated as being active */
isActive?: boolean
}

/**
* A simple form based operation
*/
export interface CoreUserEditingDefinitionForm {
type: UserEditingType.FORM
/** Id of this operation */
Expand All @@ -28,3 +38,48 @@ export interface CoreUserEditingDefinitionForm {
/** Translation namespaces to use when rendering this form */
translationNamespaces: string[]
}

export interface CoreUserEditingProperties {
/**
* These properties are dependent on the (primary) piece type, the user will get the option
* to select the type of piece (from the SourceLayerTypes i.e. Camera or Split etc.) and then
* be presented the corresponding form
*
* example:
* {
* schema: {
* camera: '{ "type": "object", "properties": { "input": { "type": "number" } } }',
* split: '{ "type": "object", ... }',
* },
* currentValue: {
* type: 'camera',
* value: {
* input: 3
* },
* }
* }
*/
pieceTypeProperties?: {
schema: Record<string, UserEditingSourceLayer>
currentValue: { type: string; value: Record<string, any> }
}

/**
* These are properties that are available to edit regardless of the piece type, examples
* could be whether it an element is locked from NRCS updates
*
* if you do not want the piece type to be changed, then use only this field.
*/
globalProperties?: { schema: JSONBlob<JSONSchema>; currentValue: Record<string, any> }

/**
* A list of id's of operations to be exposed on the properties panel as buttons. These operations
* must be available on the element
*
* note - perhaps these should have their own full definitions?
*/
operations?: CoreUserEditingDefinitionAction[]

/** Translation namespaces to use when rendering this form */
translationNamespaces: string[]
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ If an integer property, whether to treat it as zero-based
### `ui:displayType`

Override the presentation with a special mode.
Currently only valid for string properties. Valid values are 'json'.

Currently only valid for:

- object properties. Valid values are 'json'.
- string properties. Valid values are 'base64-image'.
- boolean properties. Valid values are 'switch'.

### `tsEnumNames`

Expand Down
Loading

0 comments on commit b51ee26

Please sign in to comment.