Modules (188)

DragAndDrop

Description

Dependencies

Functions

Public API

attachHandlers

Attaches global drag & drop handlers to this window. This enables dropping files/folders to open them, and also protects the Brackets app from being replaced by the browser trying to load the dropped file in its place.

    function attachHandlers() {

        function handleDragOver(event) {
            event = event.originalEvent || event;

            var files = event.dataTransfer.files;

            stopURIListPropagation(files, event);

            if (files && files.length) {
                event.stopPropagation();
                event.preventDefault();

                var dropEffect = "none";

                // Don't allow drag-and-drop of files/folders when a modal dialog is showing.
                if ($(".modal.instance").length === 0 && isValidDrop(event.dataTransfer.items)) {
                    dropEffect = "copy";
                }
                event.dataTransfer.dropEffect = dropEffect;
            }
        }

        function handleDrop(event) {
            event = event.originalEvent || event;

            var files = event.dataTransfer.files;

            stopURIListPropagation(files, event);

            if (files && files.length) {
                event.stopPropagation();
                event.preventDefault();

                brackets.app.getDroppedFiles(function (err, paths) {
                    if (!err) {
                        openDroppedFiles(paths);
                    }
                });
            }
        }

        // For most of the window, only respond if nothing more specific in the UI has already grabbed the event (e.g.
        // the Extension Manager drop-to-install zone, or an extension with a drop-to-upload zone in its panel)
        $(window.document.body)
            .on("dragover", handleDragOver)
            .on("drop", handleDrop);

        // Over CodeMirror specifically, always pre-empt CodeMirror's drag event handling if files are being dragged - CM stops
        // propagation on any drag event it sees, even when it's not a text drag/drop. But allow CM to handle all non-file drag
        // events. See bug #10617.
        window.document.body.addEventListener("dragover", function (event) {
            if ($(event.target).closest(".CodeMirror").length) {
                handleDragOver(event);
            }
        }, true);
        window.document.body.addEventListener("drop", function (event) {
            if ($(event.target).closest(".CodeMirror").length) {
                handleDrop(event);
            }
        }, true);
    }


    CommandManager.register(Strings.CMD_OPEN_DROPPED_FILES, Commands.FILE_OPEN_DROPPED_FILES, openDroppedFiles);

    // Export public API
    exports.attachHandlers      = attachHandlers;
    exports.isValidDrop         = isValidDrop;
    exports.openDroppedFiles    = openDroppedFiles;
});
Public API

isValidDrop

Returns true if the drag and drop items contains valid drop objects.

items Array.<DataTransferItem>
Array of items being dragged
Returns: boolean
True if one or more items can be dropped.
    function isValidDrop(items) {
        var i, len = items.length;

        for (i = 0; i < len; i++) {
            if (items[i].kind === "file") {
                var entry = items[i].webkitGetAsEntry();

                if (entry.isFile) {
                    // If any files are being dropped, this is a valid drop
                    return true;
                } else if (len === 1) {
                    // If exactly one folder is being dropped, this is a valid drop
                    return true;
                }
            }
        }

        // No valid entries found
        return false;
    }
Public API

openDroppedFiles

Open dropped files

files Array.<string>
Array of files dropped on the application.
Returns: Promise
Promise that is resolved if all files are opened, or rejected if there was an error.
    function openDroppedFiles(paths) {
        var errorFiles = [],
            ERR_MULTIPLE_ITEMS_WITH_DIR = {};

        return Async.doInParallel(paths, function (path, idx) {
            var result = new $.Deferred();

            // Only open files.
            FileSystem.resolve(path, function (err, item) {
                if (!err && item.isFile) {
                    // If the file is already open, and this isn't the last
                    // file in the list, return. If this *is* the last file,
                    // always open it so it gets selected.
                    if (idx < paths.length - 1) {
                        if (MainViewManager.findInWorkingSet(MainViewManager.ALL_PANES, path) !== -1) {
                            result.resolve();
                            return;
                        }
                    }

                    CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN,
                                           {fullPath: path, silent: true})
                        .done(function () {
                            result.resolve();
                        })
                        .fail(function (openErr) {
                            errorFiles.push({path: path, error: openErr});
                            result.reject();
                        });
                } else if (!err && item.isDirectory && paths.length === 1) {
                    // One folder was dropped, open it.
                    ProjectManager.openProject(path)
                        .done(function () {
                            result.resolve();
                        })
                        .fail(function () {
                            // User was already notified of the error.
                            result.reject();
                        });
                } else {
                    errorFiles.push({path: path, error: err || ERR_MULTIPLE_ITEMS_WITH_DIR});
                    result.reject();
                }
            });

            return result.promise();
        }, false)
            .fail(function () {
                function errorToString(err) {
                    if (err === ERR_MULTIPLE_ITEMS_WITH_DIR) {
                        return Strings.ERROR_MIXED_DRAGDROP;
                    } else {
                        return FileUtils.getFileErrorString(err);
                    }
                }

                if (errorFiles.length > 0) {
                    var message = Strings.ERROR_OPENING_FILES;

                    message += "<ul class='dialog-list'>";
                    errorFiles.forEach(function (info) {
                        message += "<li><span class='dialog-filename'>" +
                            StringUtils.breakableUrl(ProjectManager.makeProjectRelativeIfPossible(info.path)) +
                            "</span> - " + errorToString(info.error) +
                            "</li>";
                    });
                    message += "</ul>";

                    Dialogs.showModalDialog(
                        DefaultDialogs.DIALOG_ID_ERROR,
                        Strings.ERROR_OPENING_FILE_TITLE,
                        message
                    );
                }
            });
    }

stopURIListPropagation

Determines if the event contains a type list that has a URI-list. If it does and contains an empty file list, then what is being dropped is a URL. If that is true then we stop the event propagation and default behavior to save Brackets editor from the browser taking over.

files Array.<File>
Array of File objects from the event datastructure. URLs are the only drop item that would contain a URI-list.
event event
The event datastucture containing datatransfer information about the drag/drop event. Contains a type list which may or may not hold a URI-list depending on what was dragged/dropped. Interested if it does.
    function stopURIListPropagation(files, event) {
        var types = event.dataTransfer.types;
            
        if ((!files || !files.length) && types) { // We only want to check if a string of text was dragged into the editor
            types.forEach(function (value) {
                //Dragging text externally (dragging text from another file): types has "text/plain" and "text/html"
                //Dragging text internally (dragging text to another line): types has just "text/plain"
                //Dragging a file: types has "Files"
                //Dragging a url: types has "text/plain" and "text/uri-list" <-what we are interested in
                if (value === "text/uri-list") { 
                    event.stopPropagation();
                    event.preventDefault();
                    return;
                }
            });
        }
    }