mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 17:58:13 +08:00
Make the dropdown widget's file drop a callback and keep stray file drops from navigating the browser away (#4553)
* Make the dropdown widget's file drop a callback and keep stray file drops from navigating the browser away * Look up the dropped file's widget before reading the file
This commit is contained in:
@@ -70,6 +70,15 @@
|
||||
editor.widgetValueDragDrop(layoutTarget, widgets[widgetIndex].widgetId);
|
||||
}
|
||||
|
||||
async function widgetValueFileDrop(widgetIndex: number, file: File) {
|
||||
// The layout may change while the file is read, so the widget is looked up first
|
||||
const target = layoutTarget;
|
||||
const widgetId = widgets[widgetIndex].widgetId;
|
||||
|
||||
const data = await file.bytes();
|
||||
editor.widgetValueFileDrop(target, widgetId, file.name, file.type, data);
|
||||
}
|
||||
|
||||
// Extracts the kind and props from a Widget tagged enum, validated against the widget registry.
|
||||
// The overload declares the precise correlated return type while the implementation uses broader types.
|
||||
function unwrapWidget(widgetInstance: WidgetInstance): UnwrappedWidget | undefined;
|
||||
@@ -165,9 +174,7 @@
|
||||
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);
|
||||
},
|
||||
fileDrop: (e: CustomEvent<File>) => widgetValueFileDrop(index, e.detail),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -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, IngestAction } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { MenuListEntry, ActionShortcut } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const DASH_ENTRY: MenuListEntry = {
|
||||
value: "",
|
||||
@@ -35,7 +35,7 @@
|
||||
// Behavior
|
||||
export let virtualScrolling = false;
|
||||
export let interactive = true;
|
||||
export let fileDropAction: IngestAction | undefined = undefined;
|
||||
export let takesFileDrop = false;
|
||||
// Sizing
|
||||
export let minWidth = 0;
|
||||
export let maxWidth = 0;
|
||||
@@ -117,7 +117,7 @@
|
||||
}
|
||||
|
||||
function takesDraggedFile(e: DragEvent): boolean {
|
||||
return Boolean(fileDropAction) && !disabled && Boolean(e.dataTransfer?.types.includes("Files"));
|
||||
return takesFileDrop && !disabled && Boolean(e.dataTransfer?.types.includes("Files"));
|
||||
}
|
||||
|
||||
function fileDragOverWidget(e: DragEvent) {
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
onModifyInputField,
|
||||
onFocusOut,
|
||||
onContextMenu,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
onPaste,
|
||||
onPointerLockChange,
|
||||
updateDirectInput,
|
||||
@@ -45,6 +47,8 @@ const listeners: Listener[] = [
|
||||
{ target: window, eventName: "wheel", action: (e: WheelEvent) => editorWrapper && onWheelScroll(e, editorWrapper), options: { passive: false } },
|
||||
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => editorWrapper && onModifyInputField(e, editorWrapper) },
|
||||
{ target: window, eventName: "focusout", action: () => onFocusOut() },
|
||||
{ target: window, eventName: "dragover", action: (e: DragEvent) => onDragOver(e) },
|
||||
{ target: window, eventName: "drop", action: (e: DragEvent) => onDrop(e) },
|
||||
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => onContextMenu(e) },
|
||||
{ target: window.document, eventName: "fullscreenchange", action: () => fullscreenModeChanged() },
|
||||
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent) => editorWrapper && onPaste(e, editorWrapper) },
|
||||
|
||||
@@ -293,6 +293,23 @@ export function onPaste(e: ClipboardEvent, editor: EditorWrapper) {
|
||||
});
|
||||
}
|
||||
|
||||
// A spot that takes dropped files has canceled the event by the time it bubbles up to the window
|
||||
function isUnclaimedFileDrag(e: DragEvent): boolean {
|
||||
return !e.defaultPrevented && Boolean(e.dataTransfer?.types.includes("Files"));
|
||||
}
|
||||
|
||||
export function onDragOver(e: DragEvent) {
|
||||
if (!isUnclaimedFileDrag(e)) return;
|
||||
|
||||
// Refusing the drop here keeps the browser from navigating away to open the file
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = "none";
|
||||
}
|
||||
|
||||
export function onDrop(e: DragEvent) {
|
||||
if (isUnclaimedFileDrag(e)) e.preventDefault();
|
||||
}
|
||||
|
||||
export function onFocusOut() {
|
||||
canvasFocused = false;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ mod editor_commands {
|
||||
use editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport;
|
||||
use editor::messages::portfolio::utility_types::PanelGroupId;
|
||||
use editor::messages::prelude::*;
|
||||
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
||||
use editor::messages::tool::tool_messages::tool_prelude::{DroppedFile, WidgetId};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
@@ -176,6 +176,13 @@ mod editor_commands {
|
||||
LayoutMessage::WidgetValueDragDrop { layout_target, widget_id }.into()
|
||||
}
|
||||
|
||||
/// Hand a file dropped on a UI widget to the widget's file drop callback
|
||||
fn widget_value_file_drop(layout_target: LayoutTarget, widget_id: u64, name: String, mime_type: String, data: Vec<u8>) -> Message {
|
||||
let widget_id = WidgetId(widget_id);
|
||||
let file = DroppedFile { name, mime_type, data };
|
||||
LayoutMessage::WidgetValueFileDrop { layout_target, widget_id, file }.into()
|
||||
}
|
||||
|
||||
/// Closes out the current transaction (drag-end / text-commit end), so emits during a slider drag collapse into one history step instead of N
|
||||
fn end_transaction() -> Message {
|
||||
DocumentMessage::EndTransaction.into()
|
||||
@@ -597,7 +604,7 @@ mod editor_commands {
|
||||
ClipboardMessage::ReadSelection { content, cut }.into()
|
||||
}
|
||||
|
||||
/// A file headed for a known action, either picked in the dialog that `TriggerBrowse` opened or dropped onto a widget that takes files
|
||||
/// A file headed for a known action, picked in the dialog that `TriggerBrowse` opened
|
||||
fn ingest_picked(name: String, mime_type: String, data: Vec<u8>, action: IngestAction) -> Message {
|
||||
IngestMessage::Ingest {
|
||||
data,
|
||||
|
||||
Reference in New Issue
Block a user