mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 15:28:11 +08:00
Add drag-and-drop support to the file picker dropdown widget (#4551)
Add drag-and-drop file ingest into file picker dropdowns
This commit is contained in:
@@ -165,6 +165,9 @@
|
||||
hoverInEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||
hoverOutEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||
selectedIndex: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true),
|
||||
fileDrop: async (e: CustomEvent<File>) => {
|
||||
if (props.fileDropAction) editor.ingestPicked(e.detail.name, e.detail.type, await e.detail.bytes(), props.fileDropAction);
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "/src/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
|
||||
import type { MenuListEntry, ActionShortcut } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { MenuListEntry, ActionShortcut, IngestAction } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const DASH_ENTRY: MenuListEntry = {
|
||||
value: "",
|
||||
@@ -19,7 +19,7 @@
|
||||
tooltipShortcut: undefined,
|
||||
};
|
||||
|
||||
const dispatch = createEventDispatcher<{ selectedIndex: number; hoverInEntry: number; hoverOutEntry: number }>();
|
||||
const dispatch = createEventDispatcher<{ selectedIndex: number; hoverInEntry: number; hoverOutEntry: number; fileDrop: File }>();
|
||||
|
||||
let self: LayoutRow | undefined;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
// Behavior
|
||||
export let virtualScrolling = false;
|
||||
export let interactive = true;
|
||||
export let fileDropAction: IngestAction | undefined = undefined;
|
||||
// Sizing
|
||||
export let minWidth = 0;
|
||||
export let maxWidth = 0;
|
||||
@@ -47,6 +48,7 @@
|
||||
let activeEntrySkipWatcher = false;
|
||||
let initialSelectedIndex: number | undefined = undefined;
|
||||
let open = false;
|
||||
let fileDragOver = false;
|
||||
|
||||
$: watchSelectedIndex(selectedIndex);
|
||||
$: watchEntries(entries);
|
||||
@@ -113,15 +115,49 @@
|
||||
const blurTarget = (e.target instanceof Element ? e.target.closest("[data-dropdown-input]") : undefined) || undefined;
|
||||
if (blurTarget !== self?.div?.()) open = false;
|
||||
}
|
||||
|
||||
function takesDraggedFile(e: DragEvent): boolean {
|
||||
return Boolean(fileDropAction) && !disabled && Boolean(e.dataTransfer?.types.includes("Files"));
|
||||
}
|
||||
|
||||
function fileDragOverWidget(e: DragEvent) {
|
||||
if (!takesDraggedFile(e)) return;
|
||||
|
||||
// The browser refuses the drop unless the dragover is canceled
|
||||
e.preventDefault();
|
||||
fileDragOver = true;
|
||||
}
|
||||
|
||||
function fileDragLeaveWidget(e: DragEvent) {
|
||||
// Moving between the widget's own children is not leaving it
|
||||
if (e.relatedTarget instanceof Node && self?.div?.()?.contains(e.relatedTarget)) return;
|
||||
|
||||
fileDragOver = false;
|
||||
}
|
||||
|
||||
function fileDropOnWidget(e: DragEvent) {
|
||||
if (!takesDraggedFile(e)) return;
|
||||
|
||||
// The drop is kept from also reaching a panel that imports dropped files
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
fileDragOver = false;
|
||||
|
||||
const file = e.dataTransfer?.files[0];
|
||||
if (file) dispatch("fileDrop", file);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutRow
|
||||
class="dropdown-input"
|
||||
classes={{ narrow }}
|
||||
classes={{ narrow, "file-drag-over": fileDragOver }}
|
||||
styles={{
|
||||
...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}),
|
||||
...(maxWidth > 0 ? { "max-width": `${maxWidth}px` } : {}),
|
||||
}}
|
||||
on:dragover={fileDragOverWidget}
|
||||
on:dragleave={fileDragLeaveWidget}
|
||||
on:drop={fileDropOnWidget}
|
||||
bind:this={self}
|
||||
data-dropdown-input
|
||||
>
|
||||
@@ -169,6 +205,15 @@
|
||||
--widget-height: 20px;
|
||||
}
|
||||
|
||||
&.file-drag-over::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border: 1px dashed var(--color-e-nearwhite);
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dropdown-box {
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -597,7 +597,7 @@ mod editor_commands {
|
||||
ClipboardMessage::ReadSelection { content, cut }.into()
|
||||
}
|
||||
|
||||
/// A file picked in the dialog that `TriggerBrowse` opened
|
||||
/// A file headed for a known action, either picked in the dialog that `TriggerBrowse` opened or dropped onto a widget that takes files
|
||||
fn ingest_picked(name: String, mime_type: String, data: Vec<u8>, action: IngestAction) -> Message {
|
||||
IngestMessage::Ingest {
|
||||
data,
|
||||
|
||||
Reference in New Issue
Block a user