Skip to content

Commit 7a79afa

Browse files
committed
feat: doubleclick on part selection for properties panel
1 parent 2ad708a commit 7a79afa

File tree

3 files changed

+112
-74
lines changed

3 files changed

+112
-74
lines changed

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react'
22
import {
33
AdLibActionId,
44
PartInstanceId,
5-
PieceId,
5+
PieceInstanceId,
66
RundownId,
77
SegmentId,
88
} from '@sofie-automation/corelib/dist/dataModel/Ids'
@@ -23,9 +23,9 @@ interface PartInstanceElement {
2323
elementId: PartInstanceId
2424
}
2525

26-
interface PieceElement {
27-
type: 'piece'
28-
elementId: PieceId
26+
interface PieceInstanceElement {
27+
type: 'pieceInstance'
28+
elementId: PieceInstanceId
2929
}
3030

3131
interface AdlibActionElement {
@@ -34,7 +34,7 @@ interface AdlibActionElement {
3434
}
3535

3636
// Union types for all possible elements
37-
type SelectedElement = RundownElement | SegmentElement | PartInstanceElement | PieceElement | AdlibActionElement
37+
type SelectedElement = RundownElement | SegmentElement | PartInstanceElement | PieceInstanceElement | AdlibActionElement
3838
type ElementId = SelectedElement['elementId']
3939

4040
export interface SelectionContextType {
@@ -155,11 +155,13 @@ export const useSelection = (): SelectionContextType => {
155155
}
156156

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

161163
return {
162164
isSelected: React.useMemo(() => isSelected(element.elementId), [isSelected, element.elementId]),
163-
toggleSelection: React.useCallback(() => toggleSelection(element), [toggleSelection, element]),
165+
clearAndSetSelection: React.useCallback(() => clearAndSetSelection(element), [clearAndSetSelection, element]),
164166
}
165167
}

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

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { TransitionSourceRenderer } from './Renderers/TransitionSourceRenderer'
2323
import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
2424
import { ReadonlyDeep } from 'type-fest'
2525
import { PieceContentStatusObj } from '@sofie-automation/meteor-lib/dist/api/pieceContentStatus'
26+
import { useSelection } from '../RundownView/SelectedElementsContext'
2627
const LEFT_RIGHT_ANCHOR_SPACER = 15
2728
const MARGINAL_ANCHORED_WIDTH = 5
2829

@@ -102,11 +103,20 @@ interface ISourceLayerItemState {
102103
/** Set to `true` when the segment is "highlighted" (in focus, generally from a scroll event) */
103104
highlight: boolean
104105
}
105-
export const SourceLayerItem = withTranslation()(
106-
class SourceLayerItem extends React.Component<ISourceLayerItemProps & WithTranslation, ISourceLayerItemState> {
106+
107+
interface WithSelectionProps {
108+
handlePieceSelect: (piece: PieceUi, e: React.MouseEvent<HTMLDivElement>) => void
109+
isPieceSelected: boolean
110+
}
111+
112+
const SourceLayerItemWithSelection = withTranslation()(
113+
class SourceLayerItem extends React.Component<
114+
ISourceLayerItemProps & WithTranslation & WithSelectionProps,
115+
ISourceLayerItemState
116+
> {
107117
animFrameHandle: number | undefined
108118

109-
constructor(props: ISourceLayerItemProps & WithTranslation) {
119+
constructor(props: ISourceLayerItemProps & WithTranslation & WithSelectionProps) {
110120
super(props)
111121
this.state = {
112122
showMiniInspector: false,
@@ -465,11 +475,15 @@ export const SourceLayerItem = withTranslation()(
465475
}
466476

467477
itemDblClick = (e: React.MouseEvent<HTMLDivElement>) => {
468-
e.preventDefault()
469-
e.stopPropagation()
478+
if (this.props.studio?.settings.enableUserEdits) {
479+
this.props.handlePieceSelect(this.props.piece, e)
480+
} else {
481+
e.preventDefault()
482+
e.stopPropagation()
470483

471-
if (typeof this.props.onDoubleClick === 'function') {
472-
this.props.onDoubleClick(this.props.piece, e)
484+
if (typeof this.props.onDoubleClick === 'function') {
485+
this.props.onDoubleClick(this.props.piece, e)
486+
}
473487
}
474488
}
475489

@@ -714,3 +728,21 @@ export const SourceLayerItem = withTranslation()(
714728
}
715729
}
716730
)
731+
732+
export const SourceLayerItem = (props: ISourceLayerItemProps & WithTranslation): React.ReactElement => {
733+
const { isSelected, clearAndSetSelection } = useSelection()
734+
735+
const isPieceSelected = isSelected(props.piece.instance._id)
736+
737+
const handlePieceSelect = React.useCallback(
738+
(piece: PieceUi, e: React.MouseEvent<HTMLDivElement>) => {
739+
clearAndSetSelection({ type: 'pieceInstance', elementId: piece.instance._id })
740+
props.onClick?.(piece, e)
741+
},
742+
[isPieceSelected]
743+
)
744+
745+
return (
746+
<SourceLayerItemWithSelection {...props} handlePieceSelect={handlePieceSelect} isPieceSelected={isPieceSelected} />
747+
)
748+
}

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

Lines changed: 63 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import * as React from 'react'
2-
// @ts-expect-error No types available
3-
import * as VelocityReact from 'velocity-react'
42
import { i18nTranslator } from '../i18n'
53
import { translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
64
import { doUserAction, UserAction } from '../../lib/clientUserAction'
@@ -22,7 +20,7 @@ import {
2220
import { useTranslation } from 'react-i18next'
2321
import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData'
2422
import _ from 'underscore'
25-
import { Segments } from '../../collections'
23+
import { Segments, PieceInstances } from '../../collections'
2624
import { UIPartInstances, UIParts } from '../Collections'
2725
import { useSelection } from '../RundownView/SelectedElementsContext'
2826
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
@@ -46,75 +44,81 @@ export function PropertiesPanel(): JSX.Element {
4644
}
4745
}, [])
4846

47+
const pieceInstance = useTracker(
48+
() => PieceInstances.findOne({ _id: selectedElement.elementId }),
49+
[selectedElement?.elementId]
50+
)
51+
4952
const partInstance = useTracker(
50-
() => UIPartInstances.findOne({ _id: selectedElement.elementId }),
51-
[selectedElement.elementId]
53+
() => UIPartInstances.findOne({ _id: pieceInstance ? pieceInstance.partInstanceId : selectedElement.elementId }),
54+
[selectedElement?.elementId]
5255
)
5356
const part = useTracker(() => UIParts.findOne({ _id: partInstance?.part._id }), [partInstance?.part._id])
5457

5558
const segment: DBSegment | undefined = useTracker(
5659
() => Segments.findOne({ _id: part ? part.segmentId : selectedElement.elementId }),
57-
[selectedElement.elementId]
60+
[selectedElement?.elementId]
5861
)
5962
const rundownId = part ? part.rundownId : segment?.rundownId
6063

6164
if (!rundownId) return <></>
6265

6366
return (
6467
<div className="propertiespanel-pop-up">
65-
{selectedElement.type === 'partInstance' && (
66-
<>
67-
<div className="propertiespanel-pop-up__header">
68-
{part?.userEditOperations &&
69-
part.userEditOperations.map((operation) => {
70-
if (operation.type === UserEditingType.FORM || !operation.svgIcon || !operation.isActive) return null
68+
{selectedElement.type === 'partInstance' ||
69+
(selectedElement.type === 'pieceInstance' && (
70+
<>
71+
<div className="propertiespanel-pop-up__header">
72+
{part?.userEditOperations &&
73+
part.userEditOperations.map((operation) => {
74+
if (operation.type === UserEditingType.FORM || !operation.svgIcon || !operation.isActive) return null
7175

72-
return (
73-
<div
74-
key={operation.id}
75-
className="svg"
76-
dangerouslySetInnerHTML={{
77-
__html: operation.svgIcon,
78-
}}
79-
></div>
80-
)
81-
})}
82-
PART : {String(part?.title)}
83-
</div>
84-
<div className="propertiespanel-pop-up__contents">
85-
{segment &&
86-
part?._id &&
87-
part.userEditOperations?.map((userEditOperation, i) => {
88-
switch (userEditOperation.type) {
89-
case UserEditingType.ACTION:
90-
return (
91-
<EditingTypeAction
92-
key={i}
93-
userEditOperation={userEditOperation}
94-
segment={segment}
95-
part={part}
96-
rundownId={rundownId}
97-
/>
98-
)
99-
case UserEditingType.FORM:
100-
return (
101-
<EditingTypeChangeSource
102-
key={i}
103-
userEditOperation={userEditOperation}
104-
segment={segment}
105-
part={part}
106-
rundownId={rundownId}
107-
/>
108-
)
109-
default:
110-
assertNever(userEditOperation)
111-
return null
112-
}
113-
})}
114-
<hr />
115-
</div>
116-
</>
117-
)}
76+
return (
77+
<div
78+
key={operation.id}
79+
className="svg"
80+
dangerouslySetInnerHTML={{
81+
__html: operation.svgIcon,
82+
}}
83+
></div>
84+
)
85+
})}
86+
PART : {String(part?.title)}
87+
</div>
88+
<div className="propertiespanel-pop-up__contents">
89+
{segment &&
90+
part?._id &&
91+
part.userEditOperations?.map((userEditOperation, i) => {
92+
switch (userEditOperation.type) {
93+
case UserEditingType.ACTION:
94+
return (
95+
<EditingTypeAction
96+
key={i}
97+
userEditOperation={userEditOperation}
98+
segment={segment}
99+
part={part}
100+
rundownId={rundownId}
101+
/>
102+
)
103+
case UserEditingType.FORM:
104+
return (
105+
<EditingTypeChangeSource
106+
key={i}
107+
userEditOperation={userEditOperation}
108+
segment={segment}
109+
part={part}
110+
rundownId={rundownId}
111+
/>
112+
)
113+
default:
114+
assertNever(userEditOperation)
115+
return null
116+
}
117+
})}
118+
<hr />
119+
</div>
120+
</>
121+
))}
118122
{selectedElement.type === 'segment' && (
119123
<>
120124
<div className="propertiespanel-pop-up__header">
@@ -305,7 +309,7 @@ function EditingTypeChangeSource(props: {
305309
enum: string[]
306310
tsEnumNames: string[]
307311
}
308-
let groups: UserEditingGroupingType[] = clone(props.userEditOperation.grouping) || []
312+
const groups: UserEditingGroupingType[] = clone(props.userEditOperation.grouping) || []
309313
const numberOfEmptySlots = 14 - groups.length
310314
for (let i = 0; i < numberOfEmptySlots; i++) {
311315
groups.push({})

0 commit comments

Comments
 (0)