瀏覽代碼

Add drop files function

Fabio Alessandrelli 5 年之前
父節點
當前提交
6a49b83e39

+ 29 - 2
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) {

+ 1 - 0
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!");

+ 1 - 0
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 */
 

+ 156 - 0
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);
+			});
+		});
+	}
+})();