diff --git a/packages/meteor-lib/src/triggers/RundownViewEventBus.ts b/packages/meteor-lib/src/triggers/RundownViewEventBus.ts index 60b575aeba..9c095d7de7 100644 --- a/packages/meteor-lib/src/triggers/RundownViewEventBus.ts +++ b/packages/meteor-lib/src/triggers/RundownViewEventBus.ts @@ -48,6 +48,7 @@ export enum RundownViewEvents { TOGGLE_SHELF_DROPZONE = 'toggleShelfDropzone', ITEM_DROPPED = 'itemDropped', + CLOSE_NOTIFICATIONS = 'closeNotifications', } export interface IEventContext { @@ -156,6 +157,7 @@ export interface RundownViewEventBusEvents { [RundownViewEvents.CREATE_SNAPSHOT_FOR_DEBUG]: [e: BaseEvent] [RundownViewEvents.TOGGLE_SHELF_DROPZONE]: [e: ToggleShelfDropzoneEvent] [RundownViewEvents.ITEM_DROPPED]: [e: ItemDroppedEvent] + [RundownViewEvents.CLOSE_NOTIFICATIONS]: [] } class RundownViewEventBus0 extends EventEmitter {} diff --git a/packages/webui/src/client/styles/propertiesPanel.scss b/packages/webui/src/client/styles/propertiesPanel.scss index 10dfaa86eb..7afaf69913 100644 --- a/packages/webui/src/client/styles/propertiesPanel.scss +++ b/packages/webui/src/client/styles/propertiesPanel.scss @@ -32,6 +32,10 @@ background: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.15) 100%); } + .propertiespanel-pop-up__label-with-icon { + margin-left: 0.5em; + } + .propertiespanel-pop-up { background: #2e2e2e; border-radius: 1px; @@ -64,8 +68,6 @@ letter-spacing: 0.5px; > .svg { - width: 1em; - height: 1.2em; flex-shrink: 0; } > .title { @@ -87,13 +89,16 @@ flex-shrink: 0; } > .propertiespanel-pop-up_close { - height: 1em; - margin-left: 1em; background-color: unset; border: none; } } + .propertiespanel-pop-up__buttons-container { + display: flex; + gap: 0.5em; + } + > .propertiespanel-pop-up__footer { flex: 1; flex: 0 0 0; @@ -109,7 +114,9 @@ > .propertiespanel-pop-up__button, .propertiespanel-pop-up__button-group .propertiespanel-pop-up__button { - display: block; + display: flex; + gap: 0.5em; + align-items: center; border-radius: 5px; border: 1px solid #7f7f7f; @@ -227,14 +234,14 @@ } .propertiespanel-pop-up__button { - // margin-top: 10px; background: #636363; padding: 10px; - gap: 10px; border-radius: 5px; border: 1px solid #7f7f7f; color: #dfdfdf; + gap: 0.5em; + font-size: 0.875em; font-weight: 500; @@ -266,63 +273,6 @@ 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; - // 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; - // } - // } - .form-switch { margin: 0.5rem 0; @@ -337,13 +287,6 @@ margin-bottom: 0.5rem; // Increased spacing between label and selector margin-top: 0.5rem; // Clearance from the previous } - - // .label { - // font-size: 0.875rem; - // font-weight: 500; - // color: #374151; - // display: block; - // } } } } diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index 32ee778f21..5fac7716f9 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -161,7 +161,7 @@ $break-width: 35rem; } &.properties-panel-open { - padding-right: $properties-panel-width; + padding-right: calc(#{$properties-panel-width} - 3.5em); transition: 0s padding-right 1s; > .rundown-header .rundown-overview { @@ -209,8 +209,13 @@ body.no-overflow { bottom: 0; right: 0; - background: - linear-gradient(-45deg, $color-status-fatal 33%, transparent 33%, transparent 66%, $color-status-fatal 66%), + background: linear-gradient( + -45deg, + $color-status-fatal 33%, + transparent 33%, + transparent 66%, + $color-status-fatal 66% + ), linear-gradient(-45deg, $color-status-fatal 33%, transparent 33%, transparent 66%, $color-status-fatal 66%), linear-gradient(-45deg, $color-status-fatal 33%, transparent 33%, transparent 66%, $color-status-fatal 66%), linear-gradient(-45deg, $color-status-fatal 33%, transparent 33%, transparent 66%, $color-status-fatal 66%); @@ -1100,8 +1105,7 @@ svg.icon { } .segment-timeline__part { .segment-timeline__part__invalid-cover { - background-image: - repeating-linear-gradient( + background-image: repeating-linear-gradient( 45deg, var(--invalid-reason-color-transparent) 0%, var(--invalid-reason-color-transparent) 4px, @@ -1383,8 +1387,7 @@ svg.icon { left: 2px; right: 2px; z-index: 3; - background: - repeating-linear-gradient( + background: repeating-linear-gradient( 45deg, var(--invalid-reason-color-opaque) 0, var(--invalid-reason-color-opaque) 5px, @@ -1566,8 +1569,7 @@ svg.icon { right: 1px; z-index: 10; pointer-events: all; - background-image: - repeating-linear-gradient( + background-image: repeating-linear-gradient( 45deg, var(--invalid-reason-color-transparent) 0%, var(--invalid-reason-color-transparent) 5px, diff --git a/packages/webui/src/client/ui/RundownView.tsx b/packages/webui/src/client/ui/RundownView.tsx index 9573aec79f..3ed1ecc8f7 100644 --- a/packages/webui/src/client/ui/RundownView.tsx +++ b/packages/webui/src/client/ui/RundownView.tsx @@ -378,6 +378,7 @@ const RundownViewContent = translateWithTracker { @@ -904,6 +906,12 @@ const RundownViewContent = translateWithTracker { + this.setState({ + isNotificationsCenterOpen: undefined, + }) + } + private onToggleSupportPanel = () => { this.setState({ isSupportPanelOpen: !this.state.isSupportPanelOpen, @@ -1369,6 +1377,7 @@ const RundownViewContent = translateWithTracker {(selectionContext) => { + const isPropertiesPanelOpen = selectionContext.listSelectedElements().length > 0 return (
- {this.state.isNotificationsCenterOpen && ( + {!isPropertiesPanelOpen && this.state.isNotificationsCenterOpen && ( )} - {!this.state.isNotificationsCenterOpen && selectionContext.listSelectedElements().length > 0 && ( + {isPropertiesPanelOpen && (
@@ -1518,7 +1527,10 @@ const RundownViewContent = translateWithTracker selectionContext.clearAndSetSelection(selection)} + onEditProps={(selection) => { + this.setState({ isNotificationsCenterOpen: undefined }) + selectionContext.clearAndSetSelection(selection) + }} studioMode={this.props.userPermissions.studio} enablePlayFromAnywhere={!!studio.settings.enablePlayFromAnywhere} enableQuickLoop={!!studio.settings.enableQuickLoop} diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentContextMenu.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentContextMenu.tsx index 0118421bd1..b3966b5946 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentContextMenu.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentContextMenu.tsx @@ -69,6 +69,25 @@ export const SegmentContextMenu = withTranslation()( part?.instance._id !== this.props.playlist.nextPartInfo?.partInstanceId && part?.instance._id !== this.props.playlist.previousPartInfo?.partInstanceId + const segmentHasEditableContent = !!( + segment?.userEditOperations?.length || + segment?.userEditProperties?.pieceTypeProperties || + segment?.userEditProperties?.globalProperties || + segment?.userEditProperties?.operations?.length + ) + const partHasEditableContent = !!( + part?.instance.part.userEditOperations?.length || + part?.instance.part.userEditProperties?.pieceTypeProperties || + part?.instance.part.userEditProperties?.globalProperties || + part?.instance.part.userEditProperties?.operations?.length + ) + const pieceHasEditableContent = !!( + piece?.instance.piece.userEditOperations?.length || + piece?.instance.piece.userEditProperties?.pieceTypeProperties || + piece?.instance.piece.userEditProperties?.globalProperties || + piece?.instance.piece.userEditProperties?.operations?.length + ) + const canSetAsNext = !!this.props.playlist?.activationId return segment?.orphaned !== SegmentOrphanedReason.ADLIB_TESTING ? ( @@ -108,7 +127,7 @@ export const SegmentContextMenu = withTranslation()( /> )}
- {this.props.enableUserEdits && ( + {this.props.enableUserEdits && segmentHasEditableContent && ( <>
- {this.props.enableUserEdits && ( - <> -
- this.props.onEditProps({ type: 'segment', elementId: part.instance.segmentId })} - > - {t('Edit Segment Properties')} - - this.props.onEditProps({ type: 'part', elementId: part.instance.part._id })} - > - {t('Edit Part Properties')} - - {piece && piece.instance.piece.userEditProperties && ( - this.props.onEditProps({ type: 'piece', elementId: piece.instance.piece._id })} - > - {t('Edit Piece Properties')} - - )} - - )} + {this.props.enableUserEdits && + (segmentHasEditableContent || partHasEditableContent || pieceHasEditableContent) && ( + <> +
+ {segmentHasEditableContent && ( + + this.props.onEditProps({ type: 'segment', elementId: part.instance.segmentId }) + } + > + {t('Edit Segment Properties')} + + )} + {partHasEditableContent && ( + this.props.onEditProps({ type: 'part', elementId: part.instance.part._id })} + > + {t('Edit Part Properties')} + + )} + {pieceHasEditableContent && ( + this.props.onEditProps({ type: 'piece', elementId: piece.instance.piece._id })} + > + {t('Edit Piece Properties')} + + )} + + )} )} diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index 957d15faa3..f5956db81e 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -1057,8 +1057,18 @@ export class SegmentTimelineClass extends React.Component { if (this.props.studio.settings.enableUserEdits) { - if (!selectElementContext.isSelected(this.props.segment._id)) { - selectElementContext.clearAndSetSelection({ type: 'segment', elementId: this.props.segment._id }) + const segment = this.props.segment + + const hasEditableContent = !!( + segment.userEditOperations?.length || + segment.userEditProperties?.pieceTypeProperties || + segment.userEditProperties?.globalProperties || + segment.userEditProperties?.operations?.length + ) + if (!hasEditableContent) return + + if (!selectElementContext.isSelected(segment._id)) { + selectElementContext.clearAndSetSelection({ type: 'segment', elementId: segment._id }) } else { selectElementContext.clearSelections() } diff --git a/packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx b/packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx index 5c6f36f459..56cc78ce37 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx @@ -12,7 +12,10 @@ import { LocalLayerItemRenderer } from './Renderers/LocalLayerItemRenderer.js' import { DEBUG_MODE } from './SegmentTimelineDebugMode.js' import { getElementDocumentOffset, OffsetPosition } from '../../utils/positions.js' import { unprotectString } from '@sofie-automation/shared-lib/dist/lib/protectedString' -import { RundownViewEvents, HighlightEvent } from '@sofie-automation/meteor-lib/dist/triggers/RundownViewEventBus' +import RundownViewEventBus, { + RundownViewEvents, + HighlightEvent, +} from '@sofie-automation/meteor-lib/dist/triggers/RundownViewEventBus' import { pieceUiClassNames } from '../../lib/ui/pieceUiClassNames.js' import { TransitionSourceRenderer } from './Renderers/TransitionSourceRenderer.js' import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios' @@ -165,8 +168,19 @@ export const SourceLayerItem = (props: Readonly): JSX.Ele const itemDblClick = useCallback( (e: React.MouseEvent) => { if (studio?.settings.enableUserEdits && !studio?.settings.allowPieceDirectPlay) { - const pieceId = piece.instance.piece._id + const innerPiece = piece.instance.piece + + const hasEditableContent = !!( + innerPiece.userEditOperations?.length || + innerPiece.userEditProperties?.pieceTypeProperties || + innerPiece.userEditProperties?.globalProperties || + innerPiece.userEditProperties?.operations?.length + ) + if (!hasEditableContent) return + + const pieceId = innerPiece._id if (!selectElementContext.isSelected(pieceId)) { + RundownViewEventBus.emit(RundownViewEvents.CLOSE_NOTIFICATIONS) selectElementContext.clearAndSetSelection({ type: 'piece', elementId: pieceId }) } else { selectElementContext.clearSelections() diff --git a/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx b/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx index 835f0554bc..7497e57f50 100644 --- a/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx +++ b/packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx @@ -18,7 +18,7 @@ import { useTranslation } from 'react-i18next' import { useSelectedElements, useSelectedElementsContext } from '../RundownView/SelectedElementsContext.js' import { RundownUtils } from '../../lib/rundown.js' import * as CoreIcon from '@nrk/core-icons/jsx' -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { SchemaFormWithState } from '../../lib/forms/SchemaFormWithState.js' import { translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage' import { BlueprintAssetIcon } from '../../lib/Components/BlueprintAssetIcon.js' @@ -35,6 +35,21 @@ export function PropertiesPanel(): JSX.Element { const { piece, part, segment, rundownId } = useSelectedElements(selectedElement, () => setPendingChange(undefined)) + const [hadPiece, setHadPiece] = useState(false) + + useEffect(() => { + if (piece) setHadPiece(true) + }, [piece]) + + useEffect(() => { + const pieceChangedId = selectedElement?.type === 'piece' && hadPiece && piece === undefined + + if (pieceChangedId) { + setHadPiece(false) + clearSelections() + } + }, [selectedElement, piece, hadPiece, clearSelections]) + const handleCommitChanges = async (e: React.MouseEvent) => { if (!rundownId || !selectedElement || !pendingChange) return @@ -154,7 +169,7 @@ export function PropertiesPanel(): JSX.Element { title={t('Close Properties Panel')} onClick={clearSelections} > - +
@@ -182,20 +197,18 @@ export function PropertiesPanel(): JSX.Element {
@@ -357,7 +370,7 @@ function ActionList({ const { t } = useTranslation() return ( -
+
{actions.map((action) => (