I am using a multi-container DndContext with SortableContext to drag and drop tracks and blocks. The tracks are individual parent containers which can be dragged separately. Inside each track, there are blocks which can be dragged inside that track and outside to any other track as well. I am using redux for state management and I am trying to implement dnd kit based on redux
I have implemented the code and it works well for the individual track dragging and also for block dragging inside each track. Problem is that when I try to autoscroll a block, it does not autoscroll. It only autoscrolls when I drag it outside of the div where the DndContext is placed and push the dragged block below in the outter div. Here is the main are where the dndContext is placed
<div className="editor__tracks">
<DndContext
sensors={sensors}
// collisionDetection={}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
// modifiers={[restrictToVerticalAxis]}
>
<SortableContext
items={currentProjectTracksLocalState}
strategy={verticalListSortingStrategy}
>
{currentProjectTracksLocalState.map((t, i) => (
<SortableTrack
key={i}
track={t}
selectedTrack={selectedTrack ?? null}
timeMultiplier={timeMultiplier}
playBackTime={playBackTime}
timeScrollPos={timeScrollPos}
/>
))}
</SortableContext>
<DragOverlay dropAnimation={dropAnimation}>{dragOverlay}</DragOverlay>
</DndContext>
</div>v
For blocks, the SortableContext is placed like this:
const currentTrackBlocksIds = blocks?.map((b) => b.blockId) ?? []
const trackBlocksContainerRef = useRef<HTMLDivElement>(null)
const blockWrapperRef = useRef<HTMLDivElement>(null)
blocks?.forEach((b) => {
totalDuration += b.duration
})
const blockParameterHandlingEnabled = blockParameterHandling !== 'disabled'
return (
<SortableContext items={currentTrackBlocksIds} strategy={horizontalListSortingStrategy}>
<div className="track" ref={trackBlocksContainerRef} onClick={onClick}>
<div
className="track__block-wrapper"
ref={blockWrapperRef}
style={{
marginLeft: timeScrollPos * visualMultiplier
}}
>
{blocks?.map((b, i) => {
const baseProps = {
parentTrackId: id,
isClickable: areBlocksClickable,
key: `Track-${title}-Block-${i}`,
visualMultiplier,
duration: b.duration,
blockHeight,
onSelect: () => {
onSelectBlock(b.blockId)
},
onChangeDuration: (v: number) => {
if (blockParameterHandlingEnabled) {
blockParameterHandling.onChangeBlockDuration(v, b.blockId)
}
},
selected: b.blockId === selectedBlockId
}
// Check if blockParameterHandling is enabled, else return empty functions
let onIntChange = (v: number): void => {}
let onEffectChange = (propertyValuePair: EffectChangeKeyValueType,
direction: 'in' | 'out',
isFrequency: boolean,
i: number): void => {}
let onFreqChange = (v: number, i: number): void => {}
if (blockParameterHandlingEnabled) {
onIntChange = (v: number) => {
blockParameterHandling.onChangeBlockIntensity(v, b.blockId)
}
onEffectChange = (
propertyValuePair: EffectChangeKeyValueType,
direction: 'in' | 'out',
isFrequency: boolean
) => {
blockParameterHandling.onChangeBlockEffect(
propertyValuePair,
direction,
isFrequency,
b.blockId
)
}
onFreqChange = (v: number) => {
blockParameterHandling.onChangeBlockFrequency(v, b.blockId)
}
}
switch (b.type) {
case 'pause':
return <PauseBlock {...baseProps} type={'pause'} blockId={b.blockId} />
break
case 'pulse':
return (
<PulseBlock
{...baseProps}
type={'pulse'}
blockId={b.blockId}
intensity={b.intensity}
onChangeIntensity={(v: number) => {
onIntChange(v)
}}
frequencyIsFocus={b.type === 'pulse'}
frequency={b?.frequency}
onChangeFrequency={(v: number) => {
onFreqChange(v, i)
}}
/>
)
break
case 'vibration':
return (
<VibrateBlock
{...baseProps}
type={'vibration'}
blockId={b.blockId}
intensity={b.intensity}
onChangeIntensity={(v: number) => {
onIntChange(v)
}}
inEffect={b?.inEffect}
outEffect={b?.outEffect}
frequency={b?.frequency}
frequencyIsFocus={b.type === 'vibration'}
onChangeFrequency={(v: number) => {
onFreqChange(v, i)
}}
onChangeEffect={(
propertyValuePair,
direction,
isFrequency
) => {
onEffectChange(propertyValuePair, direction, isFrequency, i)
}}
/>
)
break
default:
console.error('This block type is not defined')
return null
break
}
})}
{children}
{playbackTime !== undefined && (
<div
style={{
left: playbackTime * visualMultiplier
}}
className="track__playback-time-indicator"
/>
)}
</div>
</div>
</SortableContext>
)
}
These are my dragover, dragstart and dragend
const handleDragStart = (event: DragStartEvent): void => {
const { active } = event
if (active.data.current?.type === 'block') {
setDragOverlay(
<BlockParent {...active.data.current?.props} />
)
} else {
if (active.data.current?.props.track.type === 'single') {
setDragOverlay(
<EditorTrack {...active.data.current?.props} />
)
} else {
setDragOverlay(
<MultiTrack {...active.data.current?.props} />
)
}
}
}
const handleDragEnd = (event: DragEndEvent): void => {
setDragOverlay(null)
const { active, over } = event
if (active === null || over === null) {
return
}
if (active.id !== over.id) {
// See if active id and over id are contained in allCurrentProjectTracks
if (
active.data.current?.type === 'track' && over.data.current?.type === 'track'
) {
setcurrentProjectTracksLocalState((tracks: TrackItem[]) => {
const oldIndex = tracks.findIndex((track) => track.id === active.id)
const newIndex = tracks.findIndex((track) => track.id === over.id)
return arrayMove(tracks, oldIndex, newIndex)
})
} else {
dispatch(resortBlocksOnDragging({ activeBlockId: active.id, overBlockId: over.id, overTrackId: over.data.current?.parentTrackId }))
}
}
}
const handleDragOver = (event: DragOverEvent): void => {
const { active, over } = event
const activeBlockParentTrackId = active.data.current?.parentTrackId
const overBlockParentTrackId = over?.data.current?.parentTrackId
if (activeBlockParentTrackId === undefined || overBlockParentTrackId === undefined) {
return
}
if (activeBlockParentTrackId !== overBlockParentTrackId) {
dispatch(editBlockParentTrackId({ blockId: active.id, newTrackId: overBlockParentTrackId }))
}
}