| 1 | import { DragEvent } from 'react' |
| 2 | import { moveFiles } from './clip' |
| 3 | import { DirEntry } from './state' |
| 4 | |
| 5 | let entry = '' // dataTransfer.getData is not available onDragOver, so we use this var to keep track |
| 6 | let accept = false |
| 7 | let classedEl: HTMLElement | undefined |
| 8 | const className = 'drop-over' |
| 9 | |
| 10 | export const dragFilesSource = (de: DirEntry) => de.canDelete() ? { |
| 11 | draggable: true, |
| 12 | onDragStart(ev: DragEvent) { |
| 13 | entry = (ev.target as HTMLElement).getAttribute('href') || '' |
| 14 | }, |
| 15 | ...de.canUpload() && dragFilesDestination, |
| 16 | } : null |
| 17 | |
| 18 | export const dragFilesDestination = { |
| 19 | onDragOver(ev: DragEvent) { |
| 20 | if (!accept) return |
| 21 | ev.preventDefault() |
| 22 | ev.stopPropagation() |
| 23 | ev.dataTransfer.dropEffect = 'move' // on most browser this just avoids the "+" icon of the 'copy' operation |
| 24 | }, |
| 25 | onDrop(ev: DragEvent) { |
| 26 | classedEl?.classList.remove(className) |
| 27 | const el = ev.currentTarget as HTMLElement |
| 28 | const src = entry |
| 29 | if (!src) return |
| 30 | const dst = el.getAttribute('href') || '/' |
| 31 | if (src === dst) return |
| 32 | ev.preventDefault() |
| 33 | void moveFiles([src], dst) |
| 34 | }, |
| 35 | onDragEnter(ev: DragEvent) { // we "accept" here and in dropOver, but this is fired first, so we calculate it here |
| 36 | accept = false |
| 37 | const src = entry |
| 38 | if (!src) return |
| 39 | const dst = (ev.currentTarget as HTMLElement).getAttribute('href') || '/' |
| 40 | if (src === dst) return |
| 41 | accept = true |
| 42 | const el = ev.currentTarget as HTMLElement |
| 43 | if (el.tagName !== 'A') return |
| 44 | classedEl?.classList.remove(className) |
| 45 | classedEl = el |
| 46 | el.classList.add(className) // manipulating the dom is a risk with react, and would cause problems if React is changing classes in the meantime, but for now this is not the case, so we keep the code simpler |
| 47 | }, |
| 48 | onDragLeave(ev: DragEvent) { |
| 49 | if (ev.relatedTarget && ev.currentTarget.contains(ev.relatedTarget as any)) return // with the nested dom (SPAN in A) we can get a second enter before the leave of the previous, and getting the correct behavior was actually empirical: test thoroughly for any change |
| 50 | if (!accept) return |
| 51 | const el = ev.currentTarget as HTMLElement |
| 52 | if (el !== classedEl) return |
| 53 | if (el.tagName !== 'A') return |
| 54 | classedEl?.classList.remove(className) |
| 55 | }, |
| 56 | } |