Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export async function checkPieceContentStatusAndDependencies(
blacks: [],
scenes: [],

thumbnailUrl: undefined,
thumbnailUrl: '/dev/fakeThumbnail.png',
previewUrl: '/dev/fakePreview.mp4',

packageName: null,
Expand Down
55 changes: 54 additions & 1 deletion packages/blueprints-integration/src/previews.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { SplitsContentBoxContent, SplitsContentBoxProperties } from './content.js'
import { SourceLayerType, SplitsContentBoxContent, SplitsContentBoxProperties } from './content.js'
import { NoteSeverity } from './lib.js'
import { ITranslatableMessage } from './translations.js'

export interface PopupPreview<P extends Previews = Previews> {
name?: string
preview?: P
warnings?: InvalidPreview[]
/**
* Add custom content preview content
*/
additionalPreviewContent?: Array<PreviewContent>
}
export type Previews = TablePreview | ScriptPreview | HTMLPreview | SplitPreview | VTPreview | BlueprintImagePreview

Expand All @@ -19,6 +23,55 @@ export enum PreviewType {
BlueprintImage = 'blueprintImage',
}

// The PreviewContent types are a partly replica of the types in PreviewPopUpContext.tsx
export type PreviewContent =
| {
type: 'iframe'
href: string
postMessage?: any
dimensions?: { width: number; height: number }
}
| {
type: 'image'
src: string
}
| {
type: 'video'
src: string
}
| {
type: 'script'
script?: string
firstWords?: string
lastWords?: string
comment?: string
lastModified?: number
}
| {
type: 'title'
content: string
}
| {
type: 'inOutWords'
in?: string
out: string
}
| {
type: 'layerInfo'
Copy link
Contributor

@jstarpl jstarpl Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a layerInfo or more of an "auxiliary Piece info" on a layer of given type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think that calling it layerInfo makes sense, based on what it's used for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments has been added to the code, to explain it's purpose

layerType: SourceLayerType
text: Array<string>
inTime?: number | string
outTime?: number | string
duration?: number | string
Comment on lines +126 to +128
Copy link
Contributor

@jstarpl jstarpl Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do these properties mean? What's the difference between outTime vs inTime + duration? I'm also somewhat unsure about making this interface this loose & bound to a particular implementation of the hoverscrub preview component/any other component using this infromation - say Presenters' Screen. Any change there would effectively require a change to blueprints integration, which we expect to be at least reasonably stable.

I'm also not a big fan of introducing terms inTime and duration - I think they somewhat overlap with expectedStart and expectedDuration? Are they the same? If that's the case, I think it would make sense to use the same names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are optional values for the "in", "out", "duration" labels, and used for the LayerInfo content, not necessary an expectedStart or expectedDuration calculation.
So either we should add a comment in here that explains what it is, or add it to some documentation, what do you prefer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments for it's use has been added to code.

}
| {
type: 'separationLine'
}
| {
type: 'data'
content: { key: string; value: string }[]
}
Comment on lines 134 to 141
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface specifically is very vague for a public-use interface. I think we need to make sure that it's more clear what one can expect/put into here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify what additional specificity you'd like for the data type?
We should definitely dig into what this does and add some documentation, currently I don't know how this feature works, or where it's being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments for it's purpose has been added to code.


Comment on lines 78 to 142
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think with this now becoming a public interface this needs to be described more in terms what the Blueprint Developer should expect to get.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, as this is an add on to the existing popupPreview implementation, are there any place where this is already documented, so I can add it there, or du you prefer it as comments in the interface definition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As no previous documentation was found, JSDoc and comments has been added to code.

interface PreviewBase {
type: PreviewType
}
Expand Down
Binary file added packages/webui/public/dev/fakeThumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
}

.dashboard-panel__panel__button {
margin-top: 10px;
height: 110px;
max-width: 170px !important;
> .dashboard-panel__panel__button__content {
display: grid;
grid-template-columns: 1fr min-content;
Expand All @@ -31,7 +34,7 @@

> .dashboard-panel__panel__button__thumbnail {
position: relative;
height: auto;
height: 85px;
z-index: 1;
overflow: hidden;
grid-column: auto / span 2;
Expand Down
157 changes: 145 additions & 12 deletions packages/webui/src/client/ui/PreviewPopUp/PreviewPopUp.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@
.preview-popUp {
border: 1px solid var(--sofie-segment-layer-hover-popup-border);
background: var(--sofie-segment-layer-hover-popup-background);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.5);
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.8);
border-radius: 5px;
overflow: hidden;
pointer-events: none;

box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.6);

z-index: 9999;

&--large {
width: 482px;
padding-bottom: 10px;
--preview-max-dimension: 480;
}

Expand All @@ -25,18 +24,65 @@
&--hidden {
visibility: none;
}

font-family: Roboto Flex;

font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 110%;
/* identical to box height, or 15px */
letter-spacing: 0.02em;
font-feature-settings:
'tnum',
'liga' off;
color: #ffffff;
font-variation-settings:
'GRAD' 0,
'opsz' 15,
'slnt' 0,
'wdth' 30,
'XOPQ' 96,
'XTRA' 468,
'YOPQ' 79,
'YTAS' 750,
'YTDE' -203,
'YTFI' 738,
'YTLC' 548,
'YTUC' 712;
}

.preview-popUp__preview {
width: 100%;
font-family: 'Roboto Condensed';
font-size: 0.9375rem; // 15px;

.preview-popUp__script,
.preview-popUp__script-comment,
.preview-popUp__script-last-modified {
padding: 0.4em 0.4em 0.4em 0.6em;
font-style: italic;
padding: 5px;
padding-left: 2%;
padding-right: 2%;
font-weight: 300;
font-size: 16px;
line-height: 120%;
letter-spacing: 0.03em;
font-feature-settings:
'tnum',
'liga' off;

color: #ffffff;
font-variation-settings:
'GRAD' 0,
'opsz' 16,
'slnt' -10,
'wdth' 75,
'XOPQ' 96,
'XTRA' 468,
'YOPQ' 79,
'YTAS' 750,
'YTDE' -203,
'YTFI' 738,
'YTLC' 548,
'YTUC' 712;
}

.preview-popUp__script-comment,
Expand All @@ -54,6 +100,72 @@
letter-spacing: 0.02rem;

padding: 5px;
padding-left: 2%;
}

.preview-popUp__element-with-time-info {
width: 100%;
display: flex;

margin-bottom: 7px;

.preview-popUp__element-with-time-info__layer-color {
height: 13px;
aspect-ratio: 1;
margin-left: 2%;
margin-top: 7px;
flex-shrink: 0;
@include item-type-colors();
}

.preview-popUp__element-with-time-info__text {
margin: 5px;
width: calc(100% - 35px);
flex-grow: 1;
}

.preview-popUp__element-with-time-info__timing {
margin-left: 5px;
overflow: none;
white-space: nowrap;
text-overflow: ellipsis;
font-feature-settings: 'liga' off;

font-weight: 500;
line-height: 100%; /* 15px */

.label {
font-weight: 100;
line-height: 100%;
/* identical to box height, or 15px */
letter-spacing: 0.02em;
font-feature-settings:
'tnum',
'liga' off;
color: #b2b2b2;
font-variation-settings:
'GRAD' 0,
'opsz' 30,
'slnt' 0,
'wdth' 25,
'XOPQ' 96,
'XTRA' 468,
'YOPQ' 79,
'YTAS' 750,
'YTDE' -203,
'YTFI' 738,
'YTLC' 548,
'YTUC' 712;
}
}
}

.preview-popup__separation-line {
width: 96%;
margin-left: 2%;
background-color: #5b5b5b;
margin-top: 0px;
margin-bottom: 0px;
}

.preview-popUp__warning {
Expand Down Expand Up @@ -174,21 +286,42 @@
}

.preview-popUp__in-out-words {
letter-spacing: 0em;
font-weight: 300;
font-size: 16px;
line-height: 100%;
letter-spacing: 0.02em;
font-feature-settings:
'tnum',
'liga' off;
color: #ffffff;
font-variation-settings:
'GRAD' 0,
'opsz' 16,
'slnt' -10,
'wdth' 75,
'XOPQ' 96,
'XTRA' 468,
'YOPQ' 79,
'YTAS' 750,
'YTDE' -203,
'YTFI' 738,
'YTLC' 548,
'YTUC' 712;

width: 100%;
overflow: hidden;
text-overflow: clip;
white-space: nowrap;

margin-top: -25px; //Pull up the in/out words a bit
padding: 7px;
padding: 5px;
padding-left: 2%;
padding-right: 2%;

.separation-line {
width: 100%;
height: 1px;
background-color: #5b5b5b;
margin-bottom: 5px;
margin-bottom: 7px;
}

.in-words,
Expand All @@ -201,7 +334,7 @@
}

.out-words {
direction: rtl;
text-align: right;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react'
import { PreviewContent } from './PreviewPopUpContext.js'
import { WarningIconSmall } from '../../lib/ui/icons/notifications.js'
import { translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
import { TFunction, useTranslation } from 'react-i18next'
Expand All @@ -11,9 +10,11 @@ import { RundownUtils } from '../../lib/rundown.js'
import { PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep'
import { PieceLifespan } from '@sofie-automation/blueprints-integration'
import { LayerInfoPreview } from './Previews/LayerInfoPreview.js'
import { PreviewContentUI } from './PreviewPopUpContext.js'

interface PreviewPopUpContentProps {
content: PreviewContent
content: PreviewContentUI
time: number | null
}

Expand All @@ -38,7 +39,6 @@ export function PreviewPopUpContent({ content, time }: PreviewPopUpContentProps)
case 'inOutWords':
return (
<div className="preview-popUp__in-out-words">
<hr className="separation-line" />
<div className="in-words">{content.in}</div>
<div className="out-words">{content.out}</div>
</div>
Expand All @@ -59,6 +59,10 @@ export function PreviewPopUpContent({ content, time }: PreviewPopUpContentProps)
</table>
</div>
)
case 'layerInfo':
return <LayerInfoPreview {...content} />
case 'separationLine':
return <hr className="preview-popup__separation-line" />
case 'boxLayout':
return <BoxLayoutPreview content={content} />
case 'warning':
Expand Down Expand Up @@ -108,17 +112,17 @@ function getDurationText(
function getLifeSpanText(t: TFunction, lifespan: PieceLifespan): string {
switch (lifespan) {
case PieceLifespan.WithinPart:
return t('Until next take')
return t('Until Next Take')
case PieceLifespan.OutOnSegmentChange:
return t('Until next segment')
return t('Until Next Segment')
case PieceLifespan.OutOnSegmentEnd:
return t('Until end of segment')
return t('Until End of Segment')
case PieceLifespan.OutOnRundownChange:
return t('Until next rundown')
return t('Until Next Rundown')
case PieceLifespan.OutOnRundownEnd:
return t('Until end of rundown')
return t('Until End of Rundown')
case PieceLifespan.OutOnShowStyleEnd:
return t('Until end of showstyle')
return t('Until End of Showstyle')
default:
return ''
}
Expand Down
Loading
Loading