1
1
/* eslint-disable @typescript-eslint/ban-ts-comment */
2
2
3
- import { toArray } from '@lumino/algorithm' ;
3
+ import { IDragEvent } from '@lumino/dragdrop' ;
4
+
5
+ import { ArrayExt , toArray } from '@lumino/algorithm' ;
6
+
7
+ import { ElementExt } from '@lumino/domutils' ;
4
8
5
9
import { PromiseDelegate , ReadonlyJSONObject } from '@lumino/coreutils' ;
6
10
7
- import { DOMUtils } from '@jupyterlab/apputils' ;
11
+ import { DOMUtils , showErrorMessage } from '@jupyterlab/apputils' ;
8
12
9
13
import { JupyterFrontEnd } from '@jupyterlab/application' ;
10
14
11
15
import { Contents , ContentsManager } from '@jupyterlab/services' ;
12
16
13
17
import { DocumentRegistry } from '@jupyterlab/docregistry' ;
14
18
19
+ import { renameFile } from '@jupyterlab/docmanager' ;
20
+
15
21
import { PathExt } from '@jupyterlab/coreutils' ;
16
22
17
23
import {
@@ -29,6 +35,16 @@ import { IStateDB } from '@jupyterlab/statedb';
29
35
// @ts -ignore
30
36
import folderOpenSvgstr from '../style/icons/folder-open.svg' ;
31
37
38
+ /**
39
+ * The class name added to drop targets.
40
+ */
41
+ const DROP_TARGET_CLASS = 'jp-mod-dropTarget' ;
42
+
43
+ /**
44
+ * The mime type for a contents drag object.
45
+ */
46
+ const CONTENTS_MIME = 'application/x-jupyter-icontents' ;
47
+
32
48
export const folderOpenIcon = new LabIcon ( {
33
49
name : 'ui-components:folder-open' ,
34
50
svgstr : folderOpenSvgstr
@@ -225,6 +241,148 @@ export class DirTreeListing extends DirListing {
225
241
}
226
242
}
227
243
}
244
+
245
+ private _eventDragEnter ( event : IDragEvent ) : void {
246
+ if ( event . mimeData . hasData ( CONTENTS_MIME ) ) {
247
+ // @ts -ignore
248
+ const index = this . _hitTestNodes ( this . _items , event ) ;
249
+
250
+ let target : HTMLElement ;
251
+ if ( index !== - 1 ) {
252
+ // @ts -ignore
253
+ target = this . _items [ index ] ;
254
+ } else {
255
+ target = event . target as HTMLElement ;
256
+ }
257
+ target . classList . add ( DROP_TARGET_CLASS ) ;
258
+ event . preventDefault ( ) ;
259
+ event . stopPropagation ( ) ;
260
+ }
261
+ }
262
+
263
+ private _eventDragOver ( event : IDragEvent ) : void {
264
+ event . preventDefault ( ) ;
265
+ event . stopPropagation ( ) ;
266
+ event . dropAction = event . proposedAction ;
267
+
268
+ const dropTarget = DOMUtils . findElement ( this . node , DROP_TARGET_CLASS ) ;
269
+ if ( dropTarget ) {
270
+ dropTarget . classList . remove ( DROP_TARGET_CLASS ) ;
271
+ }
272
+
273
+ // @ts -ignore
274
+ const index = this . _hitTestNodes ( this . _items , event ) ;
275
+
276
+ let target : HTMLElement ;
277
+ if ( index !== - 1 ) {
278
+ // @ts -ignore
279
+ target = this . _items [ index ] ;
280
+ } else {
281
+ target = event . target as HTMLElement ;
282
+ }
283
+ target . classList . add ( DROP_TARGET_CLASS ) ;
284
+ }
285
+
286
+ private _eventDrop ( event : IDragEvent ) : void {
287
+ event . preventDefault ( ) ;
288
+ event . stopPropagation ( ) ;
289
+ // @ts -ignore
290
+ clearTimeout ( this . _selectTimer ) ;
291
+ if ( event . proposedAction === 'none' ) {
292
+ event . dropAction = 'none' ;
293
+ return ;
294
+ }
295
+ if ( ! event . mimeData . hasData ( CONTENTS_MIME ) ) {
296
+ return ;
297
+ }
298
+
299
+ let target = event . target as HTMLElement ;
300
+ while ( target && target . parentElement ) {
301
+ if ( target . classList . contains ( DROP_TARGET_CLASS ) ) {
302
+ target . classList . remove ( DROP_TARGET_CLASS ) ;
303
+ break ;
304
+ }
305
+ target = target . parentElement ;
306
+ }
307
+
308
+ // Get the path based on the target node.
309
+ // @ts -ignore
310
+ const index = ArrayExt . firstIndexOf ( this . _items , target ) ;
311
+ let newDir : string ;
312
+
313
+ if ( index !== - 1 ) {
314
+ const item = toArray ( this . model . items ( ) ) [ index ] ;
315
+
316
+ if ( item . type === 'directory' ) {
317
+ newDir = item . path ;
318
+ } else {
319
+ newDir = PathExt . dirname ( item . path ) ;
320
+ }
321
+ } else {
322
+ newDir = '' ;
323
+ }
324
+
325
+ // @ts -ignore
326
+ const manager = this . _manager ;
327
+
328
+ // Handle the items.
329
+ const promises : Promise < Contents . IModel | null > [ ] = [ ] ;
330
+ const paths = event . mimeData . getData ( CONTENTS_MIME ) as string [ ] ;
331
+
332
+ if ( event . ctrlKey && event . proposedAction === 'move' ) {
333
+ event . dropAction = 'copy' ;
334
+ } else {
335
+ event . dropAction = event . proposedAction ;
336
+ }
337
+ for ( const path of paths ) {
338
+ const localPath = manager . services . contents . localPath ( path ) ;
339
+ const name = PathExt . basename ( localPath ) ;
340
+ const newPath = PathExt . join ( newDir , name ) ;
341
+ // Skip files that are not moving.
342
+ if ( newPath === path ) {
343
+ continue ;
344
+ }
345
+
346
+ if ( event . dropAction === 'copy' ) {
347
+ promises . push ( manager . copy ( path , newDir ) ) ;
348
+ } else {
349
+ promises . push ( renameFile ( manager , path , newPath ) ) ;
350
+ }
351
+ }
352
+ Promise . all ( promises ) . catch ( error => {
353
+ void showErrorMessage (
354
+ // @ts -ignore
355
+ this . _trans . _p ( 'showErrorMessage' , 'Error while copying/moving files' ) ,
356
+ error
357
+ ) ;
358
+ } ) ;
359
+ }
360
+
361
+ private _hitTestNodes ( nodes : HTMLElement [ ] , event : MouseEvent ) : number {
362
+ return ArrayExt . findFirstIndex (
363
+ nodes ,
364
+ node =>
365
+ ElementExt . hitTest ( node , event . clientX , event . clientY ) ||
366
+ event . target === node
367
+ ) ;
368
+ }
369
+
370
+ handleEvent ( event : Event ) : void {
371
+ switch ( event . type ) {
372
+ case 'lm-dragenter' :
373
+ this . _eventDragEnter ( event as IDragEvent ) ;
374
+ break ;
375
+ case 'lm-dragover' :
376
+ this . _eventDragOver ( event as IDragEvent ) ;
377
+ break ;
378
+ case 'lm-drop' :
379
+ this . _eventDrop ( event as IDragEvent ) ;
380
+ break ;
381
+ default :
382
+ super . handleEvent ( event ) ;
383
+ break ;
384
+ }
385
+ }
228
386
}
229
387
230
388
/**
0 commit comments