Skip to content

Commit 45f9dac

Browse files
committed
chore: add new previews for dashboard buttons
1 parent 54349f7 commit 45f9dac

File tree

10 files changed

+125
-34
lines changed

10 files changed

+125
-34
lines changed

packages/webui/src/client/ui/PreviewPopUp/PreviewPopUp.tsx

+19-12
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ export const PreviewPopUp = React.forwardRef<
1515
hidden?: boolean
1616
preview?: React.ReactNode
1717
startCoordinate?: number
18+
trackMouse?: boolean
1819
}>
1920
>(function PreviewPopUp(
20-
{ anchor, padding, placement, hidden, size, children, startCoordinate },
21+
{ anchor, padding, placement, hidden, size, children, startCoordinate, trackMouse },
2122
ref
2223
): React.JSX.Element {
2324
const [popperEl, setPopperEl] = useState<HTMLDivElement | null>(null)
@@ -62,24 +63,30 @@ export const PreviewPopUp = React.forwardRef<
6263
anchor?.getBoundingClientRect().y ?? 0
6364
),
6465
})
65-
const { styles, attributes, update } = usePopper(virtualElement.current, popperEl, popperOptions)
66+
const { styles, attributes, update } = usePopper(
67+
trackMouse ? virtualElement.current : anchor,
68+
popperEl,
69+
popperOptions
70+
)
6671

6772
const updateRef = useRef(update)
6873

6974
useEffect(() => {
7075
updateRef.current = update
7176

72-
const listener = ({ clientX: x }: MouseEvent) => {
73-
virtualElement.current.getBoundingClientRect = generateGetBoundingClientRect(
74-
x,
75-
anchor?.getBoundingClientRect().y ?? 0
76-
)
77-
if (update) update()
78-
}
79-
document.addEventListener('mousemove', listener)
77+
if (trackMouse) {
78+
const listener = ({ clientX: x }: MouseEvent) => {
79+
virtualElement.current.getBoundingClientRect = generateGetBoundingClientRect(
80+
x,
81+
anchor?.getBoundingClientRect().y ?? 0
82+
)
83+
if (update) update()
84+
}
85+
document.addEventListener('mousemove', listener)
8086

81-
return () => {
82-
document.removeEventListener('mousemove', listener)
87+
return () => {
88+
document.removeEventListener('mousemove', listener)
89+
}
8390
}
8491
}, [update, anchor])
8592

packages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx

+14-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { PieceContentStatusObj } from '@sofie-automation/meteor-lib/dist/api/pie
1717
import { ITranslatableMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
1818
import { PieceUi } from '../SegmentContainer/withResolvedSegment'
1919
import _ from 'underscore'
20+
import { IAdLibListItem } from '../Shelf/AdLibListItem'
21+
import { PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
2022

2123
type VirtualElement = {
2224
getBoundingClientRect: () => DOMRect
@@ -65,11 +67,11 @@ export function convertPreviewToContents(
6567

6668
export function convertSourceLayerItemToPreview(
6769
sourceLayerType: SourceLayerType,
68-
piece: PieceUi,
70+
item: ReadonlyObjectDeep<PieceInstancePiece> | IAdLibListItem,
6971
contentStatus?: ReadonlyObjectDeep<PieceContentStatusObj>
7072
): PreviewContent[] {
7173
if (sourceLayerType === SourceLayerType.VT || sourceLayerType === SourceLayerType.LIVE_SPEAK) {
72-
const content = piece.instance.piece.content as VTContent
74+
const content = item.content as VTContent
7375

7476
return _.compact([
7577
{
@@ -94,7 +96,7 @@ export function convertSourceLayerItemToPreview(
9496
return [
9597
{
9698
type: 'title',
97-
content: piece.instance.piece.name,
99+
content: item.name,
98100
},
99101
// {
100102
// type: 'data',
@@ -105,12 +107,12 @@ export function convertSourceLayerItemToPreview(
105107
href: 'http://localhost:3005/dev/templatePreview.html',
106108
postMessage: {
107109
event: 'sofie-update',
108-
payload: piece.instance.piece.name,
110+
payload: item.name,
109111
},
110112
},
111113
]
112114
} else if (sourceLayerType === SourceLayerType.SCRIPT) {
113-
const content = piece.instance.piece.content as ScriptContent
115+
const content = item.content as ScriptContent
114116
return [
115117
{
116118
type: 'script',
@@ -121,7 +123,7 @@ export function convertSourceLayerItemToPreview(
121123
},
122124
]
123125
} else if (sourceLayerType === SourceLayerType.SPLITS) {
124-
const content = piece.instance.piece.content as SplitsContent
126+
const content = item.content as SplitsContent
125127
return [{ type: 'boxLayout', boxSourceConfiguration: content.boxSourceConfiguration }]
126128
}
127129

@@ -207,9 +209,11 @@ interface PreviewRequestOptions {
207209
time?: number
208210
/** */
209211
startCoordinate?: number
212+
/** */
213+
trackMouse?: boolean
210214
}
211215

212-
interface IPreviewPopUpContext {
216+
export interface IPreviewPopUpContext {
213217
/**
214218
* Request a new preview session
215219
* @param anchor The HTML element the preview
@@ -235,6 +239,7 @@ interface PreviewSession {
235239
placement: Placement
236240
size: 'small' | 'large'
237241
startCoordinate?: number
242+
trackMouse?: boolean
238243
}
239244

240245
export function PreviewPopUpContextProvider({ children }: React.PropsWithChildren<{}>): React.ReactNode {
@@ -258,6 +263,7 @@ export function PreviewPopUpContextProvider({ children }: React.PropsWithChildre
258263
placement: opts?.placement ?? 'top',
259264
size: opts?.size ?? 'small',
260265
startCoordinate: opts?.startCoordinate,
266+
trackMouse: opts?.trackMouse,
261267
})
262268
setPreviewContent(content)
263269

@@ -292,6 +298,7 @@ export function PreviewPopUpContextProvider({ children }: React.PropsWithChildre
292298
size={previewSession.size}
293299
placement={previewSession.placement}
294300
startCoordinate={previewSession.startCoordinate}
301+
trackMouse={previewSession.trackMouse}
295302
>
296303
{previewContent && previewContent.map((content) => <PreviewPopUpContent time={t} content={content} />)}
297304
</PreviewPopUp>

packages/webui/src/client/ui/SegmentList/LinePartMainPiece/LinePartMainPiece.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export function LinePartMainPiece({
139139
const previewContents = piece.instance.piece.content.popUpPreview
140140
? convertPreviewToContents(piece.instance.piece.content.popUpPreview, contentStatus)
141141
: piece.sourceLayer
142-
? convertSourceLayerItemToPreview(piece.sourceLayer?.type, piece, contentStatus)
142+
? convertSourceLayerItemToPreview(piece.sourceLayer?.type, piece.instance.piece, contentStatus)
143143
: []
144144

145145
const onPointerEnter = (e: React.PointerEvent<HTMLDivElement>) => {
@@ -152,6 +152,7 @@ export function LinePartMainPiece({
152152
previewSession.current = previewContext.requestPreview(e.target as any, previewContents, {
153153
time: mousePosition * (piece.instance.piece.content.sourceDuration || 0),
154154
startCoordinate: e.screenX,
155+
trackMouse: true,
155156
})
156157

157158
const newOffset = pieceEl.current && getElementDocumentOffset(pieceEl.current)

packages/webui/src/client/ui/SegmentList/LinePartPieceIndicator/LinePartScriptPiece.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function LinePartScriptPiece({ pieces }: IProps): JSX.Element {
5555
(thisPieces[0].instance.piece.content.popUpPreview
5656
? convertPreviewToContents(thisPieces[0].instance.piece.content.popUpPreview, contentStatus)
5757
: thisPieces[0].sourceLayer
58-
? convertSourceLayerItemToPreview(thisPieces[0].sourceLayer?.type, thisPieces[0], contentStatus)
58+
? convertSourceLayerItemToPreview(thisPieces[0].sourceLayer?.type, thisPieces[0].instance.piece, contentStatus)
5959
: [])
6060

6161
function onMouseEnter(e: React.PointerEvent<HTMLDivElement>) {

packages/webui/src/client/ui/SegmentList/LinePartSecondaryPiece/LinePartSecondaryPiece.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const LinePartSecondaryPiece: React.FC<IProps> = React.memo(function Line
5858
const previewContents = piece.instance.piece.content.popUpPreview
5959
? convertPreviewToContents(piece.instance.piece.content.popUpPreview, contentStatus)
6060
: piece.sourceLayer
61-
? convertSourceLayerItemToPreview(piece.sourceLayer?.type, piece, contentStatus)
61+
? convertSourceLayerItemToPreview(piece.sourceLayer?.type, piece.instance.piece, contentStatus)
6262
: []
6363

6464
const onPointerEnter = (e: React.PointerEvent<HTMLDivElement>) => {

packages/webui/src/client/ui/SegmentStoryboard/StoryboardPartSecondaryPieces/StoryboardSecondaryPiece.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function StoryboardSecondaryPiece(props: IProps): JSX.Element {
9494
const contentStatus = useContentStatusForPieceInstance(piece.instance)
9595
const previewContents = piece.instance.piece.content.popUpPreview
9696
? convertPreviewToContents(piece.instance.piece.content.popUpPreview, contentStatus)
97-
: convertSourceLayerItemToPreview(props.layer.type, piece, contentStatus)
97+
: convertSourceLayerItemToPreview(props.layer.type, piece.instance.piece, contentStatus)
9898

9999
const onPointerEnter = (e: React.PointerEvent<HTMLDivElement>) => {
100100
if (e.pointerType !== 'mouse') return

packages/webui/src/client/ui/SegmentStoryboard/StoryboardPartThumbnail/StoryboardPartThumbnailInner.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function StoryboardPartThumbnailInner({
5454
const previewContents = piece.instance.piece.content.popUpPreview
5555
? convertPreviewToContents(piece.instance.piece.content.popUpPreview, contentStatus)
5656
: layer
57-
? convertSourceLayerItemToPreview(layer.type, piece, contentStatus)
57+
? convertSourceLayerItemToPreview(layer.type, piece.instance.piece, contentStatus)
5858
: []
5959

6060
const onPointerEnter = (e: React.PointerEvent<HTMLDivElement>) => {

packages/webui/src/client/ui/SegmentTimeline/SourceLayerItem.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,13 @@ export const SourceLayerItem = (props: Readonly<ISourceLayerItemProps>): JSX.Ele
238238
convertPreviewToContents(piece.instance.piece.content.popUpPreview, contentStatus)
239239
)
240240
} else {
241-
const previewContents = convertSourceLayerItemToPreview(layer.type, piece, contentStatus)
241+
const previewContents = convertSourceLayerItemToPreview(layer.type, piece.instance.piece, contentStatus)
242242

243243
if (previewContents.length) {
244244
previewSession.current = previewContext.requestPreview(e.target as any, previewContents, {
245245
time: cursorTimePosition,
246246
startCoordinate: e.screenX,
247+
trackMouse: true,
247248
})
248249
} else {
249250
setShowMiniInspector(v)

packages/webui/src/client/ui/Shelf/DashboardPieceButton.tsx

+51-6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ import { AdLibPieceUi } from '../../lib/shelf'
2626
import { protectString } from '../../lib/tempLib'
2727
import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
2828
import { PieceStatusCode } from '@sofie-automation/corelib/dist/dataModel/Piece'
29+
import {
30+
convertPreviewToContents,
31+
convertSourceLayerItemToPreview,
32+
IPreviewPopUpContext,
33+
IPreviewPopUpSession,
34+
PreviewPopUpContext,
35+
} from '../PreviewPopUp/PreviewPopUpContext'
2936

3037
export interface IDashboardButtonProps {
3138
piece: IAdLibListItem
@@ -51,6 +58,9 @@ export interface IDashboardButtonProps {
5158
canOverflowHorizontally?: boolean
5259
lineBreak?: string
5360
}
61+
interface WithPreviewContext {
62+
previewContext: IPreviewPopUpContext
63+
}
5464
export const DEFAULT_BUTTON_WIDTH = 6.40625
5565
export const DEFAULT_BUTTON_HEIGHT = 5.625
5666
export const HOVER_TIMEOUT = 5000
@@ -63,7 +73,7 @@ interface IState {
6373
}
6474

6575
export class DashboardPieceButtonBase<T = {}> extends React.Component<
66-
React.PropsWithChildren<IDashboardButtonProps> & T & WithMediaObjectStatusProps,
76+
React.PropsWithChildren<IDashboardButtonProps> & T & WithMediaObjectStatusProps & WithPreviewContext,
6777
IState
6878
> {
6979
private element: HTMLDivElement | null = null
@@ -78,8 +88,9 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
7888
private pointerId: number | null = null
7989
private hoverTimeout: number | null = null
8090
protected inBucket = false
91+
private previewSession: IPreviewPopUpSession | null = null
8192

82-
constructor(props: IDashboardButtonProps & T & WithMediaObjectStatusProps) {
93+
constructor(props: IDashboardButtonProps & T & WithMediaObjectStatusProps & WithPreviewContext) {
8394
super(props)
8495

8596
this.state = {
@@ -201,7 +212,7 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
201212
this.element = el
202213
}
203214

204-
private handleOnMouseEnter = (_e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => {
215+
private handleOnMouseEnter = (e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => {
205216
if (this.element) {
206217
const { top, left, width, height } = this.element.getBoundingClientRect()
207218
this.positionAndSize = {
@@ -211,12 +222,27 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
211222
height,
212223
}
213224
}
214-
this.setState({ isHovered: true })
225+
226+
const previewContents = this.props.piece.content.popUpPreview
227+
? convertPreviewToContents(this.props.piece.content.popUpPreview, this.props.contentStatus)
228+
: this.props.layer
229+
? convertSourceLayerItemToPreview(this.props.layer.type, this.props.piece, this.props.contentStatus)
230+
: []
231+
if (!previewContents.length) return
232+
233+
this.previewSession = this.props.previewContext.requestPreview(e.target as any, previewContents, {
234+
time: this.state.timePosition,
235+
})
215236
}
216237

217238
private handleOnMouseLeave = (_e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => {
218239
this.setState({ isHovered: false })
219240
this.positionAndSize = null
241+
242+
if (this.previewSession) {
243+
this.previewSession.close()
244+
this.previewSession = null
245+
}
220246
}
221247

222248
private handleOnPointerEnter = (e: React.PointerEvent<HTMLDivElement>) => {
@@ -230,8 +256,18 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
230256
}
231257
}
232258
if (e.pointerType === 'mouse') {
233-
this.setState({ isHovered: true })
234259
this.startHoverTimeout()
260+
261+
const previewContents = this.props.piece.content.popUpPreview
262+
? convertPreviewToContents(this.props.piece.content.popUpPreview, this.props.contentStatus)
263+
: this.props.layer
264+
? convertSourceLayerItemToPreview(this.props.layer.type, this.props.piece, this.props.contentStatus)
265+
: []
266+
if (!previewContents.length) return
267+
268+
this.previewSession = this.props.previewContext.requestPreview(e.target as any, previewContents, {
269+
time: this.state.timePosition,
270+
})
235271
}
236272
}
237273

@@ -242,6 +278,11 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
242278
this.hoverTimeout = null
243279
}
244280
this.positionAndSize = null
281+
282+
if (this.previewSession) {
283+
this.previewSession.close()
284+
this.previewSession = null
285+
}
245286
}
246287

247288
private handleOnMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
@@ -267,6 +308,9 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
267308
Meteor.clearTimeout(this.hoverTimeout)
268309
this.startHoverTimeout()
269310
}
311+
if (this.previewSession) {
312+
this.previewSession.setPointerTime(timePercentage * sourceDuration)
313+
}
270314
}
271315

272316
private startHoverTimeout = () => {
@@ -491,6 +535,7 @@ export class DashboardPieceButtonBase<T = {}> extends React.Component<
491535

492536
export function DashboardPieceButton(props: React.PropsWithChildren<IDashboardButtonProps>): JSX.Element {
493537
const contentStatus = useContentStatusForAdlibPiece(props.piece)
538+
const previewContext = React.useContext(PreviewPopUpContext)
494539

495-
return <DashboardPieceButtonBase {...props} contentStatus={contentStatus} />
540+
return <DashboardPieceButtonBase {...props} contentStatus={contentStatus} previewContext={previewContext} />
496541
}

0 commit comments

Comments
 (0)