|
2 | 2 | import { getContext, onMount, onDestroy, tick } from "svelte"; |
3 | 3 |
|
4 | 4 | import type { Editor } from "@graphite/editor"; |
5 | | - import { beginDraggingElement } from "@graphite/io-managers/drag"; |
6 | 5 | import { |
7 | 6 | defaultWidgetLayout, |
8 | 7 | patchWidgetLayout, |
|
50 | 49 | let layers: LayerListingInfo[] = []; |
51 | 50 |
|
52 | 51 | // Interactive dragging |
53 | | - let draggable = true; |
54 | 52 | let draggingData: undefined | DraggingData = undefined; |
55 | 53 | let fakeHighlightOfNotYetSelectedLayerBeingDragged: undefined | bigint = undefined; |
56 | 54 | let dragInPanel = false; |
| 55 | + let isDragging = false; |
| 56 | + let dragStartPosition = { x: 0, y: 0 }; |
| 57 | + const dragThreshold = 5; |
57 | 58 |
|
58 | 59 | // Interactive clipping |
59 | 60 | let layerToClipUponClick: LayerListingInfo | undefined = undefined; |
|
185 | 186 |
|
186 | 187 | async function onEditLayerName(listing: LayerListingInfo) { |
187 | 188 | if (listing.editingName) return; |
188 | | -
|
189 | | - draggable = false; |
190 | 189 | listing.editingName = true; |
191 | 190 | layers = layers; |
192 | 191 |
|
|
200 | 199 | function onEditLayerNameChange(listing: LayerListingInfo, e: Event) { |
201 | 200 | // Eliminate duplicate events |
202 | 201 | if (!listing.editingName) return; |
203 | | -
|
204 | | - draggable = true; |
205 | 202 | listing.editingName = false; |
206 | 203 | layers = layers; |
207 | 204 |
|
|
211 | 208 | } |
212 | 209 |
|
213 | 210 | async function onEditLayerNameDeselect(listing: LayerListingInfo) { |
214 | | - draggable = true; |
215 | 211 | listing.editingName = false; |
216 | 212 | layers = layers; |
217 | 213 |
|
|
371 | 367 | }; |
372 | 368 | } |
373 | 369 |
|
374 | | - async function dragStart(event: DragEvent, listing: LayerListingInfo) { |
| 370 | + function handlePointerDown(event: PointerEvent, listing: LayerListingInfo) { |
| 371 | + // Only handle primary button (left mouse button) |
| 372 | + if (event.button !== 0) return; |
| 373 | +
|
375 | 374 | const layer = listing.entry; |
376 | | - dragInPanel = true; |
377 | 375 | if (!$nodeGraph.selected.includes(layer.id)) { |
378 | 376 | fakeHighlightOfNotYetSelectedLayerBeingDragged = layer.id; |
379 | 377 | } |
380 | 378 | const select = () => { |
381 | 379 | if (!$nodeGraph.selected.includes(layer.id)) selectLayer(listing, false, false); |
382 | 380 | }; |
383 | 381 |
|
384 | | - const target = (event.target instanceof HTMLElement && event.target) || undefined; |
385 | | - const closest = target?.closest("[data-layer]") || undefined; |
386 | | - const draggingELement = (closest instanceof HTMLElement && closest) || undefined; |
387 | | - if (draggingELement) beginDraggingElement(draggingELement); |
388 | | -
|
389 | | - // Set style of cursor for drag |
390 | | - if (event.dataTransfer) { |
391 | | - event.dataTransfer.dropEffect = "move"; |
392 | | - event.dataTransfer.effectAllowed = "move"; |
393 | | - } |
| 382 | + // Store initial drag position and select function for later use |
| 383 | + dragStartPosition = { x: event.clientX, y: event.clientY }; |
| 384 | + draggingData = { select, insertParentId: undefined, insertDepth: 0, insertIndex: undefined, highlightFolder: false, markerHeight: 0 }; |
394 | 385 |
|
395 | | - if (list) draggingData = calculateDragIndex(list, event.clientY, select); |
| 386 | + // Capture pointer to receive move events |
| 387 | + (event.target as HTMLElement)?.setPointerCapture(event.pointerId); |
396 | 388 | } |
397 | 389 |
|
398 | | - function updateInsertLine(event: DragEvent) { |
399 | | - if (!draggable) return; |
| 390 | + function updateInsertLine(event: PointerEvent) { |
| 391 | + // If not dragging yet, check if we should start dragging |
| 392 | + if (!isDragging && event.buttons === 1 && draggingData) { |
| 393 | + const deltaX = event.clientX - dragStartPosition.x; |
| 394 | + const deltaY = event.clientY - dragStartPosition.y; |
| 395 | + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); |
| 396 | +
|
| 397 | + if (distance > dragThreshold) { |
| 398 | + isDragging = true; |
| 399 | + dragInPanel = true; |
| 400 | + event.preventDefault(); |
| 401 | + } |
| 402 | + } |
| 403 | +
|
| 404 | + if (!isDragging) return; |
400 | 405 |
|
401 | | - // Stop the drag from being shown as cancelled |
402 | 406 | event.preventDefault(); |
403 | 407 | dragInPanel = true; |
404 | 408 |
|
405 | 409 | if (list) draggingData = calculateDragIndex(list, event.clientY, draggingData?.select); |
406 | 410 | } |
407 | 411 |
|
408 | | - function drop(e: DragEvent) { |
| 412 | + function handlePointerUp(event: PointerEvent) { |
| 413 | + const wasDragging = isDragging; |
| 414 | +
|
| 415 | + // If we were dragging, complete the move operation |
| 416 | + if (isDragging && draggingData) { |
| 417 | + const { select, insertParentId, insertIndex } = draggingData; |
| 418 | +
|
| 419 | + // Complete the layer move |
| 420 | + select?.(); |
| 421 | + editor.handle.moveLayerInTree(insertParentId, insertIndex); |
| 422 | + } |
| 423 | +
|
| 424 | + // Cleanup state |
| 425 | + draggingData = undefined; |
| 426 | + fakeHighlightOfNotYetSelectedLayerBeingDragged = undefined; |
| 427 | + isDragging = false; |
| 428 | + dragInPanel = false; |
| 429 | +
|
| 430 | + // Release pointer capture |
| 431 | + if (wasDragging) { |
| 432 | + try { |
| 433 | + (event.target as HTMLElement)?.releasePointerCapture(event.pointerId); |
| 434 | + } catch { |
| 435 | + // Ignore errors - pointer might not be captured |
| 436 | + } |
| 437 | + } |
| 438 | + } |
| 439 | +
|
| 440 | + function handleDrop(e: DragEvent) { |
409 | 441 | if (!draggingData) return; |
410 | | - const { select, insertParentId, insertIndex } = draggingData; |
| 442 | + const { insertParentId, insertIndex } = draggingData; |
411 | 443 |
|
412 | 444 | e.preventDefault(); |
413 | 445 |
|
414 | | - if (e.dataTransfer) { |
415 | | - // Moving layers |
416 | | - if (e.dataTransfer.items.length === 0) { |
417 | | - if (draggable && dragInPanel) { |
418 | | - select?.(); |
419 | | - editor.handle.moveLayerInTree(insertParentId, insertIndex); |
| 446 | + if (e.dataTransfer && e.dataTransfer.items.length > 0) { |
| 447 | + // Handle file imports |
| 448 | + Array.from(e.dataTransfer.items).forEach(async (item) => { |
| 449 | + const file = item.getAsFile(); |
| 450 | + if (!file) return; |
| 451 | +
|
| 452 | + if (file.type.includes("svg")) { |
| 453 | + const svgData = await file.text(); |
| 454 | + editor.handle.pasteSvg(file.name, svgData, undefined, undefined, insertParentId, insertIndex); |
| 455 | + return; |
420 | 456 | } |
421 | | - } |
422 | | - // Importing files |
423 | | - else { |
424 | | - Array.from(e.dataTransfer.items).forEach(async (item) => { |
425 | | - const file = item.getAsFile(); |
426 | | - if (!file) return; |
427 | | -
|
428 | | - if (file.type.includes("svg")) { |
429 | | - const svgData = await file.text(); |
430 | | - editor.handle.pasteSvg(file.name, svgData, undefined, undefined, insertParentId, insertIndex); |
431 | | - return; |
432 | | - } |
433 | 457 |
|
434 | | - if (file.type.startsWith("image")) { |
435 | | - const imageData = await extractPixelData(file); |
436 | | - editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, undefined, undefined, insertParentId, insertIndex); |
437 | | - return; |
438 | | - } |
| 458 | + if (file.type.startsWith("image")) { |
| 459 | + const imageData = await extractPixelData(file); |
| 460 | + editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, undefined, undefined, insertParentId, insertIndex); |
| 461 | + return; |
| 462 | + } |
439 | 463 |
|
440 | | - // When we eventually have sub-documents, this should be changed to import the document instead of opening it in a separate tab |
441 | | - const graphiteFileSuffix = "." + editor.handle.fileExtension(); |
442 | | - if (file.name.endsWith(graphiteFileSuffix)) { |
443 | | - const content = await file.text(); |
444 | | - const documentName = file.name.slice(0, -graphiteFileSuffix.length); |
445 | | - editor.handle.openDocumentFile(documentName, content); |
446 | | - return; |
447 | | - } |
448 | | - }); |
449 | | - } |
| 464 | + // When we eventually have sub-documents, this should be changed to import the document instead of opening it in a separate tab |
| 465 | + const graphiteFileSuffix = "." + editor.handle.fileExtension(); |
| 466 | + if (file.name.endsWith(graphiteFileSuffix)) { |
| 467 | + const content = await file.text(); |
| 468 | + const documentName = file.name.slice(0, -graphiteFileSuffix.length); |
| 469 | + editor.handle.openDocumentFile(documentName, content); |
| 470 | + return; |
| 471 | + } |
| 472 | + }); |
450 | 473 | } |
451 | | -
|
452 | | - draggingData = undefined; |
453 | | - fakeHighlightOfNotYetSelectedLayerBeingDragged = undefined; |
454 | | - dragInPanel = false; |
455 | 474 | } |
456 | 475 |
|
457 | 476 | function rebuildLayerHierarchy(updateDocumentLayerStructure: DocumentLayerStructure) { |
|
509 | 528 | data-layer-panel |
510 | 529 | bind:this={list} |
511 | 530 | on:click={() => deselectAllLayers()} |
512 | | - on:dragover={updateInsertLine} |
513 | | - on:dragend={drop} |
514 | | - on:drop={drop} |
| 531 | + on:pointermove={updateInsertLine} |
| 532 | + on:pointerup={handlePointerUp} |
| 533 | + on:pointercancel={handlePointerUp} |
| 534 | + on:pointerleave={() => (dragInPanel = false)} |
| 535 | + on:drop={handleDrop} |
515 | 536 | > |
516 | 537 | {#each layers as listing, index} |
517 | 538 | {@const selected = fakeHighlightOfNotYetSelectedLayerBeingDragged !== undefined ? fakeHighlightOfNotYetSelectedLayerBeingDragged === listing.entry.id : listing.entry.selected} |
|
528 | 549 | data-layer |
529 | 550 | data-index={index} |
530 | 551 | tooltip={listing.entry.tooltip} |
531 | | - {draggable} |
532 | | - on:dragstart={(e) => draggable && dragStart(e, listing)} |
| 552 | + on:pointerdown={(e) => handlePointerDown(e, listing)} |
533 | 553 | on:click={(e) => selectLayerWithModifiers(e, listing)} |
534 | 554 | > |
535 | 555 | {#if listing.entry.childrenAllowed} |
|
0 commit comments