Skip to content

Commit f862daf

Browse files
committed
fix: entity list drag previews
1 parent 0bf485f commit f862daf

File tree

6 files changed

+311
-75
lines changed

6 files changed

+311
-75
lines changed

webui/src/Controls/Components/EntityEditorRow.tsx

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type SomeEntityModel,
66
} from '@companion-app/shared/Model/EntityModel.js'
77
import { observer } from 'mobx-react-lite'
8-
import React, { useContext, useState, useCallback, useRef } from 'react'
8+
import React, { useContext, useState, useCallback, useRef, useEffect } from 'react'
99
import { usePanelCollapseHelperContextForPanel } from '~/Helpers/CollapseHelper.js'
1010
import { useControlEntityService } from '~/Services/Controls/ControlEntitiesService.js'
1111
import { RootAppStoreContext } from '~/Stores/RootAppStore.js'
@@ -15,7 +15,8 @@ import { EntityManageChildGroups } from './EntityChildGroup.js'
1515
import { EntityCommonCells } from './EntityCommonCells.js'
1616
import { faSort } from '@fortawesome/free-solid-svg-icons'
1717
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
18-
import { useDrop, useDrag } from 'react-dnd'
18+
import { useDrop, useDrag, useDragLayer } from 'react-dnd'
19+
import { getEmptyImage } from 'react-dnd-html5-backend'
1920
import { checkDragStateWithThresholds, DragPlacement } from '~/Resources/DragAndDrop.js'
2021
import type { EntityListDragItem } from './EntityListDropZone.js'
2122
import type { ClientEntityDefinition } from '@companion-app/shared/Model/EntityDefinitionModel.js'
@@ -101,31 +102,79 @@ export const EntityTableRow = observer(function EntityTableRow({
101102
item.dragState = null
102103
},
103104
})
104-
const [{ isDragging }, drag, preview] = useDrag<EntityListDragItem, unknown, EntityTableRowDragStatus>({
105+
106+
const [_c, drag, preview] = useDrag<EntityListDragItem, unknown, EntityTableRowDragStatus>({
105107
type: dragId,
106108
canDrag: !readonly,
107-
item: {
109+
item: () => ({
108110
entityId: entity.id,
109111
listId: serviceFactory.listId,
110112
index: index,
111113
ownerId: ownerId,
112-
// ref: ref,
113114
dragState: null,
114-
},
115-
collect: (monitor) => ({
116-
isDragging: monitor.isDragging(),
115+
elementWidth: ref.current?.offsetWidth,
117116
}),
118117
})
119-
preview(drop(ref))
118+
119+
// Check if the current item is being dragged
120+
const { draggingItem } = useDragLayer((monitor) => ({
121+
draggingItem: monitor.getItem<EntityListDragItem>(),
122+
}))
123+
const isDragging = draggingItem?.entityId === entity.id
124+
125+
// Hide default browser preview
126+
useEffect(() => {
127+
preview(getEmptyImage())
128+
}, [preview])
129+
130+
// Connect drag and drop
131+
drop(ref)
120132

121133
if (!entity) {
122134
// Invalid entity, so skip
123135
return null
124136
}
125137

126138
return (
127-
<tr ref={ref} className={isDragging ? 'entitylist-dragging' : ''}>
128-
<td ref={drag} className="td-reorder">
139+
<EntityTableRowContent
140+
entity={entity}
141+
ownerId={ownerId}
142+
entityType={entityType}
143+
entityTypeLabel={entityTypeLabel}
144+
feedbackListType={feedbackListType}
145+
isDragging={isDragging}
146+
rowRef={ref}
147+
dragRef={drag}
148+
/>
149+
)
150+
})
151+
152+
interface EntityTableRowContentProps {
153+
entity: SomeEntityModel
154+
ownerId: EntityOwner | null
155+
156+
entityType: EntityModelType
157+
entityTypeLabel: string
158+
feedbackListType: ClientEntityDefinition['feedbackType']
159+
160+
isDragging: boolean
161+
rowRef: React.LegacyRef<HTMLTableRowElement> | null
162+
dragRef: React.LegacyRef<HTMLTableCellElement> | null
163+
}
164+
165+
export const EntityTableRowContent = observer(function EntityTableRowContent({
166+
entity,
167+
ownerId,
168+
entityType,
169+
entityTypeLabel,
170+
feedbackListType,
171+
isDragging,
172+
rowRef,
173+
dragRef,
174+
}: EntityTableRowContentProps): React.JSX.Element {
175+
return (
176+
<tr ref={rowRef} className={isDragging ? 'entitylist-dragging' : ''}>
177+
<td ref={dragRef} className="td-reorder">
129178
<FontAwesomeIcon icon={faSort} />
130179
</td>
131180
<td>
@@ -151,7 +200,7 @@ interface EntityEditorRowContentProps {
151200
feedbackListType: ClientEntityDefinition['feedbackType']
152201
}
153202

154-
export const EntityEditorRowContent = observer(function EntityEditorRowContent({
203+
const EntityEditorRowContent = observer(function EntityEditorRowContent({
155204
ownerId,
156205
entityTypeLabel,
157206
entity,

webui/src/Controls/Components/EntityList.tsx

Lines changed: 104 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { EntityOwner, SomeEntityModel, EntityModelType } from '@companion-app/shared/Model/EntityModel.js'
22
import React from 'react'
33
import { MyErrorBoundary } from '~/Resources/Error.js'
4-
import { EntityTableRow } from './EntityEditorRow.js'
5-
import { EntityDropPlaceholderZone } from './EntityListDropZone.js'
4+
import { EntityTableRow, EntityTableRowContent } from './EntityEditorRow.js'
5+
import { EntityDropPlaceholderZone, type EntityListDragItem } from './EntityListDropZone.js'
66
import type { ClientEntityDefinition } from '@companion-app/shared/Model/EntityDefinitionModel.js'
77
import { EntityEditorHeading } from './EntityEditorHeadingProps.js'
88
import { AddEntityPanel } from './AddEntityPanel.js'
99
import { observer } from 'mobx-react-lite'
1010
import { useEntityEditorContext } from './EntityEditorContext.js'
11+
import { useDragLayer } from 'react-dnd'
1112

1213
interface EditableEntityListProps {
1314
heading: JSX.Element | string | null
@@ -76,31 +77,107 @@ export const MinimalEntityList = observer(function MinimalEntityList({
7677
const dragId = `${controlId}_${entityType}`
7778

7879
return (
79-
<table className="table entity-table">
80-
<tbody>
81-
{entities &&
82-
entities.map((a, i) => (
83-
<MyErrorBoundary key={a?.id ?? i}>
84-
<EntityTableRow
85-
key={a?.id ?? i}
86-
ownerId={ownerId}
87-
entity={a}
88-
index={i}
89-
dragId={dragId}
90-
entityType={entityType}
91-
entityTypeLabel={entityTypeLabel}
92-
feedbackListType={feedbackListType}
93-
/>
94-
</MyErrorBoundary>
95-
))}
80+
<>
81+
<EntityListDragLayer
82+
entities={entities}
83+
dragId={dragId}
84+
ownerId={ownerId}
85+
entityType={entityType}
86+
entityTypeLabel={entityTypeLabel}
87+
feedbackListType={feedbackListType}
88+
/>
89+
<table className="table entity-table">
90+
<tbody>
91+
{entities &&
92+
entities.map((a, i) => (
93+
<MyErrorBoundary key={a?.id ?? i}>
94+
<EntityTableRow
95+
key={a?.id ?? i}
96+
ownerId={ownerId}
97+
entity={a}
98+
index={i}
99+
dragId={dragId}
100+
entityType={entityType}
101+
entityTypeLabel={entityTypeLabel}
102+
feedbackListType={feedbackListType}
103+
/>
104+
</MyErrorBoundary>
105+
))}
96106

97-
<EntityDropPlaceholderZone
98-
dragId={dragId}
99-
ownerId={ownerId}
100-
entityCount={entities ? entities.length : 0}
101-
entityTypeLabel={entityTypeLabel}
102-
/>
103-
</tbody>
104-
</table>
107+
<EntityDropPlaceholderZone
108+
dragId={dragId}
109+
ownerId={ownerId}
110+
entityCount={entities ? entities.length : 0}
111+
entityTypeLabel={entityTypeLabel}
112+
/>
113+
</tbody>
114+
</table>
115+
</>
116+
)
117+
})
118+
119+
interface EntityListDragLayerProps {
120+
entities: SomeEntityModel[] | undefined
121+
dragId: string
122+
ownerId: EntityOwner | null
123+
entityType: EntityModelType
124+
entityTypeLabel: string
125+
feedbackListType: ClientEntityDefinition['feedbackType']
126+
}
127+
128+
const EntityListDragLayer = observer(function EntityListDragLayer({
129+
entities,
130+
dragId,
131+
ownerId,
132+
entityType,
133+
entityTypeLabel,
134+
feedbackListType,
135+
}: EntityListDragLayerProps): JSX.Element | null {
136+
const { isDragging, item, currentOffset } = useDragLayer<{
137+
isDragging: boolean
138+
item: EntityListDragItem | null
139+
currentOffset: { x: number; y: number } | null
140+
}>((monitor) => ({
141+
isDragging: monitor.isDragging() && monitor.getItemType() === dragId,
142+
item: monitor.getItem(),
143+
currentOffset: monitor.getSourceClientOffset(),
144+
}))
145+
146+
if (!isDragging || !item || !currentOffset) {
147+
return null
148+
}
149+
150+
// Find the entity being dragged
151+
const entity = entities?.find((e) => e.id === item.entityId)
152+
if (!entity) return null
153+
154+
return (
155+
<div
156+
className="entity-list-drag-layer"
157+
style={{
158+
position: 'fixed',
159+
pointerEvents: 'none',
160+
zIndex: 100,
161+
left: 0,
162+
top: 0,
163+
transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
164+
width: item.elementWidth,
165+
}}
166+
>
167+
<table className="table entity-table">
168+
<tbody>
169+
<EntityTableRowContent
170+
ownerId={ownerId}
171+
entityTypeLabel={entityTypeLabel}
172+
entity={entity}
173+
feedbackListType={feedbackListType}
174+
entityType={entityType}
175+
isDragging={true}
176+
rowRef={null}
177+
dragRef={null}
178+
/>
179+
</tbody>
180+
</table>
181+
</div>
105182
)
106183
})

webui/src/Controls/Components/EntityListDropZone.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface EntityListDragItem {
1010
index: number
1111
ownerId: EntityOwner | null
1212
dragState: DragState | null
13+
elementWidth: number | undefined
1314
}
1415

1516
interface EntityDropPlaceholderZoneProps {

0 commit comments

Comments
 (0)