diff --git a/src/app.h b/src/app.h index d8282ad..55ff6c4 100644 --- a/src/app.h +++ b/src/app.h @@ -167,7 +167,7 @@ public: bool clipboard_set_text(const std::string& s); void pick_image(std::function callback); void pick_file(std::vector types, std::function callback); -#if __IOS__ +#if __IOS__ || __WEB__ void pick_file_save(const std::string& type, std::function writer, std::function callback); #else diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index 3487b00..297d855 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -8,11 +8,15 @@ #include "node_about.h" #include "node_changelog.h" #include "node_usermanual.h" +#include "node_dialog_export_ppbr.h" #ifdef __QUEST__ #include "oculus_vr.h" +#elif __WEB__ +void webgl_pick_file(std::function callback); +void webgl_pick_file_save(const std::string& path, + const std::string& name, std::function callback); #endif -#include "node_dialog_export_ppbr.h" std::shared_ptr App::show_progress(const std::string& title, int total /*= 0*/) { @@ -435,7 +439,8 @@ void App::dialog_export(std::string ext) { // TODO: use picker auto path = work_path + "/" + doc_name + ext; - canvas->m_canvas->export_equirectangular(path, [this, path]{ + auto name = doc_name + ext; + canvas->m_canvas->export_equirectangular(path, [this, path, name]{ #if defined(__IOS__) message_box("Export Equirectangular", "Image exported to Photos"); #elif defined(__OSX__) @@ -444,6 +449,10 @@ void App::dialog_export(std::string ext) message_box("Export Equirectangular", "Image exported to " + work_path); #elif defined(__QUEST__) //auto result = ovr_Media_ShareToFacebook("Sharing from PanoPainter on Oculus Quest", path.c_str(), ovrMediaContentType_Photo); +#elif __WEB__ + ui_task([=]{ + webgl_pick_file_save(path, name, [](bool success){ }); + }); #endif }); } @@ -609,7 +618,7 @@ void App::dialog_ppbr_export() info.dest_path = dialog->m_dest_path; if (dialog->export_check) info.export_data = dialog->export_check->checked; -#if __IOS__ +#if __IOS__ || __WEB__ App::I->pick_file_save("ppbr", [this, dialog, info] (std::string path) { presets->export_ppbr(path, info); diff --git a/src/app_events.cpp b/src/app_events.cpp index 96f92ac..41351ea 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -19,6 +19,8 @@ std::string win32_clipboard_get_text(); #include #elif __WEB__ void webgl_pick_file(std::function callback); +void webgl_pick_file_save(const std::string& path, + const std::string& name, std::function callback); #endif @@ -235,8 +237,18 @@ void App::pick_file_save(const std::string& type, std::functiondestroy(); }; } +#elif __WEB__ +void App::pick_file_save(const std::string& type, std::function writer, + std::function callback) +{ + redraw = true; + auto path = data_path + "/file-save." + type; + LOG("App::pick_file_save %s", path.c_str()); + writer(path); + webgl_pick_file_save(path, "file." + type, callback); +} #else -void App::pick_file_save(std::vector types, std::function callback) +void App::pick_file_save(std::vector types, std::function callback) { redraw = true; #if __OSX__ diff --git a/webgl/src/main.cpp b/webgl/src/main.cpp index f1d7dd8..bf8301d 100644 --- a/webgl/src/main.cpp +++ b/webgl/src/main.cpp @@ -14,15 +14,20 @@ GLFWwindow* wnd; float theta = 0; glm::vec2 g_cursor_pos; +template class TaskCallback { - std::function fn; + std::function fn; public: template TaskCallback(T callback) : fn(callback) {} void call(const std::string& tmp_file_path) { fn(tmp_file_path); } + void call(bool success) + { + fn(success); + } TaskCallback() { printf("callback created\n"); @@ -33,36 +38,64 @@ public: } }; -void TaskCallback_call(uintptr_t tc, std::string tmp_file_path) +void TaskCallback_open_call(uintptr_t tc, std::string tmp_file_path) { - auto x = reinterpret_cast(tc); + auto x = reinterpret_cast*>(tc); x->call(tmp_file_path); } - -void TaskCallback_delete(uintptr_t tc) +void TaskCallback_open_delete(uintptr_t tc) { - auto x = reinterpret_cast(tc); + auto x = reinterpret_cast*>(tc); + delete x; +} +void TaskCallback_save_call(uintptr_t tc, bool success) +{ + auto x = reinterpret_cast*>(tc); + x->call(success); +} +void TaskCallback_save_delete(uintptr_t tc) +{ + auto x = reinterpret_cast*>(tc); delete x; } +std::string UtilityGetString(uintptr_t s) +{ + return *reinterpret_cast(s); +} + EMSCRIPTEN_BINDINGS(TaskCallback_bind) { - class_("TaskCallback"); - function("TaskCallback_call", &TaskCallback_call); - function("TaskCallback_delete", &TaskCallback_delete); + class_>("TaskCallbackOpen"); + class_>("TaskCallbackSave"); + function("TaskCallback_save_call", &TaskCallback_save_call); + function("TaskCallback_save_delete", &TaskCallback_save_delete); + function("TaskCallback_open_call", &TaskCallback_open_call); + function("TaskCallback_open_delete", &TaskCallback_open_delete); + function("UtilityGetString", &UtilityGetString); } extern "C" { -extern void js_pick_file(TaskCallback* tc); +extern void js_pick_file(TaskCallback* tc); +extern void js_pick_file_save(std::string path, std::string name, TaskCallback* tc); } void webgl_pick_file(std::function callback) { - js_pick_file(new TaskCallback([callback](std::string tmp_file_path){ + js_pick_file(new TaskCallback([callback](std::string tmp_file_path){ printf("callback called: %s\n", tmp_file_path.c_str()); callback(tmp_file_path); })); } +void webgl_pick_file_save(const std::string& path, + const std::string& name, std::function callback) +{ + js_pick_file_save(path, name, new TaskCallback([callback](bool success){ + printf("save callback called: %s\n", success ? "ok" : "failed"); + callback(success); + })); +} + void main_loop() { app.render_thread_tick(); diff --git a/webgl/src/mylib.js b/webgl/src/mylib.js index b57765b..9eccab1 100644 --- a/webgl/src/mylib.js +++ b/webgl/src/mylib.js @@ -15,14 +15,46 @@ function js_pick_file(fn) { console.log("reader.onload " + file.name); var content = new Uint8Array(readerEvent.target.result); // this is the content! console.log( content ); - FS.writeFile(file.name, content); - Module.TaskCallback_call(fn, file.name); - Module.TaskCallback_delete(fn); + FS.writeFile("/" + file.name, content); + Module.TaskCallback_open_call(fn, "/" + file.name); + Module.TaskCallback_open_delete(fn); } } input.click(); } +function js_pick_file_save(path, name, fn) { + jspath = Module.UtilityGetString(path); + jsname = Module.UtilityGetString(name); + console.log("js_pick_file_save"); + console.log(jspath); + file = FS.readFile(jspath); + blob = new Blob([file]); + console.log(file); + + // IE hack; + // see: http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx + if (window.navigator.msSaveOrOpenBlob) + { + window.navigator.msSaveBlob(blob, jsname); + } + else + { + var a = window.document.createElement("a"); + a.href = window.URL.createObjectURL(blob, {type: "application/octet-stream"}); + a.download = jsname; + document.body.appendChild(a); + // IE: "Access is denied"; + // see: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access + a.click(); + document.body.removeChild(a); + } + + Module.TaskCallback_save_call(fn, true); + Module.TaskCallback_save_delete(fn); +} + mergeInto(LibraryManager.library, { js_pick_file: js_pick_file, + js_pick_file_save: js_pick_file_save, });