diff options
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 31 | ||||
-rw-r--r-- | platform/javascript/display_server_javascript.h | 1 | ||||
-rw-r--r-- | platform/javascript/javascript_main.cpp | 1 | ||||
-rw-r--r-- | platform/javascript/native/utils.js | 156 |
4 files changed, 187 insertions, 2 deletions
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 103bbf4ead..060e446fca 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -82,6 +82,25 @@ EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, co return false; } +// Drag and drop callback (see native/utils.js). +extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) { + DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); + if (!ds) { + ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active"); + } + if (ds->drop_files_callback.is_null()) + return; + Vector<String> files; + for (int i = 0; i < p_filec; i++) { + files.push_back(String::utf8(p_filev[i])); + } + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce); +} + // Keys template <typename T> @@ -911,11 +930,12 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive /* clang-format off */ EM_ASM_ARGS({ Module.listeners = {}; + const canvas = Module['canvas']; const send_window_event = cwrap('send_window_event', null, ['number']); const notifications = arguments; (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) { Module.listeners[event] = send_window_event.bind(null, notifications[index]); - Module['canvas'].addEventListener(event, Module.listeners[event]); + canvas.addEventListener(event, Module.listeners[event]); }); // Clipboard const update_clipboard = cwrap('update_clipboard', null, ['string']); @@ -923,6 +943,13 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive update_clipboard(evt.clipboardData.getData('text')); }; window.addEventListener('paste', Module.listeners['paste'], false); + Module.listeners['dragover'] = function(ev) { + // Prevent default behavior (which would try to open the file(s)) + ev.preventDefault(); + }; + Module.listeners['drop'] = Module.drop_handler; // Defined in native/utils.js + canvas.addEventListener('dragover', Module.listeners['dragover'], false); + canvas.addEventListener('drop', Module.listeners['drop'], false); }, WINDOW_EVENT_MOUSE_ENTER, WINDOW_EVENT_MOUSE_EXIT, @@ -1044,7 +1071,7 @@ void DisplayServerJavaScript::window_set_input_text_callback(const Callable &p_c } void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { - // TODO this should be implemented. + drop_files_callback = p_callable; } void DisplayServerJavaScript::window_set_title(const String &p_title, WindowID p_window) { diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index 7bd0c2b535..73a7b017e6 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -91,6 +91,7 @@ public: Callable window_event_callback; Callable input_event_callback; Callable input_text_callback; + Callable drop_files_callback; // from DisplayServer virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 0ccdc0e13e..854383aeee 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -100,6 +100,7 @@ int main(int argc, char *argv[]) { FS.syncfs(true, function(err) { ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]) }); + ); /* clang-format on */ diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js index d45c6c3032..95585d26ae 100644 --- a/platform/javascript/native/utils.js +++ b/platform/javascript/native/utils.js @@ -46,3 +46,159 @@ Module['copyToFS'] = function(path, buffer) { FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'}); } +Module.drop_handler = (function() { + var upload = []; + var uploadPromises = []; + var uploadCallback = null; + + function readFilePromise(entry, path) { + return new Promise(function(resolve, reject) { + entry.file(function(file) { + var reader = new FileReader(); + reader.onload = function() { + var f = { + "path": file.relativePath || file.webkitRelativePath, + "name": file.name, + "type": file.type, + "size": file.size, + "data": reader.result + }; + if (!f['path']) + f['path'] = f['name']; + upload.push(f); + resolve() + }; + reader.onerror = function() { + console.log("Error reading file"); + reject(); + } + + reader.readAsArrayBuffer(file); + + }, function(err) { + console.log("Error!"); + reject(); + }); + }); + } + + function readDirectoryPromise(entry) { + return new Promise(function(resolve, reject) { + var reader = entry.createReader(); + reader.readEntries(function(entries) { + for (var i = 0; i < entries.length; i++) { + var ent = entries[i]; + if (ent.isDirectory) { + uploadPromises.push(readDirectoryPromise(ent)); + } else if (ent.isFile) { + uploadPromises.push(readFilePromise(ent)); + } + } + resolve(); + }); + }); + } + + function processUploadsPromises(resolve, reject) { + if (uploadPromises.length == 0) { + resolve(); + return; + } + uploadPromises.pop().then(function() { + setTimeout(function() { + processUploadsPromises(resolve, reject); + //processUploadsPromises.bind(null, resolve, reject) + }, 0); + }); + } + + function dropFiles(files) { + var args = files || []; + var argc = args.length; + var argv = stackAlloc((argc + 1) * 4); + for (var i = 0; i < argc; i++) { + HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i]); + } + HEAP32[(argv >> 2) + argc] = 0; + // Defined in display_server_javascript.cpp + ccall('_drop_files_callback', 'void', ['number', 'number'], [argv, argc]); + } + + return function(ev) { + ev.preventDefault(); + if (ev.dataTransfer.items) { + // Use DataTransferItemList interface to access the file(s) + for (var i = 0; i < ev.dataTransfer.items.length; i++) { + const item = ev.dataTransfer.items[i]; + var entry = null; + if ("getAsEntry" in item) { + entry = item.getAsEntry(); + } else if ("webkitGetAsEntry" in item) { + entry = item.webkitGetAsEntry(); + } + if (!entry) { + console.error("File upload not supported"); + } else if (entry.isDirectory) { + uploadPromises.push(readDirectoryPromise(entry)); + } else if (entry.isFile) { + uploadPromises.push(readFilePromise(entry)); + } else { + console.error("Unrecognized entry...", entry); + } + } + } else { + console.error("File upload not supported"); + } + uploadCallback = new Promise(processUploadsPromises).then(function() { + const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/"; + var drops = []; + var files = []; + upload.forEach((elem) => { + var path = elem['path']; + Module['copyToFS'](DROP + path, elem['data']); + var idx = path.indexOf("/"); + if (idx == -1) { + // Root file + drops.push(DROP + path); + } else { + // Subdir + var sub = path.substr(0, idx); + idx = sub.indexOf("/"); + if (idx < 0 && drops.indexOf(DROP + sub) == -1) { + drops.push(DROP + sub); + } + } + files.push(DROP + path); + }); + uploadPromises = []; + upload = []; + dropFiles(drops); + var dirs = [DROP.substr(0, DROP.length -1)]; + files.forEach(function (file) { + FS.unlink(file); + var dir = file.replace(DROP, ""); + var idx = dir.lastIndexOf("/"); + while (idx > 0) { + dir = dir.substr(0, idx); + if (dirs.indexOf(DROP + dir) == -1) { + dirs.push(DROP + dir); + } + idx = dir.lastIndexOf("/"); + } + }); + // Remove dirs. + dirs = dirs.sort(function(a, b) { + var al = (a.match(/\//g) || []).length; + var bl = (b.match(/\//g) || []).length; + if (al > bl) + return -1; + else if (al < bl) + return 1; + return 0; + }); + dirs.forEach(function(dir) { + FS.rmdir(dir); + }); + }); + } +})(); |