@@ -43,13 +43,13 @@ export interface BoardState {
43
43
/** Holds a modified version of `board` that DOESN'T contain whatever item is in-flight,
44
44
* or null if no item has currently been picked up from a sortable. */
45
45
draggingBoard : KanbanBoard | null ;
46
-
47
46
// Hold in-flight items in state so we can inject them back into draggingBoard, in a selector
48
47
cardInFlight : DraggedItem < Card > | null ;
49
48
listInFlight : DraggedItem < KanbanList > | null ;
50
-
51
49
nextId : number ;
52
50
spilledCard : boolean ;
51
+ isCopying : boolean ;
52
+ shouldCopy : boolean ;
53
53
}
54
54
55
55
export const initialState = {
@@ -59,8 +59,47 @@ export const initialState = {
59
59
listInFlight : null ,
60
60
nextId : 1000 ,
61
61
spilledCard : false ,
62
+ isCopying : false ,
63
+ shouldCopy : false ,
62
64
} ;
63
65
66
+ const resetDrag = ( state : BoardState ) : BoardState => ( {
67
+ ...state ,
68
+ draggingBoard : null ,
69
+ cardInFlight : null ,
70
+ listInFlight : null ,
71
+ isCopying : false ,
72
+ spilledCard : false
73
+ } ) ;
74
+
75
+ export const CARD_ID_WHEN_COPYING = Symbol ( "CLONED_CARD" ) as any ;
76
+ const cloneCard = ( card : Card , nextId : any ) => ( { ...card , id : nextId } ) ;
77
+
78
+ // - When you're not copying, draggingBoard holds 'board - cardInFlight.data',
79
+ // so you can just insertCard wherever it is meant to go.
80
+ // - When you ARE copying, you don't care: you want 'board + clone of
81
+ // cardInFlight.data'
82
+ // - So this function returns whichever one depending on isCopying, and neatly
83
+ // increments nextId for you (so it gives you a whole state back).
84
+ const copyOrInsertCard = ( state : BoardState , clonedCard : Card ) : BoardState => {
85
+ let { board, nextId } = state ;
86
+ if ( state . cardInFlight ) {
87
+ const { data, hover } = state . cardInFlight ;
88
+ if ( state . isCopying ) {
89
+ nextId ++ ;
90
+ board = state . spilledCard
91
+ ? state . board
92
+ : insertCard ( state . board , clonedCard , hover . listId , hover . index ) ;
93
+ } else {
94
+ const either = state . draggingBoard || state . board ;
95
+ board = state . spilledCard
96
+ ? either
97
+ : insertCard ( either , data , hover . listId , hover . index ) ;
98
+ }
99
+ }
100
+ return { ...resetDrag ( state ) , board, nextId } ;
101
+ }
102
+
64
103
// Each of these functions is a 'mini-reducer' dedicated to handling sort events.
65
104
// `action.event` is like `action.type`, so use it the same way with a switch statement.
66
105
@@ -80,21 +119,18 @@ export function listReducer(state: BoardState, action: SortList) {
80
119
}
81
120
case SortableEvents . Drop : {
82
121
return {
83
- ...state ,
122
+ ...resetDrag ( state ) ,
84
123
board : insertList ( currentBoard , data , hover . index ) ,
85
- draggingBoard : null ,
86
- listInFlight : null
87
124
} ;
88
125
}
89
126
case SortableEvents . EndDrag : {
90
- return { ... state , draggingBoard : null , listInFlight : null } ;
127
+ return resetDrag ( state ) ;
91
128
}
92
129
default : return state ;
93
130
}
94
131
}
95
132
96
133
export function cardReducer ( state : BoardState , action : SortCard ) {
97
- const currentBoard = state . draggingBoard || state . board ;
98
134
const { data, index, listId, hover } = action . item ;
99
135
100
136
// turn off 'spill' any time a reordering happens, because that means card has left spill area
@@ -104,22 +140,18 @@ export function cardReducer(state: BoardState, action: SortCard) {
104
140
return {
105
141
...state ,
106
142
draggingBoard : removeCard ( state . board , listId , index ) ,
107
- cardInFlight : action . item
143
+ cardInFlight : action . item ,
144
+ isCopying : state . shouldCopy && listId === 1 ,
108
145
} ;
109
146
}
110
147
case SortableEvents . Hover : {
111
148
return { ...state , cardInFlight : action . item } ;
112
149
}
113
150
case SortableEvents . Drop : {
114
- return {
115
- ...state ,
116
- board : insertCard ( currentBoard , data , hover . listId , hover . index ) ,
117
- draggingBoard : null ,
118
- cardInFlight : null
119
- } ;
151
+ return copyOrInsertCard ( state , cloneCard ( state . cardInFlight . data , state . nextId ) ) ;
120
152
}
121
153
case SortableEvents . EndDrag : {
122
- return { ... state , draggingBoard : null , cardInFlight : null } ;
154
+ return resetDrag ( state ) ;
123
155
}
124
156
default : return state ;
125
157
}
@@ -160,11 +192,7 @@ export function reducer(state: BoardState = initialState, action: Actions): Boar
160
192
}
161
193
162
194
case ActionTypes . Spill : {
163
- return {
164
- ...state ,
165
- spilledCard : true ,
166
- cardInFlight : action . item
167
- }
195
+ return { ...state , spilledCard : true , cardInFlight : action . item }
168
196
}
169
197
170
198
default :
@@ -173,11 +201,17 @@ export function reducer(state: BoardState = initialState, action: Actions): Boar
173
201
}
174
202
175
203
const _boardState = createFeatureSelector < BoardState > ( 'kanban' ) ;
176
- const _board = createSelector ( _boardState , state => state . draggingBoard || state . board ) ;
177
- const _cardInFlight = createSelector ( _boardState , state => state . cardInFlight ) ;
178
- const _listInFlight = createSelector ( _boardState , state => state . listInFlight ) ;
179
- const _spilledCard = createSelector ( _boardState , state => state . spilledCard ) ;
180
204
205
+ export const _isCopying = createSelector ( _boardState , s => s . isCopying ) ;
206
+ export const _cardInFlight = createSelector ( _boardState , state => state . cardInFlight ) ;
207
+ // produce a clone to be inserted into the board while dragging
208
+ // a permanent one is created on drop
209
+ export const _temporaryClone = createSelector (
210
+ _isCopying ,
211
+ _cardInFlight ,
212
+ ( copying , card ) => ( copying && card ) ? cloneCard ( card . data , CARD_ID_WHEN_COPYING ) : null ) ;
213
+
214
+ const _board = createSelector ( _boardState , state => state . board ) ;
181
215
export const _listById = ( listId : any ) => createSelector ( _board , board => {
182
216
const list = board . find ( l => l . id === listId ) ;
183
217
return list && list . cards ;
@@ -194,19 +228,19 @@ export const _listById = (listId: any) => createSelector(_board, board => {
194
228
// called. Then insertXXX is called here -- and it works.
195
229
196
230
export const _render = createSelector (
197
- _board ,
198
- _cardInFlight ,
199
- _listInFlight ,
200
- _spilledCard ,
201
- ( board , cardInFlight , listInFlight , spilledCard ) => {
202
- if ( cardInFlight != null && ! spilledCard ) {
203
- const { index , listId } = cardInFlight . hover ;
204
- board = insertCard ( board , cardInFlight . data , listId , index ) ;
231
+ _boardState ,
232
+ _temporaryClone ,
233
+ ( state , tempClone ) => {
234
+ const { cardInFlight , listInFlight } = state ;
235
+ let either = state . draggingBoard || state . board ;
236
+
237
+ if ( cardInFlight != null ) {
238
+ return copyOrInsertCard ( state , tempClone ) . board ;
205
239
}
206
240
if ( listInFlight != null ) {
207
- const { index , listId } = listInFlight . hover ;
208
- board = insertList ( board , listInFlight . data , index ) ;
241
+ const { hover , data } = listInFlight ;
242
+ return insertList ( either , data , hover . index ) ;
209
243
}
210
- return board ;
244
+ return either ;
211
245
}
212
246
) ;
0 commit comments