Files
whale-town-front-v2/_Core/utils/WebFilePicker.gd

124 lines
3.8 KiB
GDScript

extends RefCounted
var _javascript_callback: Variant
var _result_callback: Callable
var _callback_name: String = ""
func open(accept: String, max_bytes: int, result_callback: Callable) -> bool:
if OS.get_name() != "Web" or not Engine.has_singleton("JavaScriptBridge"):
return false
if _result_callback.is_valid():
return false
var javascript_bridge: Object = Engine.get_singleton("JavaScriptBridge")
_callback_name = "__whaletownFilePicker%d" % get_instance_id()
_result_callback = result_callback
_javascript_callback = javascript_bridge.create_callback(_on_javascript_result)
var window: Variant = javascript_bridge.get_interface("window")
if window == null:
_reset()
return false
window[_callback_name] = _javascript_callback
var script := """
(() => {
const callbackName = %s;
const callback = window[callbackName];
if (typeof callback !== "function") return false;
const previous = document.getElementById("whaletown-web-file-picker");
if (previous) previous.remove();
const input = document.createElement("input");
input.id = "whaletown-web-file-picker";
input.type = "file";
input.accept = %s;
input.style.display = "none";
let finished = false;
const finish = (status, name, mime, payload) => {
if (finished) return;
finished = true;
callback(status, name || "", mime || "", payload || "");
input.remove();
delete window[callbackName];
};
input.addEventListener("cancel", () => finish("cancel", "", "", ""));
input.addEventListener("change", () => {
const file = input.files && input.files[0];
if (!file) {
finish("cancel", "", "", "");
return;
}
if (file.size > %d) {
finish("too_large", file.name, file.type, "");
return;
}
const reader = new FileReader();
reader.onerror = () => finish("read_error", file.name, file.type, "");
reader.onload = () => {
const bytes = new Uint8Array(reader.result);
const chunks = [];
const chunkSize = 0x8000;
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
chunks.push(String.fromCharCode(...bytes.subarray(offset, offset + chunkSize)));
}
finish("ok", file.name, file.type, btoa(chunks.join("")));
};
reader.readAsArrayBuffer(file);
});
document.body.appendChild(input);
input.click();
return true;
})()
""" % [JSON.stringify(_callback_name), JSON.stringify(accept), max_bytes]
var opened: Variant = javascript_bridge.eval(script, true)
if opened != true:
_reset()
return false
return true
func _on_javascript_result(arguments: Array) -> void:
var callback := _result_callback
_reset()
if not callback.is_valid():
return
var status := str(arguments[0]) if arguments.size() > 0 else "read_error"
var file_name := str(arguments[1]) if arguments.size() > 1 else ""
var mime_type := str(arguments[2]) if arguments.size() > 2 else ""
var base64_data := str(arguments[3]) if arguments.size() > 3 else ""
callback.call(status, file_name, mime_type, base64_data)
func _reset() -> void:
_javascript_callback = null
_result_callback = Callable()
_callback_name = ""
static func save_base64_file(base64_data: String, file_name: String, prefix: String) -> String:
if base64_data.is_empty():
return ""
var bytes := Marshalls.base64_to_raw(base64_data)
if bytes.is_empty():
return ""
var extension := file_name.get_extension().to_lower()
if not ["png", "jpg", "jpeg", "webp"].has(extension):
extension = "bin"
var root := DirAccess.open("user://")
if root == null:
return ""
if not root.dir_exists("web_uploads") and root.make_dir("web_uploads") != OK:
return ""
var safe_prefix := prefix.to_lower().replace(" ", "_")
var path := "user://web_uploads/%s_%d.%s" % [safe_prefix, Time.get_ticks_msec(), extension]
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
return ""
file.store_buffer(bytes)
file.close()
return path