1- import React , { useCallback , useMemo } from 'react'
1+ import React , { useCallback , useMemo , useState } from 'react'
22import { Entity } from '@dcl/ecs'
33
44import { CAMERA , PLAYER , ROOT } from '../../lib/sdk/tree'
@@ -86,6 +86,7 @@ const Hierarchy: React.FC = () => {
8686 centerViewOnEntity
8787 } = useTree ( )
8888 const selectedEntities = useEntitiesWith ( ( components ) => components . Selection )
89+ const [ lastSelectedItem , setLastSelectedItem ] = useState < Entity | undefined > ( undefined )
8990
9091 const isSelected = useCallback (
9192 ( entity : Entity ) => {
@@ -94,13 +95,67 @@ const Hierarchy: React.FC = () => {
9495 [ selectedEntities ]
9596 )
9697
98+ const getAllVisibleEntities = useCallback ( ( ) => {
99+ const entities : Entity [ ] = [ ]
100+
101+ const traverse = ( entity : Entity ) => {
102+ if ( ! isHidden ( entity ) ) {
103+ entities . push ( entity )
104+ if ( isOpen ( entity ) ) {
105+ getChildren ( entity ) . forEach ( ( child ) => traverse ( child ) )
106+ }
107+ }
108+ }
109+
110+ traverse ( ROOT )
111+
112+ return entities
113+ } , [ getChildren , isOpen , isHidden ] )
114+
115+ const handleRangeSelection = useCallback (
116+ ( fromEntity : Entity , toEntity : Entity ) => {
117+ const allEntities = getAllVisibleEntities ( )
118+ const fromIndex = allEntities . findIndex ( ( e ) => getId ( e ) === getId ( fromEntity ) )
119+ const toIndex = allEntities . findIndex ( ( e ) => getId ( e ) === getId ( toEntity ) )
120+
121+ const startIndex = Math . min ( fromIndex , toIndex )
122+ const endIndex = Math . max ( fromIndex , toIndex )
123+
124+ allEntities . forEach ( ( entity , index ) => {
125+ if ( index >= startIndex && index <= endIndex ) {
126+ void select ( entity , index > startIndex ) // first item replaces selection, others add to selection
127+ }
128+ } )
129+ } ,
130+ [ getAllVisibleEntities , getId , select ]
131+ )
132+
133+ const handleSelect = useCallback (
134+ ( entity : Entity , clickType ?: 'single' | 'ctrl' | 'shift' ) => {
135+ if ( clickType === 'shift' && lastSelectedItem ) {
136+ handleRangeSelection ( lastSelectedItem , entity )
137+ } else {
138+ const isMultipleSelection = clickType === 'ctrl' || clickType === 'shift'
139+ void select ( entity , isMultipleSelection )
140+ }
141+ } ,
142+ [ select , lastSelectedItem , handleRangeSelection ]
143+ )
144+
145+ const handleLastSelectedChange = useCallback (
146+ ( entity : Entity ) => {
147+ if ( entity !== lastSelectedItem ) setLastSelectedItem ( entity )
148+ } ,
149+ [ lastSelectedItem ]
150+ )
151+
97152 const props = {
98153 getExtraContextMenu : ContextMenu ,
99154 onAddChild : addChild ,
100155 onDrop : setParent ,
101156 onRemove : remove ,
102157 onRename : rename ,
103- onSelect : select ,
158+ onSelect : handleSelect ,
104159 onDoubleSelect : centerViewOnEntity ,
105160 onSetOpen : setOpen ,
106161 onDuplicate : duplicate ,
@@ -115,7 +170,8 @@ const Hierarchy: React.FC = () => {
115170 canRemove : canRemove ,
116171 canDuplicate : canDuplicate ,
117172 canDrag : canDrag ,
118- canReorder : canReorder
173+ canReorder : canReorder ,
174+ onLastSelectedChange : handleLastSelectedChange
119175 }
120176
121177 return (
0 commit comments