Skip to content

Commit 48521c6

Browse files
committed
feat: select part by double clicking a piece
1 parent 8dc7700 commit 48521c6

File tree

4 files changed

+145
-137
lines changed

4 files changed

+145
-137
lines changed

packages/webui/src/client/ui/RundownView/SelectedElementsContext.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from 'react'
22
import {
33
AdLibActionId,
4+
PartId,
45
PartInstanceId,
5-
PieceInstanceId,
6+
PieceId,
67
RundownId,
78
SegmentId,
89
} from '@sofie-automation/corelib/dist/dataModel/Ids'
@@ -18,14 +19,19 @@ interface SegmentElement {
1819
elementId: SegmentId
1920
}
2021

22+
interface PartElement {
23+
type: 'part'
24+
elementId: PartId
25+
}
26+
2127
interface PartInstanceElement {
2228
type: 'partInstance'
2329
elementId: PartInstanceId
2430
}
2531

26-
interface PieceInstanceElement {
27-
type: 'pieceInstance'
28-
elementId: PieceInstanceId
32+
interface PieceElement {
33+
type: 'piece'
34+
elementId: PieceId
2935
}
3036

3137
interface AdlibActionElement {
@@ -34,7 +40,13 @@ interface AdlibActionElement {
3440
}
3541

3642
// Union types for all possible elements
37-
type SelectedElement = RundownElement | SegmentElement | PartInstanceElement | PieceInstanceElement | AdlibActionElement
43+
type SelectedElement =
44+
| RundownElement
45+
| SegmentElement
46+
| PartElement
47+
| PartInstanceElement
48+
| PieceElement
49+
| AdlibActionElement
3850
type ElementId = SelectedElement['elementId']
3951

4052
export interface SelectionContextType {
@@ -155,13 +167,11 @@ export const useSelection = (): SelectionContextType => {
155167
}
156168

157169
// Helper hook for common selection patterns
158-
export const useElementSelection = (
159-
element: SelectedElement
160-
): { isSelected: boolean; clearAndSetSelection: () => void } => {
161-
const { isSelected, clearAndSetSelection } = useSelection()
170+
export const useElementSelection = (element: SelectedElement): { isSelected: boolean; toggleSelection: () => void } => {
171+
const { isSelected, toggleSelection } = useSelection()
162172

163173
return {
164174
isSelected: React.useMemo(() => isSelected(element.elementId), [isSelected, element.elementId]),
165-
clearAndSetSelection: React.useCallback(() => clearAndSetSelection(element), [clearAndSetSelection, element]),
175+
toggleSelection: React.useCallback(() => toggleSelection(element), [toggleSelection, element]),
166176
}
167177
}

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

Lines changed: 63 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,11 +1050,6 @@ export class SegmentTimelineClass extends React.Component<Translated<WithTiming<
10501050
role="region"
10511051
aria-roledescription={t('segment')}
10521052
aria-labelledby={`segment-name-${this.props.segment._id}`}
1053-
onDoubleClick={() => {
1054-
if (this.props.studio.settings.enableUserEdits && this.props.onSegmentSelect) {
1055-
this.props.onSegmentSelect(this.props.segment._id)
1056-
}
1057-
}}
10581053
>
10591054
<ContextMenuTrigger
10601055
id="segment-timeline-context-menu"
@@ -1065,62 +1060,70 @@ export class SegmentTimelineClass extends React.Component<Translated<WithTiming<
10651060
holdToDisplay={contextMenuHoldToDisplayTime()}
10661061
renderTag="div"
10671062
>
1068-
<h2
1069-
id={`segment-name-${this.props.segment._id}`}
1070-
className={
1071-
'segment-timeline__title__label' +
1072-
(this.props.segment.identifier ? ' identifier' : '') +
1073-
(this.state.isSelected ? ' selected' : '')
1074-
}
1075-
data-identifier={this.props.segment.identifier}
1063+
<div
1064+
onDoubleClick={() => {
1065+
if (this.props.studio.settings.enableUserEdits && this.props.onSegmentSelect) {
1066+
this.props.onSegmentSelect(this.props.segment._id)
1067+
}
1068+
}}
10761069
>
1077-
{/* for debugging: */ this.props.isSelected && <span>!!</span>}
1078-
{this.props.segment.name}
1079-
</h2>
1080-
{(criticalNotes > 0 || warningNotes > 0) && (
1081-
<div className="segment-timeline__title__notes">
1082-
{criticalNotes > 0 && (
1083-
<div
1084-
className="segment-timeline__title__notes__note segment-timeline__title__notes__note--critical"
1085-
onClick={() =>
1086-
this.props.onHeaderNoteClick &&
1087-
this.props.onHeaderNoteClick(this.props.segment._id, NoteSeverity.ERROR)
1088-
}
1089-
aria-label={t('Critical problems')}
1090-
>
1091-
<CriticalIconSmall />
1092-
<div className="segment-timeline__title__notes__count">{criticalNotes}</div>
1093-
</div>
1094-
)}
1095-
{warningNotes > 0 && (
1096-
<div
1097-
className="segment-timeline__title__notes__note segment-timeline__title__notes__note--warning"
1098-
onClick={() =>
1099-
this.props.onHeaderNoteClick &&
1100-
this.props.onHeaderNoteClick(this.props.segment._id, NoteSeverity.WARNING)
1101-
}
1102-
aria-label={t('Warnings')}
1103-
>
1104-
<WarningIconSmall />
1105-
<div className="segment-timeline__title__notes__count">{warningNotes}</div>
1106-
</div>
1107-
)}
1108-
</div>
1109-
)}
1110-
{identifiers.length > 0 && (
1111-
<div className="segment-timeline__part-identifiers">
1112-
{identifiers.map((ident) => (
1113-
<div
1114-
className="segment-timeline__part-identifiers__identifier"
1115-
key={ident.partId + ''}
1116-
onClick={() => this.onClickPartIdent(ident.partId)}
1117-
>
1118-
{ident.ident}
1119-
</div>
1120-
))}
1121-
</div>
1122-
)}
1123-
<HeaderEditStates userEditOperations={this.props.segment.userEditOperations} />
1070+
<h2
1071+
id={`segment-name-${this.props.segment._id}`}
1072+
className={
1073+
'segment-timeline__title__label' +
1074+
(this.props.segment.identifier ? ' identifier' : '') +
1075+
(this.state.isSelected ? ' selected' : '')
1076+
}
1077+
data-identifier={this.props.segment.identifier}
1078+
>
1079+
{/* for debugging: */ this.props.isSelected && <span>!!</span>}
1080+
{this.props.segment.name}
1081+
</h2>
1082+
{(criticalNotes > 0 || warningNotes > 0) && (
1083+
<div className="segment-timeline__title__notes">
1084+
{criticalNotes > 0 && (
1085+
<div
1086+
className="segment-timeline__title__notes__note segment-timeline__title__notes__note--critical"
1087+
onClick={() =>
1088+
this.props.onHeaderNoteClick &&
1089+
this.props.onHeaderNoteClick(this.props.segment._id, NoteSeverity.ERROR)
1090+
}
1091+
aria-label={t('Critical problems')}
1092+
>
1093+
<CriticalIconSmall />
1094+
<div className="segment-timeline__title__notes__count">{criticalNotes}</div>
1095+
</div>
1096+
)}
1097+
{warningNotes > 0 && (
1098+
<div
1099+
className="segment-timeline__title__notes__note segment-timeline__title__notes__note--warning"
1100+
onClick={() =>
1101+
this.props.onHeaderNoteClick &&
1102+
this.props.onHeaderNoteClick(this.props.segment._id, NoteSeverity.WARNING)
1103+
}
1104+
aria-label={t('Warnings')}
1105+
>
1106+
<WarningIconSmall />
1107+
<div className="segment-timeline__title__notes__count">{warningNotes}</div>
1108+
</div>
1109+
)}
1110+
</div>
1111+
)}
1112+
{identifiers.length > 0 && (
1113+
<div className="segment-timeline__part-identifiers">
1114+
{identifiers.map((ident) => (
1115+
<div
1116+
className="segment-timeline__part-identifiers__identifier"
1117+
key={ident.partId + ''}
1118+
onClick={() => this.onClickPartIdent(ident.partId)}
1119+
>
1120+
{ident.ident}
1121+
</div>
1122+
))}
1123+
</div>
1124+
)}
1125+
<HeaderEditStates userEditOperations={this.props.segment.userEditOperations} />
1126+
</div>
11241127
</ContextMenuTrigger>
11251128
<div className="segment-timeline__duration" tabIndex={0}>
11261129
{this.props.playlist &&

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,11 +732,15 @@ const SourceLayerItemWithSelection = withTranslation()(
732732
export const SourceLayerItem = (props: ISourceLayerItemProps): React.ReactElement => {
733733
const { isSelected, clearAndSetSelection } = useSelection()
734734

735-
const isPieceSelected = isSelected(props.piece.instance._id)
735+
const isPieceSelected = isSelected(props.piece.instance.piece._id)
736736

737737
const handlePieceSelect = React.useCallback(
738738
(piece: PieceUi, e: React.MouseEvent<HTMLDivElement>) => {
739-
clearAndSetSelection({ type: 'pieceInstance', elementId: piece.instance._id })
739+
// This is only selected the corresponding part
740+
// As the piece currently doesn't have a unique ID, that can be used for back reference
741+
// If it's not an instance
742+
const partId = props.part.instance.part._id
743+
clearAndSetSelection({ type: 'part', elementId: partId })
740744
props.onClick?.(piece, e)
741745
},
742746
[isPieceSelected]

packages/webui/src/client/ui/UserEditOperations/PropertiesPanel.tsx

Lines changed: 56 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ import {
1919
} from '@sofie-automation/corelib/dist/dataModel/UserEditingDefinitions'
2020
import { useTranslation } from 'react-i18next'
2121
import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData'
22-
import _ from 'underscore'
23-
import { Segments, PieceInstances } from '../../collections'
24-
import { UIPartInstances, UIParts } from '../Collections'
22+
import { Segments } from '../../collections'
23+
import { UIParts } from '../Collections'
2524
import { useSelection } from '../RundownView/SelectedElementsContext'
2625
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
2726
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
@@ -46,16 +45,9 @@ export function PropertiesPanel(): JSX.Element {
4645
}
4746
}, [])
4847

49-
const pieceInstance = useTracker(
50-
() => PieceInstances.findOne({ _id: selectedElement.elementId }),
51-
[selectedElement?.elementId]
52-
)
48+
//const piece = useTracker(() => Pieces.findOne({ _id: selectedElement.elementId }), [selectedElement?.elementId])
5349

54-
const partInstance = useTracker(
55-
() => UIPartInstances.findOne({ _id: pieceInstance ? pieceInstance.partInstanceId : selectedElement.elementId }),
56-
[selectedElement?.elementId]
57-
)
58-
const part = useTracker(() => UIParts.findOne({ _id: partInstance?.part._id }), [partInstance?.part._id])
50+
const part = useTracker(() => UIParts.findOne({ _id: selectedElement.elementId }), [selectedElement?.elementId])
5951

6052
const segment: DBSegment | undefined = useTracker(
6153
() => Segments.findOne({ _id: part ? part.segmentId : selectedElement.elementId }),
@@ -67,60 +59,59 @@ export function PropertiesPanel(): JSX.Element {
6759

6860
return (
6961
<div className="propertiespanel-pop-up">
70-
{selectedElement.type === 'partInstance' ||
71-
(selectedElement.type === 'pieceInstance' && (
72-
<>
73-
<div className="propertiespanel-pop-up__header">
74-
{part?.userEditOperations &&
75-
part.userEditOperations.map((operation) => {
76-
if (operation.type === UserEditingType.FORM || !operation.svgIcon || !operation.isActive) return null
62+
{selectedElement.type === 'part' && (
63+
<>
64+
<div className="propertiespanel-pop-up__header">
65+
{part?.userEditOperations &&
66+
part.userEditOperations.map((operation) => {
67+
if (operation.type === UserEditingType.FORM || !operation.svgIcon || !operation.isActive) return null
7768

78-
return (
79-
<div
80-
key={operation.id}
81-
className="svg"
82-
dangerouslySetInnerHTML={{
83-
__html: operation.svgIcon,
84-
}}
85-
></div>
86-
)
87-
})}
88-
PART : {String(part?.title)}
89-
</div>
90-
<div className="propertiespanel-pop-up__contents">
91-
{segment &&
92-
part?._id &&
93-
part.userEditOperations?.map((userEditOperation, i) => {
94-
switch (userEditOperation.type) {
95-
case UserEditingType.ACTION:
96-
return (
97-
<EditingTypeAction
98-
key={i}
99-
userEditOperation={userEditOperation}
100-
segment={segment}
101-
part={part}
102-
rundownId={rundownId}
103-
/>
104-
)
105-
case UserEditingType.FORM:
106-
return (
107-
<EditingTypeChangeSource
108-
key={i}
109-
userEditOperation={userEditOperation}
110-
segment={segment}
111-
part={part}
112-
rundownId={rundownId}
113-
/>
114-
)
115-
default:
116-
assertNever(userEditOperation)
117-
return null
118-
}
119-
})}
120-
<hr />
121-
</div>
122-
</>
123-
))}
69+
return (
70+
<div
71+
key={operation.id}
72+
className="svg"
73+
dangerouslySetInnerHTML={{
74+
__html: operation.svgIcon,
75+
}}
76+
></div>
77+
)
78+
})}
79+
PART : {String(part?.title)}
80+
</div>
81+
<div className="propertiespanel-pop-up__contents">
82+
{segment &&
83+
part?._id &&
84+
part.userEditOperations?.map((userEditOperation, i) => {
85+
switch (userEditOperation.type) {
86+
case UserEditingType.ACTION:
87+
return (
88+
<EditingTypeAction
89+
key={i}
90+
userEditOperation={userEditOperation}
91+
segment={segment}
92+
part={part}
93+
rundownId={rundownId}
94+
/>
95+
)
96+
case UserEditingType.FORM:
97+
return (
98+
<EditingTypeChangeSource
99+
key={i}
100+
userEditOperation={userEditOperation}
101+
segment={segment}
102+
part={part}
103+
rundownId={rundownId}
104+
/>
105+
)
106+
default:
107+
assertNever(userEditOperation)
108+
return null
109+
}
110+
})}
111+
<hr />
112+
</div>
113+
</>
114+
)}
124115
{selectedElement.type === 'segment' && (
125116
<>
126117
<div className="propertiespanel-pop-up__header">

0 commit comments

Comments
 (0)