Add drag-and-drop and copy-paste file importing/opening throughout the UI (#2012)

* Add file importing by dragging and dropping throughout the UI

* Disable comment-profiling-changes.yaml

* Fix CI
This commit is contained in:
Keavon Chambers
2024-09-28 00:19:43 -07:00
committed by GitHub
parent 20470b566b
commit 904cf09c79
35 changed files with 578 additions and 259 deletions
@@ -42,6 +42,7 @@
on:dragleave
on:dragover
on:dragstart
on:drop
on:mouseup
on:pointerdown
on:pointerenter
@@ -58,7 +59,6 @@ on:copy
on:cut
on:drag
on:dragenter
on:drop
on:focus
on:fullscreenchange
on:fullscreenerror
@@ -42,6 +42,7 @@
on:dragleave
on:dragover
on:dragstart
on:drop
on:mouseup
on:pointerdown
on:pointerenter
@@ -58,7 +59,6 @@ on:copy
on:cut
on:drag
on:dragenter
on:drop
on:focus
on:fullscreenchange
on:fullscreenerror
+18 -8
View File
@@ -118,23 +118,33 @@
};
})($document.toolShelfLayout.layout[0]);
function pasteFile(e: DragEvent) {
function dropFile(e: DragEvent) {
const { dataTransfer } = e;
const [x, y] = e.target instanceof Element && e.target.closest("[data-viewport]") ? [e.clientX, e.clientY] : [undefined, undefined];
if (!dataTransfer) return;
e.preventDefault();
Array.from(dataTransfer.items).forEach(async (item) => {
const file = item.getAsFile();
if (file?.type.includes("svg")) {
const svgData = await file.text();
editor.handle.pasteSvg(svgData, e.clientX, e.clientY);
if (!file) return;
if (file.type.includes("svg")) {
const svgData = await file.text();
editor.handle.pasteSvg(file.name, svgData, x, y);
return;
}
if (file?.type.startsWith("image")) {
if (file.type.startsWith("image")) {
const imageData = await extractPixelData(file);
editor.handle.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height, e.clientX, e.clientY);
editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, x, y);
return;
}
if (file.name.endsWith(".graphite")) {
const content = await file.text();
editor.handle.openDocumentFile(file.name, content);
return;
}
});
}
@@ -426,7 +436,7 @@
});
</script>
<LayoutCol class="document">
<LayoutCol class="document" on:dragover={(e) => e.preventDefault()} on:drop={dropFile}>
<LayoutRow class="options-bar" classes={{ "for-graph": $document.graphViewOverlayOpen }} scrollableX={true}>
{#if !$document.graphViewOverlayOpen}
<WidgetLayout layout={$document.documentModeLayout} />
@@ -482,7 +492,7 @@
y={cursorTop}
/>
{/if}
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} on:dragover={(e) => e.preventDefault()} on:drop={(e) => pasteFile(e)} bind:this={viewport} data-viewport>
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
{@html artworkSvg}
</svg>
+44 -6
View File
@@ -4,6 +4,7 @@
import { beginDraggingElement } from "@graphite/io-managers/drag";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import { platformIsMac } from "@graphite/utility-functions/platform";
import { extractPixelData } from "@graphite/utility-functions/rasterization";
import type { Editor } from "@graphite/wasm-communication/editor";
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerStructureJs, UpdateLayersPanelOptionsLayout } from "@graphite/wasm-communication/messages";
import type { DataBuffer, LayerPanelEntry } from "@graphite/wasm-communication/messages";
@@ -305,6 +306,8 @@
}
function updateInsertLine(event: DragEvent) {
if (!draggable) return;
// Stop the drag from being shown as cancelled
event.preventDefault();
dragInPanel = true;
@@ -312,13 +315,48 @@
if (list) draggingData = calculateDragIndex(list, event.clientY, draggingData?.select);
}
async function drop() {
if (draggingData && dragInPanel) {
const { select, insertParentId, insertIndex } = draggingData;
function drop(e: DragEvent) {
if (!draggingData) return;
const { select, insertParentId, insertIndex } = draggingData;
select?.();
editor.handle.moveLayerInTree(insertParentId, insertIndex);
e.preventDefault();
if (e.dataTransfer) {
// Moving layers
if (e.dataTransfer.items.length === 0) {
if (draggable && dragInPanel) {
select?.();
editor.handle.moveLayerInTree(insertParentId, insertIndex);
}
}
// Importing files
else {
Array.from(e.dataTransfer.items).forEach(async (item) => {
const file = item.getAsFile();
if (!file) return;
if (file.type.includes("svg")) {
const svgData = await file.text();
editor.handle.pasteSvg(file.name, svgData, undefined, undefined, insertParentId, insertIndex);
return;
}
if (file.type.startsWith("image")) {
const imageData = await extractPixelData(file);
editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, undefined, undefined, insertParentId, insertIndex);
return;
}
// When we eventually have sub-documents, this should be changed to import the document instead of opening it in a separate tab
if (file.name.endsWith(".graphite")) {
const content = await file.text();
editor.handle.openDocumentFile(file.name, content);
return;
}
});
}
}
draggingData = undefined;
fakeHighlight = undefined;
dragInPanel = false;
@@ -369,7 +407,7 @@
<WidgetLayout layout={layersPanelOptionsLayout} />
</LayoutRow>
<LayoutRow class="list-area" scrollableY={true}>
<LayoutCol class="list" data-layer-panel bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
<LayoutCol class="list" data-layer-panel bind:this={list} on:click={() => deselectAllLayers()} on:dragover={updateInsertLine} on:dragend={drop} on:drop={drop}>
{#each layers as listing, index}
<LayoutRow
class="layer"
@@ -2,8 +2,6 @@
import Document from "@graphite/components/panels/Document.svelte";
import Layers from "@graphite/components/panels/Layers.svelte";
import Properties from "@graphite/components/panels/Properties.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
const PANEL_COMPONENTS = {
Document,
@@ -18,11 +16,14 @@
import { platformIsMac, isEventSupported } from "@graphite/utility-functions/platform";
import { extractPixelData } from "@graphite/utility-functions/rasterization";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type LayoutKeysGroup, type Key } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
@@ -50,6 +51,35 @@
return reservedKey ? [CONTROL, ALT] : [CONTROL];
}
function dropFile(e: DragEvent) {
if (!e.dataTransfer) return;
e.preventDefault();
Array.from(e.dataTransfer.items).forEach(async (item) => {
const file = item.getAsFile();
if (!file) return;
if (file.type.includes("svg")) {
const svgData = await file.text();
editor.handle.pasteSvg(file.name, svgData);
return;
}
if (file.type.startsWith("image")) {
const imageData = await extractPixelData(file);
editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height);
return;
}
if (file.name.endsWith(".graphite")) {
const content = await file.text();
editor.handle.openDocumentFile(file.name, content);
return;
}
});
}
export async function scrollTabIntoView(newIndex: number) {
await tick();
tabElements[newIndex]?.div?.()?.scrollIntoView();
@@ -76,7 +106,7 @@
}
}}
on:mouseup={(e) => {
// Fallback for Safari:
// Middle mouse button click fallback for Safari:
// https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event#browser_compatibility
// The downside of using mouseup is that the mousedown didn't have to originate in the same element.
// A possible future improvement could save the target element during mousedown and check if it's the same here.
@@ -110,7 +140,7 @@
{#if panelType}
<svelte:component this={PANEL_COMPONENTS[panelType]} />
{:else}
<LayoutCol class="empty-panel">
<LayoutCol class="empty-panel" on:dragover={(e) => e.preventDefault()} on:drop={dropFile}>
<LayoutCol class="content">
<LayoutRow class="logotype">
<IconLabel icon="GraphiteLogotypeSolid" />