From 50d869e0156c458fbe6b753fc813c6d174a06d57 Mon Sep 17 00:00:00 2001 From: Mint de Wit Date: Tue, 10 Dec 2024 15:57:31 +0000 Subject: [PATCH] chore: various review comments --- .../blueprints-integration/src/userEditing.ts | 37 +---- .../src/dataModel/UserEditingDefinitions.ts | 30 +--- .../job-worker/src/blueprints/context/lib.ts | 23 --- .../client/lib/forms/SchemaFormInPlace.scss | 137 +++++++++--------- .../client/lib/forms/SchemaFormInPlace.tsx | 52 ------- .../lib/forms/SchemaFormWithOverrides.tsx | 3 +- .../src/client/styles/propertiesPanel.scss | 9 +- packages/webui/src/client/ui/RundownView.tsx | 1 + .../RundownView/RundownRightHandControls.tsx | 39 +++-- .../RundownView/SelectedElementsContext.tsx | 70 +++++++-- .../selectedElementsContext.test.tsx | 12 +- .../ui/SegmentContainer/PieceElement.tsx | 1 + .../ui/SegmentTimeline/SegmentContextMenu.tsx | 13 +- .../ui/SegmentTimeline/SegmentTimeline.tsx | 23 ++- .../SegmentTimelineContainer.tsx | 3 +- .../ui/UserEditOperations/PropertiesPanel.tsx | 63 ++------ .../RenderUserEditOperations.tsx | 10 +- .../__tests__/PropertiesPanel.test.tsx | 16 +- 18 files changed, 210 insertions(+), 332 deletions(-) diff --git a/packages/blueprints-integration/src/userEditing.ts b/packages/blueprints-integration/src/userEditing.ts index 2dbfc2c818..d2972dd22c 100644 --- a/packages/blueprints-integration/src/userEditing.ts +++ b/packages/blueprints-integration/src/userEditing.ts @@ -6,10 +6,7 @@ import { SourceLayerType } from './content' /** * Description of a user performed editing operation allowed on an document */ -export type UserEditingDefinition = - | UserEditingDefinitionAction - | UserEditingDefinitionForm - | UserEditingDefinitionSourceLayerForm +export type UserEditingDefinition = UserEditingDefinitionAction | UserEditingDefinitionForm /** * A simple 'action' that can be performed @@ -26,8 +23,6 @@ export interface UserEditingDefinitionAction { svgIconInactive?: string /** Whether this action should be indicated as being active */ isActive?: boolean - /** Button Type */ - buttonType?: UserEditingButtonType } /** @@ -45,32 +40,11 @@ export interface UserEditingDefinitionForm { currentValues: Record } -/** - * A form based operation where the user first selects the type - * of form they want to use (i.e. Camera form vs VT form) - */ -export interface UserEditingDefinitionSourceLayerForm { - type: UserEditingType.SOURCE_LAYER_FORM - /** Id of this operation */ - id: string - /** Label to show to the user for this operation */ - label: ITranslatableMessage - /** The json schemas describing the form to display */ - schemas: Record - /** Current values to populate the form with */ - currentValues: { - type: SourceLayerType - value: Record - } -} - export enum UserEditingType { /** Action */ ACTION = 'action', /** Form */ FORM = 'form', - /** Forms that the user has to select a sourceLayerType first */ - SOURCE_LAYER_FORM = 'sourceLayerForm', } export interface UserEditingSourceLayer { @@ -80,15 +54,6 @@ export interface UserEditingSourceLayer { defaultValue?: Record } -export enum UserEditingButtonType { - /** Button */ - BUTTON = 'button', - /** Icon */ - SWITCH = 'switch', - /** Hidden */ - HIDDEN = 'hidden', -} - export interface UserEditingProperties { /** * These properties are dependent on the (primary) piece type, the user will get the option diff --git a/packages/corelib/src/dataModel/UserEditingDefinitions.ts b/packages/corelib/src/dataModel/UserEditingDefinitions.ts index 52509e994b..194b604054 100644 --- a/packages/corelib/src/dataModel/UserEditingDefinitions.ts +++ b/packages/corelib/src/dataModel/UserEditingDefinitions.ts @@ -3,15 +3,10 @@ import type { JSONBlob, JSONSchema, UserEditingSourceLayer, - UserEditingButtonType, - SourceLayerType, } from '@sofie-automation/blueprints-integration' import type { ITranslatableMessage } from '../TranslatableMessage' -export type CoreUserEditingDefinition = - | CoreUserEditingDefinitionAction - | CoreUserEditingDefinitionForm - | CoreUserEditingDefinitionSourceLayerForm +export type CoreUserEditingDefinition = CoreUserEditingDefinitionAction | CoreUserEditingDefinitionForm export interface CoreUserEditingDefinitionAction { type: UserEditingType.ACTION @@ -25,8 +20,6 @@ export interface CoreUserEditingDefinitionAction { svgIconInactive?: string /** Whether this action should be indicated as being active */ isActive?: boolean - //** Button Type */ - buttonType?: UserEditingButtonType } /** @@ -46,27 +39,6 @@ export interface CoreUserEditingDefinitionForm { translationNamespaces: string[] } -/** - * A form based operation where the user first selects the type - * of form they want to use (i.e. Camera form vs VT form) - */ -export interface CoreUserEditingDefinitionSourceLayerForm { - type: UserEditingType.SOURCE_LAYER_FORM - /** Id of this operation */ - id: string - /** Label to show to the user for this operation */ - label: ITranslatableMessage - /** Sourcelayer Type */ - schemas: Record - /** Translation namespaces to use when rendering this form */ - translationNamespaces: string[] - /** Current values to populate the form with */ - currentValues: { - type: SourceLayerType - value: Record - } -} - export interface CoreUserEditingProperties { /** * These properties are dependent on the (primary) piece type, the user will get the option diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index e9336b85a5..f2ba0479f4 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -13,7 +13,6 @@ import { CoreUserEditingDefinition, CoreUserEditingDefinitionAction, CoreUserEditingDefinitionForm, - CoreUserEditingDefinitionSourceLayerForm, CoreUserEditingProperties, } from '@sofie-automation/corelib/dist/dataModel/UserEditingDefinitions' import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' @@ -58,7 +57,6 @@ import { UserEditingDefinition, UserEditingDefinitionAction, UserEditingDefinitionForm, - UserEditingDefinitionSourceLayerForm, UserEditingProperties, UserEditingType, } from '@sofie-automation/blueprints-integration/dist/userEditing' @@ -521,7 +519,6 @@ function translateUserEditsToBlueprint( svgIcon: userEdit.svgIcon, svgIconInactive: userEdit.svgIconInactive, isActive: userEdit.isActive, - buttonType: userEdit.buttonType, } satisfies Complete case UserEditingType.FORM: return { @@ -531,14 +528,6 @@ function translateUserEditsToBlueprint( schema: clone(userEdit.schema), currentValues: clone(userEdit.currentValues), } satisfies Complete - case UserEditingType.SOURCE_LAYER_FORM: - return { - type: UserEditingType.SOURCE_LAYER_FORM, - id: userEdit.id, - label: omit(userEdit.label, 'namespaces'), - schemas: clone(userEdit.schemas), - currentValues: clone(userEdit.currentValues), - } satisfies Complete default: assertNever(userEdit) return undefined @@ -565,7 +554,6 @@ function translateUserEditPropertiesToBlueprint( svgIcon: userEdit.svgIcon, svgIconInactive: userEdit.svgIconInactive, isActive: userEdit.isActive, - buttonType: userEdit.buttonType, } satisfies Complete) ), } @@ -588,7 +576,6 @@ export function translateUserEditsFromBlueprint( svgIcon: userEdit.svgIcon, svgIconInactive: userEdit.svgIconInactive, isActive: userEdit.isActive, - buttonType: userEdit.buttonType, } satisfies Complete case UserEditingType.FORM: return { @@ -599,15 +586,6 @@ export function translateUserEditsFromBlueprint( currentValues: clone(userEdit.currentValues), translationNamespaces: unprotectStringArray(blueprintIds), } satisfies Complete - case UserEditingType.SOURCE_LAYER_FORM: - return { - type: UserEditingType.SOURCE_LAYER_FORM, - id: userEdit.id, - label: wrapTranslatableMessageFromBlueprints(userEdit.label, blueprintIds), - schemas: clone(userEdit.schemas), - currentValues: clone(userEdit.currentValues), - translationNamespaces: unprotectStringArray(blueprintIds), - } satisfies Complete default: assertNever(userEdit) return undefined @@ -635,7 +613,6 @@ export function translateUserEditPropertiesFromBlueprint( svgIcon: userEdit.svgIcon, svgIconInactive: userEdit.svgIconInactive, isActive: userEdit.isActive, - buttonType: userEdit.buttonType, } satisfies Complete) ), diff --git a/packages/webui/src/client/lib/forms/SchemaFormInPlace.scss b/packages/webui/src/client/lib/forms/SchemaFormInPlace.scss index 4e97ef45a4..bb18be6023 100644 --- a/packages/webui/src/client/lib/forms/SchemaFormInPlace.scss +++ b/packages/webui/src/client/lib/forms/SchemaFormInPlace.scss @@ -2,75 +2,74 @@ // to get styling on the current Sofie UI schema/overrides components // without changing current look and behavior in other places of Sofie .styled-schema-form { - width: 100%; - - // Force the base input-l class - .input-l { - width: 100% !important; - max-width: none !important; - margin-left: 0px; - border: none; - } - - // Force the select/text-input defaults - .select, - .inline-select, - .text-input { - display: block !important; - position: relative; - width: 100% !important; - } - - .input { - border: 1px solid #e5e7eb; - border-radius: 0.375rem; - padding: 0.5rem 0.75rem; - width: 100%; - - &:focus { - outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 1px #3b82f6; - } - } - - .label-text { - &:before { - content: none !important; - } - } - - .dropdown { - background: white; - border: 1px solid #e5e7eb; - border-radius: 0.375rem; - width: 100%; - max-width: 100%; - - .input, - .input-l { - border: none; - outline: none; - box-shadow: none; - } - - &:focus-within { - border-color: #3b82f6; - box-shadow: 0 0 0 1px #3b82f6; - } - } + width: 100%; - // Force the label over selector - .label-actual { - margin-bottom: 0.5rem !important; // Increased spacing between label and selector - } - - .label { - font-size: 0.875rem; - font-weight: 500; - color: #374151; - display: block; - } + // Force the base input-l class + .input-l { + width: 100% !important; + max-width: none !important; + margin-left: 0px; + border: none; + } + // Force the select/text-input defaults + .select, + .inline-select, + .text-input { + display: block !important; + position: relative; + width: 100% !important; + } - } \ No newline at end of file + .input { + border: 1px solid #e5e7eb; + border-radius: 0.375rem; + padding: 0.5rem 0.75rem; + width: 100%; + + &:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 1px #3b82f6; + background-color: unset !important; // origo >.> + } + } + + .label-text { + &:before { + content: none !important; + } + } + + .dropdown { + background: white; + border: 1px solid #e5e7eb; + border-radius: 0.375rem; + width: 100%; + max-width: 100%; + + .input, + .input-l { + border: none; + outline: none; + box-shadow: none; + } + + &:focus-within { + border-color: #3b82f6; + box-shadow: 0 0 0 1px #3b82f6; + } + } + + // Force the label over selector + .label-actual { + margin-bottom: 0.5rem !important; // Increased spacing between label and selector + } + + .label { + font-size: 0.875rem; + font-weight: 500; + color: #374151; + display: block; + } +} diff --git a/packages/webui/src/client/lib/forms/SchemaFormInPlace.tsx b/packages/webui/src/client/lib/forms/SchemaFormInPlace.tsx index 22daa3c47b..8e3f4d74f2 100644 --- a/packages/webui/src/client/lib/forms/SchemaFormInPlace.tsx +++ b/packages/webui/src/client/lib/forms/SchemaFormInPlace.tsx @@ -34,58 +34,6 @@ export function SchemaFormInPlace({ object, ...commonProps }: Readonly } -interface StyledSchemaFormInPlaceProps extends Omit { - /** The object to be modified in place */ - object: any - /** Optional CSS class to apply to the form wrapper */ - /** This may or may not work, as there's different styling in the sub components */ - className?: string - /** Whether to use compact styling */ - compact?: boolean - /** Width for the form (e.g., '300px', '100%') */ - width?: string -} - -export function StyledSchemaFormInPlace({ - object, - className = '', - compact = false, - width = '', - ...commonProps -}: Readonly): JSX.Element { - // This is a hack to avoid issues with the UI re-rendering as 'nothing' changed - const [editCount, setEditCount] = useState(0) - const forceRender = useCallback(() => setEditCount((v) => v + 1), []) - - const helper = useCallback(() => new OverrideOpHelperInPlace(object, forceRender), [object, forceRender]) - - const wrappedItem = useMemo( - () => - literal>({ - type: 'normal', - id: 'not-used' + editCount, - computed: object, - defaults: undefined, - overrideOps: [], - }), - [object, editCount] - ) - - const style = { - width: width || '300px', - maxWidth: width || '300px', - minWidth: width || '300px', - } - - const formClasses = ['styled-schema-form', compact ? 'space-y-2' : 'space-y-4', className].filter(Boolean).join(' ') - - return ( -
- -
- ) -} - /** * An alternate OverrideOpHelper designed to directly mutate an object, instead of using the `ObjectWithOverrides` system. * This allows us to have one SchemaForm implementation that can handle working with `ObjectWithOverrides`, and simpler options diff --git a/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx b/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx index bf1a03a614..f3199fd870 100644 --- a/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx +++ b/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx @@ -58,8 +58,7 @@ interface FormComponentProps { function useChildPropsForFormComponent(props: Readonly): FormComponentProps { return useMemo(() => { - // If a tittle is added to the Schema use that if not use the props.attr instead - const title = props.schema.title || getSchemaUIField(props.schema, SchemaFormUIField.Title) || props.attr + const title = getSchemaUIField(props.schema, SchemaFormUIField.Title) || props.schema.title || props.attr const description = getSchemaUIField(props.schema, SchemaFormUIField.Description) return { diff --git a/packages/webui/src/client/styles/propertiesPanel.scss b/packages/webui/src/client/styles/propertiesPanel.scss index 10eb99c1eb..50495487b1 100644 --- a/packages/webui/src/client/styles/propertiesPanel.scss +++ b/packages/webui/src/client/styles/propertiesPanel.scss @@ -13,11 +13,12 @@ z-index: 292; transform: translateX(100%); - transition: transform 0.2s ease-out; + animation: show 200ms 0ms cubic-bezier(0.38, 0.97, 0.56, 0.76) forwards; - // Add a class that will be applied when the component mounts - &.is-mounted { - transform: translateX(0%); + @keyframes show { + 100% { + transform: unset; + } } &::before { diff --git a/packages/webui/src/client/ui/RundownView.tsx b/packages/webui/src/client/ui/RundownView.tsx index c9525b0faf..350bdaf943 100644 --- a/packages/webui/src/client/ui/RundownView.tsx +++ b/packages/webui/src/client/ui/RundownView.tsx @@ -3020,6 +3020,7 @@ const RundownViewContent = translateWithTracker, filter: NoticeLevel) => void onToggleSupportPanel?: (e: React.MouseEvent) => void onTake?: (e: React.MouseEvent) => void @@ -155,21 +156,9 @@ export function RundownRightHandControls(props: Readonly): JSX.Element { className="type-notification" title={t('Notes')} /> - - {(context) => { - const isOpen = context.listSelectedElements().length > 0 && !props.isNotificationCenterOpen - return ( - - ) - }} - + {props.isUserEditsEnabled && ( + + )} + ) + }} + + ) +} diff --git a/packages/webui/src/client/ui/RundownView/SelectedElementsContext.tsx b/packages/webui/src/client/ui/RundownView/SelectedElementsContext.tsx index d1ea0f4f63..02232a5429 100644 --- a/packages/webui/src/client/ui/RundownView/SelectedElementsContext.tsx +++ b/packages/webui/src/client/ui/RundownView/SelectedElementsContext.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react' import { AdLibActionId, PartId, @@ -8,6 +8,12 @@ import { SegmentId, } from '@sofie-automation/corelib/dist/dataModel/Ids' import { assertNever } from '@sofie-automation/corelib/dist/lib' +import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' +import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' +import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' +import { Tracker } from 'meteor/tracker' +import { Pieces, Segments } from '../../collections' +import { UIParts } from '../Collections' interface RundownElement { type: 'rundown' @@ -40,7 +46,7 @@ interface AdlibActionElement { } // Union types for all possible elements -type SelectedElement = +export type SelectedElement = | RundownElement | SegmentElement | PartElement @@ -118,18 +124,18 @@ const defaultSelectionContext: SelectionContextType = { getSelectedCount: () => 0, } -export const SelectedElementsContext = React.createContext(defaultSelectionContext) +export const SelectedElementsContext = createContext(defaultSelectionContext) export const SelectedElementProvider: React.FC<{ children: React.ReactNode maxSelections?: number // Optional prop to limit maximum selections }> = ({ children, maxSelections = 10 }) => { - const [selectedElements, dispatch] = React.useReducer( + const [selectedElements, dispatch] = useReducer( (state: Map, action: SelectionAction) => selectionReducer(state, action, maxSelections), new Map() ) - const value = React.useMemo( + const value = useMemo( () => ({ isSelected: (elementId: ElementId) => { return selectedElements.has(elementId) @@ -169,20 +175,58 @@ export const SelectedElementProvider: React.FC<{ } // Custom hook for using the selection context -export const useSelection = (): SelectionContextType => { - const context = React.useContext(SelectedElementsContext) - if (!context) { - throw new Error('useSelection must be used within a SelectedElementProvider') - } +export const useSelectedElementsContext = (): SelectionContextType => { + const context = useContext(SelectedElementsContext) + return context } // Helper hook for common selection patterns export const useElementSelection = (element: SelectedElement): { isSelected: boolean; toggleSelection: () => void } => { - const { isSelected, toggleSelection } = useSelection() + const { isSelected, toggleSelection } = useSelectedElementsContext() + + return { + isSelected: useMemo(() => isSelected(element.elementId), [isSelected, element.elementId]), + toggleSelection: useCallback(() => toggleSelection(element), [toggleSelection, element]), + } +} + +export function useSelectedElements( + selectedElement: SelectedElement, + clearPendingChange: () => void +): { + piece: Piece | undefined + part: DBPart | undefined + segment: DBSegment | undefined + rundownId: RundownId | undefined +} { + const [piece, setPiece] = useState(undefined) + const [part, setPart] = useState(undefined) + const [segment, setSegment] = useState(undefined) + const rundownId = piece ? piece.startRundownId : part ? part.rundownId : segment?.rundownId + + useEffect(() => { + clearPendingChange() // element id changed so any pending change is for an old element + + const pieceComputation = Tracker.nonreactive(() => + Tracker.autorun(() => { + const piece = Pieces.findOne(selectedElement.elementId) + const part = UIParts.findOne({ _id: piece ? piece.startPartId : selectedElement?.elementId }) + const segment = Segments.findOne({ _id: part ? part.segmentId : selectedElement?.elementId }) + + setPiece(piece) + setPart(part) + setSegment(segment) + }) + ) + + return () => pieceComputation.stop() + }, [selectedElement?.elementId]) return { - isSelected: React.useMemo(() => isSelected(element.elementId), [isSelected, element.elementId]), - toggleSelection: React.useCallback(() => toggleSelection(element), [toggleSelection, element]), + piece, + part, + segment, + rundownId, } } diff --git a/packages/webui/src/client/ui/RundownView/__tests__/selectedElementsContext.test.tsx b/packages/webui/src/client/ui/RundownView/__tests__/selectedElementsContext.test.tsx index 401f3235a6..d17952dfc8 100644 --- a/packages/webui/src/client/ui/RundownView/__tests__/selectedElementsContext.test.tsx +++ b/packages/webui/src/client/ui/RundownView/__tests__/selectedElementsContext.test.tsx @@ -1,7 +1,7 @@ import React from 'react' // eslint-disable-next-line node/no-unpublished-import import { renderHook, act } from '@testing-library/react' -import { SelectedElementProvider, useSelection, useElementSelection } from '../SelectedElementsContext' +import { SelectedElementProvider, useSelectedElementsContext, useElementSelection } from '../SelectedElementsContext' import { protectString } from '@sofie-automation/corelib/dist/protectedString' import { RundownId, SegmentId, PartInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' @@ -27,14 +27,14 @@ describe('SelectedElementProvider', () => { ) test('init with no selections', () => { - const { result } = renderHook(() => useSelection(), { wrapper }) + const { result } = renderHook(() => useSelectedElementsContext(), { wrapper }) expect(result.current.listSelectedElements().length).toBe(0) expect(result.current.getSelectedCount()).toBe(0) }) test('clearAndSetSelection', () => { - const { result } = renderHook(() => useSelection(), { wrapper }) + const { result } = renderHook(() => useSelectedElementsContext(), { wrapper }) const element1 = createRundownElement('rundown1') const element2 = createRundownElement('rundown2') @@ -53,7 +53,7 @@ describe('SelectedElementProvider', () => { }) test('toggleSelection', () => { - const { result } = renderHook(() => useSelection(), { wrapper }) + const { result } = renderHook(() => useSelectedElementsContext(), { wrapper }) const element = createSegmentElement('segment1') act(() => { @@ -71,7 +71,7 @@ describe('SelectedElementProvider', () => { const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ) - const { result } = renderHook(() => useSelection(), { wrapper }) + const { result } = renderHook(() => useSelectedElementsContext(), { wrapper }) const elements = [ createRundownElement('rundown1'), @@ -92,7 +92,7 @@ describe('SelectedElementProvider', () => { }) test('clearSelections removes all selections', () => { - const { result } = renderHook(() => useSelection(), { wrapper }) + const { result } = renderHook(() => useSelectedElementsContext(), { wrapper }) act(() => { result.current.addSelection(createRundownElement('rundown1')) diff --git a/packages/webui/src/client/ui/SegmentContainer/PieceElement.tsx b/packages/webui/src/client/ui/SegmentContainer/PieceElement.tsx index cc14b3b9e2..99cdf9f13e 100644 --- a/packages/webui/src/client/ui/SegmentContainer/PieceElement.tsx +++ b/packages/webui/src/client/ui/SegmentContainer/PieceElement.tsx @@ -45,6 +45,7 @@ export const PieceElement = React.forwardRef
- this.props.onEditSegmentProps(part.instance.segmentId)}> + this.props.onEditSegmentProps(part.instance.segmentId)}> {t('Edit Segment Properties')} @@ -192,10 +197,10 @@ export const SegmentContextMenu = withTranslation()( {this.props.enableUserEdits && ( <>
- this.props.onEditSegmentProps(part.instance.segmentId)}> + this.props.onEditSegmentProps(part.instance.segmentId)}> {t('Edit Segment Properties')} - this.props.onEditPartProps(part.instance.part._id)}> + this.props.onEditPartProps(part.instance.part._id)}> {t('Edit Part Properties')} diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index 17277ef407..2c52980b62 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -57,7 +57,6 @@ import { logger } from '../../lib/logging' import * as RundownResolver from '../../lib/RundownResolver' import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' import { SelectedElementsContext } from '../RundownView/SelectedElementsContext' -import classNames from 'classnames' interface IProps { id: string @@ -93,8 +92,8 @@ interface IProps { onFollowLiveLine?: (state: boolean, event: any) => void onShowEntireSegment?: (event: React.MouseEvent | undefined) => void onContextMenu?: (contextMenuContext: IContextMenuContext) => void - onItemClick?: (piece: PieceUi, e: React.MouseEvent) => void - onPieceDoubleClick?: (item: PieceUi, e: React.MouseEvent) => void + onPieceClick?: (piece: PieceUi, e: React.MouseEvent) => void + onPieceDoubleClick?: (piece: PieceUi, e: React.MouseEvent) => void onHeaderNoteClick?: (segmentId: SegmentId, level: NoteSeverity) => void onSwitchViewMode?: (newViewMode: SegmentViewMode) => void segmentRef?: (el: SegmentTimelineClass, segmentId: SegmentId) => void @@ -103,7 +102,6 @@ interface IProps { showCountdownToSegment: boolean showDurationSourceLayers?: Set fixedSegmentDuration: boolean | undefined - // isSelected: boolean } interface IStateHeader { timelineWidth: number @@ -790,7 +788,7 @@ export class SegmentTimelineClass extends React.Component

{this.props.segment.name} diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx index 449c9973b4..0895504afa 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx @@ -664,7 +664,7 @@ const SegmentTimelineContainerContent = withResolvedSegment( maxTimeScale={this.state.maxTimeScale} onRecalculateMaxTimeScale={this.updateMaxTimeScale} showingAllSegment={this.state.showingAllSegment} - onItemClick={this.props.onPieceClick} + onPieceClick={this.props.onPieceClick} onPieceDoubleClick={this.props.onPieceDoubleClick} onCollapseOutputToggle={this.onCollapseOutputToggle} collapsedOutputs={this.state.collapsedOutputs} @@ -694,7 +694,6 @@ const SegmentTimelineContainerContent = withResolvedSegment( showCountdownToSegment={this.props.showCountdownToSegment} fixedSegmentDuration={this.props.fixedSegmentDuration} showDurationSourceLayers={this.props.showDurationSourceLayers} - //isSelected={this.props.isSelected} /> )} {this.props.segmentui.showShelf && this.props.adLibSegmentUi && ( diff --git a/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx b/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx index 72605c15bf..671766f3a2 100644 --- a/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx +++ b/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx @@ -15,60 +15,24 @@ import { import { literal } from '@sofie-automation/corelib/dist/lib' import classNames from 'classnames' import { useTranslation } from 'react-i18next' -import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData' -import { Pieces, Segments } from '../../collections' -import { UIParts } from '../Collections' -import { useSelection } from '../RundownView/SelectedElementsContext' -import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' +import { useSelectedElements, useSelectedElementsContext } from '../RundownView/SelectedElementsContext' import { RundownUtils } from '../../lib/rundown' import * as CoreIcon from '@nrk/core-icons/jsx' -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useState } from 'react' import { SchemaFormWithState } from '../../lib/forms/SchemaFormWithState' import { translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage' type PendingChange = DefaultUserOperationEditProperties['payload'] export function PropertiesPanel(): JSX.Element { - const { listSelectedElements, clearSelections } = useSelection() + const { listSelectedElements, clearSelections } = useSelectedElementsContext() const selectedElement = listSelectedElements()?.[0] const { t } = useTranslation() - const [pendingChange, setPendingChange] = React.useState(undefined) + const [pendingChange, setPendingChange] = useState(undefined) const hasPendingChanges = !!pendingChange - const [isAnimatedIn, setIsAnimatedIn] = React.useState(false) - React.useEffect(() => { - const timer = setTimeout(() => { - setIsAnimatedIn(true) - }, 10) - return () => clearTimeout(timer) - }, []) - - React.useEffect(() => { - return () => { - Array.from(document.querySelectorAll('.propertiespanel-pop-up.is-highlighted')).forEach((element: Element) => { - if (element instanceof HTMLElement) { - element.style.animationName = '' - } - }) - } - }, []) - - const piece = useTracker(() => { - setPendingChange(undefined) - return Pieces.findOne(selectedElement?.elementId) - }, [selectedElement?.elementId]) - - const part = useTracker(() => { - setPendingChange(undefined) - return UIParts.findOne({ _id: selectedElement?.elementId }) - }, [selectedElement?.elementId]) - - const segment: DBSegment | undefined = useTracker( - () => Segments.findOne({ _id: part ? part.segmentId : selectedElement?.elementId }), - [selectedElement?.elementId, part?.segmentId] - ) - const rundownId = piece ? piece.startRundownId : part ? part.rundownId : segment?.rundownId + const { piece, part, segment, rundownId } = useSelectedElements(selectedElement, () => setPendingChange(undefined)) const handleCommitChanges = async (e: React.MouseEvent) => { if (!rundownId || !selectedElement || !pendingChange) return @@ -85,7 +49,7 @@ export function PropertiesPanel(): JSX.Element { { segmentExternalId: segment?.externalId, partExternalId: part?.externalId, - pieceExternalId: undefined, + pieceExternalId: piece?.externalId, }, literal({ id: DefaultUserOperationsTypes.UPDATE_PROPS, @@ -111,9 +75,9 @@ export function PropertiesPanel(): JSX.Element { }, { id: - selectedElement.type === 'partInstance' - ? DefaultUserOperationsTypes.REVERT_PART - : DefaultUserOperationsTypes.REVERT_SEGMENT, + selectedElement.type === 'segment' + ? DefaultUserOperationsTypes.REVERT_SEGMENT + : DefaultUserOperationsTypes.REVERT_PART, } ) ) @@ -174,7 +138,7 @@ export function PropertiesPanel(): JSX.Element { : undefined return ( -
+
{userEditOperations && @@ -226,7 +190,7 @@ export function PropertiesPanel(): JSX.Element {
+