Skip to content

Commit 9846cf4

Browse files
committed
wip: make separate non svg ClockView piece icons
1 parent 7750561 commit 9846cf4

16 files changed

+647
-2
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
svg.piece_icon {
3+
@include item-type-colors-svg();
4+
5+
rect {
6+
@include item-type-colors-svg();
7+
}
8+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { useSubscription, useTracker } from '../../../lib/ReactMeteorData/ReactMeteorData'
2+
import { SourceLayerType, VTContent } from '@sofie-automation/blueprints-integration'
3+
import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub'
4+
import { findPieceInstanceToShow } from './utils'
5+
import { Timediff } from '../../ClockView/Timediff'
6+
import { getCurrentTime } from '../../../lib/systemTime'
7+
import {
8+
PartInstanceId,
9+
RundownId,
10+
RundownPlaylistActivationId,
11+
ShowStyleBaseId,
12+
} from '@sofie-automation/corelib/dist/dataModel/Ids'
13+
import { CorelibPubSub } from '@sofie-automation/corelib/dist/pubsub'
14+
15+
export interface IPropsHeader {
16+
partInstanceId: PartInstanceId
17+
rundownIds: RundownId[]
18+
showStyleBaseId: ShowStyleBaseId
19+
playlistActivationId: RundownPlaylistActivationId | undefined
20+
partExpectedDuration: number | undefined
21+
partStartedPlayback: number | undefined
22+
partAutoNext: boolean
23+
}
24+
25+
const supportedLayers = new Set([
26+
SourceLayerType.GRAPHICS,
27+
SourceLayerType.STUDIO_SCREEN,
28+
SourceLayerType.LIVE_SPEAK,
29+
SourceLayerType.REMOTE,
30+
SourceLayerType.REMOTE_SPEAK,
31+
SourceLayerType.SPLITS,
32+
SourceLayerType.VT,
33+
SourceLayerType.CAMERA,
34+
])
35+
36+
export function PieceCountdownContainer(props: Readonly<IPropsHeader>): JSX.Element | null {
37+
const { pieceInstance, sourceLayer } = useTracker(
38+
() => findPieceInstanceToShow(props, supportedLayers),
39+
[props.partInstanceId, props.showStyleBaseId],
40+
{
41+
sourceLayer: undefined,
42+
pieceInstance: undefined,
43+
}
44+
)
45+
46+
useSubscription(CorelibPubSub.pieceInstancesSimple, props.rundownIds, props.playlistActivationId ?? null)
47+
48+
useSubscription(MeteorPubSub.uiShowStyleBase, props.showStyleBaseId)
49+
50+
const piece = pieceInstance ? pieceInstance.piece : undefined
51+
const sourceDuration = (piece?.content as VTContent)?.sourceDuration
52+
const seek = (piece?.content as VTContent)?.seek || 0
53+
const postrollDuration = (piece?.content as VTContent)?.postrollDuration || 0
54+
const pieceEnable = typeof piece?.enable.start !== 'number' ? 0 : piece?.enable.start
55+
if (
56+
props.partStartedPlayback &&
57+
sourceLayer &&
58+
piece &&
59+
piece.content &&
60+
sourceDuration &&
61+
((props.partAutoNext && pieceEnable + (sourceDuration - seek) < (props.partExpectedDuration || 0)) ||
62+
(!props.partAutoNext &&
63+
Math.abs(pieceEnable + (sourceDuration - seek) - (props.partExpectedDuration || 0)) > 500))
64+
) {
65+
const freezeCountdown =
66+
props.partStartedPlayback + pieceEnable + (sourceDuration - seek) + postrollDuration - getCurrentTime()
67+
68+
if (freezeCountdown > 0) {
69+
return (
70+
<>
71+
<Timediff time={freezeCountdown} />
72+
<img className="freeze-icon" src="/icons/freeze-presenter-screen.svg" />
73+
</>
74+
)
75+
}
76+
}
77+
return null
78+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { useSubscription, useTracker } from '../../../lib/ReactMeteorData/ReactMeteorData'
2+
import {
3+
SourceLayerType,
4+
ISourceLayer,
5+
CameraContent,
6+
RemoteContent,
7+
EvsContent,
8+
} from '@sofie-automation/blueprints-integration'
9+
import { CamInputIcon } from './ClockViewRenderers/CamInputIcon'
10+
import { VTInputIcon } from './ClockViewRenderers/VTInputIcon'
11+
import SplitInputIcon from './ClockViewRenderers/SplitInputIcon'
12+
import { RemoteInputIcon } from './ClockViewRenderers/RemoteInputIcon'
13+
import { LiveSpeakInputIcon } from './ClockViewRenderers/LiveSpeakInputIcon'
14+
import { RemoteSpeakInputIcon } from './ClockViewRenderers/RemoteSpeakInputIcon'
15+
import { GraphicsInputIcon } from './ClockViewRenderers/GraphicsInputIcon'
16+
import { UnknownInputIcon } from './ClockViewRenderers/UnknownInputIcon'
17+
import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub'
18+
import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
19+
import { findPieceInstanceToShow, findPieceInstanceToShowFromInstances } from './utils'
20+
import LocalInputIcon from './ClockViewRenderers/LocalInputIcon'
21+
import {
22+
PartInstanceId,
23+
RundownId,
24+
RundownPlaylistActivationId,
25+
ShowStyleBaseId,
26+
} from '@sofie-automation/corelib/dist/dataModel/Ids'
27+
import { ReadonlyDeep } from 'type-fest'
28+
import { CorelibPubSub } from '@sofie-automation/corelib/dist/pubsub'
29+
30+
export interface IPropsHeader {
31+
partInstanceId: PartInstanceId
32+
rundownIds: RundownId[]
33+
showStyleBaseId: ShowStyleBaseId
34+
playlistActivationId: RundownPlaylistActivationId | undefined
35+
}
36+
37+
export const PieceIcon = (props: {
38+
pieceInstance: ReadonlyDeep<PieceInstance> | undefined
39+
sourceLayer: ISourceLayer | undefined
40+
renderUnknown?: boolean
41+
}): JSX.Element | null => {
42+
const piece = props.pieceInstance ? props.pieceInstance.piece : undefined
43+
if (props.sourceLayer && piece) {
44+
switch (props.sourceLayer.type) {
45+
case SourceLayerType.GRAPHICS:
46+
return <GraphicsInputIcon abbreviation={props.sourceLayer.abbreviation} />
47+
case SourceLayerType.LIVE_SPEAK:
48+
return <LiveSpeakInputIcon abbreviation={props.sourceLayer.abbreviation} />
49+
case SourceLayerType.REMOTE: {
50+
const rmContent = piece ? (piece.content as RemoteContent | undefined) : undefined
51+
return (
52+
<RemoteInputIcon
53+
inputIndex={rmContent ? rmContent.studioLabelShort || rmContent.studioLabel : undefined}
54+
abbreviation={props.sourceLayer.abbreviation}
55+
/>
56+
)
57+
}
58+
case SourceLayerType.REMOTE_SPEAK: {
59+
return <RemoteSpeakInputIcon abbreviation={props.sourceLayer.abbreviation} />
60+
}
61+
case SourceLayerType.LOCAL: {
62+
const localContent = piece ? (piece.content as EvsContent | undefined) : undefined
63+
return (
64+
<LocalInputIcon
65+
inputIndex={localContent ? localContent.studioLabelShort || localContent.studioLabel : undefined}
66+
abbreviation={props.sourceLayer.abbreviation}
67+
/>
68+
)
69+
}
70+
case SourceLayerType.SPLITS:
71+
return <SplitInputIcon abbreviation={props.sourceLayer.abbreviation} piece={piece} />
72+
case SourceLayerType.VT:
73+
return <VTInputIcon abbreviation={props.sourceLayer.abbreviation} />
74+
case SourceLayerType.CAMERA: {
75+
const camContent = piece ? (piece.content as CameraContent | undefined) : undefined
76+
return (
77+
<CamInputIcon
78+
inputIndex={camContent ? camContent.studioLabelShort || camContent.studioLabel : undefined}
79+
abbreviation={props.sourceLayer.abbreviation}
80+
/>
81+
)
82+
}
83+
}
84+
}
85+
86+
if (props.renderUnknown) {
87+
return <UnknownInputIcon />
88+
}
89+
90+
return null
91+
}
92+
93+
export const pieceIconSupportedLayers = new Set([
94+
SourceLayerType.GRAPHICS,
95+
SourceLayerType.LIVE_SPEAK,
96+
SourceLayerType.REMOTE,
97+
SourceLayerType.REMOTE_SPEAK,
98+
SourceLayerType.SPLITS,
99+
SourceLayerType.VT,
100+
SourceLayerType.CAMERA,
101+
SourceLayerType.LOCAL,
102+
])
103+
104+
export function PieceIconContainerNoSub({
105+
pieceInstances,
106+
sourceLayers,
107+
renderUnknown,
108+
}: Readonly<{
109+
pieceInstances: ReadonlyDeep<PieceInstance[]>
110+
sourceLayers: {
111+
[key: string]: ISourceLayer
112+
}
113+
renderUnknown?: boolean
114+
}>): JSX.Element | null {
115+
const { pieceInstance, sourceLayer } = useTracker(
116+
() => findPieceInstanceToShowFromInstances(pieceInstances, sourceLayers, pieceIconSupportedLayers),
117+
[pieceInstances, sourceLayers],
118+
{
119+
sourceLayer: undefined,
120+
pieceInstance: undefined,
121+
}
122+
)
123+
124+
return <PieceIcon pieceInstance={pieceInstance} sourceLayer={sourceLayer} renderUnknown={renderUnknown} />
125+
}
126+
127+
export function PieceIconContainer(props: Readonly<IPropsHeader>): JSX.Element | null {
128+
const { pieceInstance, sourceLayer } = useTracker(
129+
() => findPieceInstanceToShow(props, pieceIconSupportedLayers),
130+
[props.partInstanceId, props.showStyleBaseId],
131+
{
132+
pieceInstance: undefined,
133+
sourceLayer: undefined,
134+
}
135+
)
136+
137+
useSubscription(CorelibPubSub.pieceInstancesSimple, props.rundownIds, props.playlistActivationId ?? null)
138+
139+
useSubscription(MeteorPubSub.uiShowStyleBase, props.showStyleBaseId)
140+
141+
return <PieceIcon pieceInstance={pieceInstance} sourceLayer={sourceLayer} />
142+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Variables
2+
$text-color: #ffffff;
3+
$font-size-base: 70px;
4+
$text-shadow: 0 2px 9px rgba(0, 0, 0, 0.5);
5+
$letter-spacing: 0.02em;
6+
7+
@mixin layer-type-backgrounds {
8+
@each $layer-type in $layer-types {
9+
&.#{$layer-type} {
10+
// Background:
11+
display: inline-block;
12+
width: 207px;
13+
height: 126px;
14+
line-height: 126px;
15+
16+
background-color: var(--segment-layer-background-#{$layer-type});
17+
18+
&.second {
19+
background-color: var(--segment-layer-background-#{$layer-type}--second);
20+
}
21+
}
22+
}
23+
}
24+
25+
// Base icon styles
26+
.piece-icon {
27+
// Text styles:
28+
fill: $text-color;
29+
font-family: Roboto Flex;
30+
filter: drop-shadow($text-shadow);
31+
font-size: $font-size-base;
32+
font-weight: 300;
33+
letter-spacing: $letter-spacing;
34+
35+
span {
36+
// Common styles
37+
@include layer-type-backgrounds; // Apply to all span elements that have layer type classes
38+
}
39+
40+
.live-speak {
41+
fill: url(#background-gradient);
42+
}
43+
44+
// Gradient styles for live-speak
45+
#background-gradient {
46+
.stop1 {
47+
stop-color: #954c4c;
48+
}
49+
.stop2 {
50+
stop-color: #4c954c;
51+
}
52+
}
53+
54+
// Split view specific styles
55+
.upper, .lower {
56+
57+
&.camera {
58+
@include item-type-colors-svg();
59+
}
60+
&.remote {
61+
@include item-type-colors-svg();
62+
}
63+
&.remote.second {
64+
@include item-type-colors-svg();
65+
}
66+
}
67+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { useSubscription, useTracker } from '../../../lib/ReactMeteorData/ReactMeteorData'
2+
import { EvsContent, SourceLayerType } from '@sofie-automation/blueprints-integration'
3+
4+
import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub'
5+
import { IPropsHeader } from './ClockViewPieceIcon'
6+
import { findPieceInstanceToShow } from './utils'
7+
import { PieceGeneric } from '@sofie-automation/corelib/dist/dataModel/Piece'
8+
import { RundownPlaylistActivationId } from '@sofie-automation/corelib/dist/dataModel/Ids'
9+
import { ReadonlyDeep } from 'type-fest'
10+
import { CorelibPubSub } from '@sofie-automation/corelib/dist/pubsub'
11+
import { AdjustLabelFit, AdjustLabelFitProps } from '../../util/AdjustLabelFit'
12+
13+
interface INamePropsHeader extends IPropsHeader {
14+
partName: string
15+
playlistActivationId: RundownPlaylistActivationId | undefined
16+
autowidth?: AdjustLabelFitProps
17+
}
18+
19+
const supportedLayers = new Set([
20+
SourceLayerType.GRAPHICS,
21+
SourceLayerType.LIVE_SPEAK,
22+
SourceLayerType.VT,
23+
SourceLayerType.LOCAL,
24+
])
25+
26+
function getLocalPieceLabel(piece: ReadonlyDeep<PieceGeneric>, autowidth?: AdjustLabelFitProps): JSX.Element | null {
27+
const { color } = piece.content as EvsContent
28+
return (
29+
<>
30+
{color && (
31+
<span style={{ color: color.startsWith('#') ? color : `#${color}` }} className="piece__label__colored-mark">
32+
·
33+
</span>
34+
)}
35+
<AdjustLabelFit {...autowidth} label={piece.name || ''} />
36+
</>
37+
)
38+
}
39+
40+
function getPieceLabel(
41+
piece: ReadonlyDeep<PieceGeneric>,
42+
type: SourceLayerType,
43+
autowidth?: AdjustLabelFitProps
44+
): JSX.Element | null {
45+
switch (type) {
46+
case SourceLayerType.LOCAL:
47+
return getLocalPieceLabel(piece, autowidth)
48+
default:
49+
return <AdjustLabelFit {...autowidth} label={piece.name || ''} />
50+
}
51+
}
52+
53+
export function PieceNameContainer(props: Readonly<INamePropsHeader>): JSX.Element | null {
54+
const { sourceLayer, pieceInstance } = useTracker(
55+
() => findPieceInstanceToShow(props, supportedLayers),
56+
[props.partInstanceId, props.showStyleBaseId],
57+
{
58+
sourceLayer: undefined,
59+
pieceInstance: undefined,
60+
}
61+
)
62+
63+
useSubscription(CorelibPubSub.pieceInstancesSimple, props.rundownIds, props.playlistActivationId ?? null)
64+
65+
useSubscription(MeteorPubSub.uiShowStyleBase, props.showStyleBaseId)
66+
67+
if (pieceInstance && sourceLayer && supportedLayers.has(sourceLayer.type)) {
68+
return getPieceLabel(pieceInstance.piece, sourceLayer.type, props.autowidth)
69+
}
70+
return <AdjustLabelFit {...props.autowidth} label={props.partName || ''} />
71+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @todo: use dynamic data for camera number
2+
export function CamInputIcon({
3+
inputIndex,
4+
abbreviation,
5+
}: {
6+
inputIndex?: string
7+
abbreviation?: string
8+
}): JSX.Element {
9+
return (
10+
<div className="piece-icon">
11+
<span className="camera">
12+
{abbreviation !== undefined ? abbreviation : 'C'}
13+
{inputIndex !== undefined ? inputIndex : ''}
14+
</span>
15+
</div>
16+
)
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function GraphicsInputIcon({ abbreviation }: { abbreviation?: string }): JSX.Element {
2+
return (
3+
<div className="piece-icon">
4+
<span className="graphics">{abbreviation !== undefined ? abbreviation : 'G'}</span>
5+
</div>
6+
)
7+
}

0 commit comments

Comments
 (0)