yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_file_dialog.cc
Go to the documentation of this file.
1// clang-format off
2#ifdef __EMSCRIPTEN__
3
5
6#include <emscripten.h>
7#include <emscripten/html5.h>
8#include <memory>
9#include <unordered_map>
10
11#include "absl/strings/str_format.h"
12
13namespace yaze {
14namespace platform {
15
16// Static member initialization
17int WasmFileDialog::next_callback_id_ = 1;
18std::unordered_map<int, WasmFileDialog::PendingOperation> WasmFileDialog::pending_operations_;
19std::mutex WasmFileDialog::operations_mutex_;
20
21// JavaScript interop for file operations
22EM_JS(void, openFileDialog_impl, (const char* accept, int callback_id, bool is_text), {
23 var input = document.createElement('input');
24 input.type = 'file';
25 input.accept = UTF8ToString(accept);
26 input.style.display = 'none';
27 input.onchange = function(e) {
28 var file = e.target.files[0];
29 if (!file) {
30 var errPtr = allocateUTF8("No file selected");
31 Module._yazeHandleFileError(callback_id, errPtr);
32 _free(errPtr);
33 return;
34 }
35 var reader = new FileReader();
36 reader.onload = function() {
37 var filename = file.name;
38 var filenamePtr = allocateUTF8(filename);
39 if (is_text) {
40 var contentPtr = allocateUTF8(reader.result);
41 Module._yazeHandleTextFileLoaded(callback_id, filenamePtr, contentPtr);
42 _free(contentPtr);
43 } else {
44 var data = new Uint8Array(reader.result);
45 var dataPtr = Module._malloc(data.length);
46 Module.HEAPU8.set(data, dataPtr);
47 Module._yazeHandleFileLoaded(callback_id, filenamePtr, dataPtr, data.length);
48 Module._free(dataPtr);
49 }
50 _free(filenamePtr);
51 };
52 reader.onerror = function() {
53 var errPtr = allocateUTF8("Failed to read file");
54 Module._yazeHandleFileError(callback_id, errPtr);
55 _free(errPtr);
56 };
57 if (is_text) {
58 reader.readAsText(file);
59 } else {
60 reader.readAsArrayBuffer(file);
61 }
62 };
63 document.body.appendChild(input);
64 input.click();
65 setTimeout(function() { document.body.removeChild(input); }, 100);
66});
67
68EM_JS(void, downloadFile_impl, (const char* filename, const uint8_t* data, size_t size, const char* mime_type), {
69 var dataArray = HEAPU8.subarray(data, data + size);
70 var blob = new Blob([dataArray], { type: UTF8ToString(mime_type) });
71 var url = URL.createObjectURL(blob);
72 var a = document.createElement('a');
73 a.href = url;
74 a.download = UTF8ToString(filename);
75 a.style.display = 'none';
76 document.body.appendChild(a);
77 a.click();
78 setTimeout(function() {
79 document.body.removeChild(a);
80 URL.revokeObjectURL(url);
81 }, 100);
82});
83
84EM_JS(void, downloadTextFile_impl, (const char* filename, const char* content, const char* mime_type), {
85 var blob = new Blob([UTF8ToString(content)], { type: UTF8ToString(mime_type) });
86 var url = URL.createObjectURL(blob);
87 var a = document.createElement('a');
88 a.href = url;
89 a.download = UTF8ToString(filename);
90 a.style.display = 'none';
91 document.body.appendChild(a);
92 a.click();
93 setTimeout(function() {
94 document.body.removeChild(a);
95 URL.revokeObjectURL(url);
96 }, 100);
97});
98
99EM_JS(bool, isFileApiSupported, (), {
100 return (typeof File !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined' && typeof URL !== 'undefined' && typeof URL.createObjectURL !== 'undefined');
101});
102
103// Implementation of public methods
104void WasmFileDialog::OpenFileDialog(const std::string& accept, FileLoadCallback on_load, ErrorCallback on_error) {
105 PendingOperation op;
106 op.binary_callback = on_load;
107 op.error_callback = on_error;
108 op.is_text = false;
109 int callback_id = RegisterCallback(std::move(op));
110 openFileDialog_impl(accept.c_str(), callback_id, false);
111}
112
113void WasmFileDialog::OpenTextFileDialog(const std::string& accept,
114 std::function<void(const std::string&, const std::string&)> on_load,
115 ErrorCallback on_error) {
116 PendingOperation op;
117 op.text_callback = on_load;
118 op.error_callback = on_error;
119 op.is_text = true;
120 int callback_id = RegisterCallback(std::move(op));
121 openFileDialog_impl(accept.c_str(), callback_id, true);
122}
123
124absl::Status WasmFileDialog::DownloadFile(const std::string& filename, const std::vector<uint8_t>& data) {
125 if (!IsSupported()) {
126 return absl::FailedPreconditionError("File API not supported in this browser");
127 }
128 if (data.empty()) {
129 return absl::InvalidArgumentError("Cannot download empty file");
130 }
131 downloadFile_impl(filename.c_str(), data.data(), data.size(), "application/octet-stream");
132 return absl::OkStatus();
133}
134
135absl::Status WasmFileDialog::DownloadTextFile(const std::string& filename, const std::string& content, const std::string& mime_type) {
136 if (!IsSupported()) {
137 return absl::FailedPreconditionError("File API not supported in this browser");
138 }
139 downloadTextFile_impl(filename.c_str(), content.c_str(), mime_type.c_str());
140 return absl::OkStatus();
141}
142
143bool WasmFileDialog::IsSupported() {
144 return isFileApiSupported();
145}
146
147// Private methods
148int WasmFileDialog::RegisterCallback(PendingOperation operation) {
149 std::lock_guard<std::mutex> lock(operations_mutex_);
150 int id = next_callback_id_++;
151 operation.id = id;
152 pending_operations_[id] = std::move(operation);
153 return id;
154}
155
156std::unique_ptr<WasmFileDialog::PendingOperation> WasmFileDialog::GetPendingOperation(int callback_id) {
157 std::lock_guard<std::mutex> lock(operations_mutex_);
158 auto it = pending_operations_.find(callback_id);
159 if (it == pending_operations_.end()) {
160 return nullptr;
161 }
162 auto op = std::make_unique<PendingOperation>(std::move(it->second));
163 pending_operations_.erase(it);
164 return op;
165}
166
167void WasmFileDialog::HandleFileLoaded(int callback_id, const char* filename, const uint8_t* data, size_t size) {
168 auto op = GetPendingOperation(callback_id);
169 if (!op) {
170 emscripten_log(EM_LOG_WARN, "Unknown callback ID: %d", callback_id);
171 return;
172 }
173 if (op->binary_callback) {
174 std::vector<uint8_t> file_data(data, data + size);
175 op->binary_callback(filename, file_data);
176 }
177}
178
179void WasmFileDialog::HandleTextFileLoaded(int callback_id, const char* filename, const char* content) {
180 auto op = GetPendingOperation(callback_id);
181 if (!op) {
182 emscripten_log(EM_LOG_WARN, "Unknown callback ID: %d", callback_id);
183 return;
184 }
185 if (op->text_callback) {
186 op->text_callback(filename, content);
187 }
188}
189
190void WasmFileDialog::HandleFileError(int callback_id, const char* error_message) {
191 auto op = GetPendingOperation(callback_id);
192 if (!op) {
193 emscripten_log(EM_LOG_WARN, "Unknown callback ID: %d", callback_id);
194 return;
195 }
196 if (op->error_callback) {
197 op->error_callback(error_message);
198 } else {
199 emscripten_log(EM_LOG_ERROR, "File operation error: %s", error_message);
200 }
201}
202
203} // namespace platform
204} // namespace yaze
205
206// C-style callbacks for JavaScript interop - must be extern "C" with EMSCRIPTEN_KEEPALIVE
207extern "C" {
208
209EMSCRIPTEN_KEEPALIVE
210void yazeHandleFileLoaded(int callback_id, const char* filename, const uint8_t* data, size_t size) {
211 yaze::platform::WasmFileDialog::HandleFileLoaded(callback_id, filename, data, size);
212}
213
214EMSCRIPTEN_KEEPALIVE
215void yazeHandleTextFileLoaded(int callback_id, const char* filename, const char* content) {
216 yaze::platform::WasmFileDialog::HandleTextFileLoaded(callback_id, filename, content);
217}
218
219EMSCRIPTEN_KEEPALIVE
220void yazeHandleFileError(int callback_id, const char* error_message) {
221 yaze::platform::WasmFileDialog::HandleFileError(callback_id, error_message);
222}
223
224} // extern "C"
225
226#endif // __EMSCRIPTEN__
227// clang-format on
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })