7#include <emscripten/html5.h>
12#include "absl/strings/str_format.h"
18std::unique_ptr<WasmDropHandler> WasmDropHandler::instance_ =
nullptr;
21EM_JS(
void, setupDropZone_impl, (
const char* element_id), {
22 var targetElement = document.body;
23 if (element_id && UTF8ToString(element_id).length > 0) {
24 var el = document.getElementById(UTF8ToString(element_id));
31 if (window.yazeDropListeners) {
32 window.yazeDropListeners.forEach(function(listener) {
33 document.removeEventListener(listener.event, listener.handler);
36 window.yazeDropListeners = [];
39 var overlay = document.getElementById(
'yaze-drop-overlay');
41 overlay = document.createElement(
'div');
42 overlay.id =
'yaze-drop-overlay';
43 overlay.className =
'yaze-drop-overlay';
44 overlay.innerHTML =
'<div class="yaze-drop-content"><div class="yaze-drop-icon">📁</div><div class="yaze-drop-text">Drop file here</div><div class="yaze-drop-info">Supported: .sfc, .smc, .zip, .pal, .tpl</div></div>';
45 document.body.appendChild(overlay);
49 function isSupportedFile(filename) {
50 var ext = filename.toLowerCase().split(
'.').pop();
51 return ext ===
'sfc' || ext ===
'smc' || ext ===
'zip' ||
52 ext ===
'pal' || ext ===
'tpl';
56 function containsFiles(e) {
57 if (e.dataTransfer.types) {
58 for (var i = 0; i < e.dataTransfer.types.length; i++) {
59 if (e.dataTransfer.types[i] ===
"Files") {
68 function handleDragEnter(e) {
69 if (containsFiles(e)) {
72 Module._yazeHandleDragEnter();
73 overlay.classList.add(
'yaze-drop-active');
78 function handleDragOver(e) {
79 if (containsFiles(e)) {
82 e.dataTransfer.dropEffect =
'copy';
87 function handleDragLeave(e) {
88 if (e.target === document || e.target === overlay) {
91 Module._yazeHandleDragLeave();
92 overlay.classList.remove(
'yaze-drop-active');
97 function handleDrop(e) {
101 overlay.classList.remove(
'yaze-drop-active');
103 var files = e.dataTransfer.files;
104 if (!files || files.length === 0) {
105 var errPtr = allocateUTF8(
"No files dropped");
106 Module._yazeHandleDropError(errPtr);
113 if (!isSupportedFile(file.name)) {
114 var errPtr = allocateUTF8(
"Invalid file type. Please drop a ROM (.sfc) or Palette (.pal, .tpl)");
115 Module._yazeHandleDropError(errPtr);
121 overlay.classList.add(
'yaze-drop-loading');
122 overlay.querySelector(
'.yaze-drop-text').textContent =
'Loading file...';
123 overlay.querySelector(
'.yaze-drop-info').textContent = file.name +
' (' + (file.size / 1024).toFixed(1) +
' KB)';
125 var reader =
new FileReader();
126 reader.onload = function() {
127 var filename = file.name;
128 var filenamePtr = allocateUTF8(filename);
129 var data =
new Uint8Array(reader.result);
130 var dataPtr = Module._malloc(data.length);
131 Module.HEAPU8.set(data, dataPtr);
132 Module._yazeHandleDroppedFile(filenamePtr, dataPtr, data.length);
133 Module._free(dataPtr);
137 setTimeout(function() {
138 overlay.classList.remove(
'yaze-drop-loading');
139 overlay.querySelector(
'.yaze-drop-text').textContent =
'Drop file here';
140 overlay.querySelector(
'.yaze-drop-info').textContent =
'Supported: .sfc, .smc, .zip, .pal, .tpl';
144 reader.onerror = function() {
145 var errPtr = allocateUTF8(
"Failed to read file: " + file.name);
146 Module._yazeHandleDropError(errPtr);
148 overlay.classList.remove(
'yaze-drop-loading');
151 reader.readAsArrayBuffer(file);
155 var dragEnterHandler = handleDragEnter;
156 var dragOverHandler = handleDragOver;
157 var dragLeaveHandler = handleDragLeave;
158 var dropHandler = handleDrop;
160 document.addEventListener(
'dragenter', dragEnterHandler,
false);
161 document.addEventListener(
'dragover', dragOverHandler,
false);
162 document.addEventListener(
'dragleave', dragLeaveHandler,
false);
163 document.addEventListener(
'drop', dropHandler,
false);
166 window.yazeDropListeners = [
167 { event:
'dragenter', handler: dragEnterHandler },
168 { event:
'dragover', handler: dragOverHandler },
169 { event:
'dragleave', handler: dragLeaveHandler },
170 { event:
'drop', handler: dropHandler }
174EM_JS(
void, disableDropZone_impl, (), {
175 if (window.yazeDropListeners) {
176 window.yazeDropListeners.forEach(function(listener) {
177 document.removeEventListener(listener.event, listener.handler);
179 window.yazeDropListeners = [];
181 var overlay = document.getElementById(
'yaze-drop-overlay');
183 overlay.classList.remove(
'yaze-drop-active');
184 overlay.classList.remove(
'yaze-drop-loading');
188EM_JS(
void, setOverlayVisible_impl, (
bool visible), {
189 var overlay = document.getElementById(
'yaze-drop-overlay');
191 overlay.style.display = visible ?
'flex' :
'none';
195EM_JS(
void, setOverlayText_impl, (
const char* text), {
196 var overlay = document.getElementById(
'yaze-drop-overlay');
198 var textElement = overlay.querySelector(
'.yaze-drop-text');
200 textElement.textContent = UTF8ToString(text);
205EM_JS(
bool, isDragDropSupported, (), {
206 return (typeof FileReader !==
'undefined' && typeof DataTransfer !==
'undefined' &&
'draggable' in document.createElement(
'div'));
209EM_JS(
void, injectDropZoneStyles, (), {
210 if (document.getElementById(
'yaze-drop-styles')) {
214 var style = document.createElement(
'style');
215 style.id =
'yaze-drop-styles';
216 style.textContent = `
224 background: rgba(0, 0, 0, 0.85);
227 justify-content: center;
228 pointer-events: none;
229 transition: all 0.3s ease;
232 .yaze-drop-overlay.yaze-drop-active {
235 background: rgba(0, 0, 0, 0.9);
238 .yaze-drop-overlay.yaze-drop-loading {
246 font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
249 background: rgba(255, 255, 255, 0.1);
250 border: 3px dashed rgba(255, 255, 255, 0.5);
251 animation: pulse 2s infinite;
254 .yaze-drop-overlay.yaze-drop-active .yaze-drop-content {
255 border-color: #4CAF50;
256 background: rgba(76, 175, 80, 0.2);
257 animation: pulse-active 1s infinite;
260 .yaze-drop-overlay.yaze-drop-loading .yaze-drop-content {
261 border-color: #2196F3;
262 background: rgba(33, 150, 243, 0.2);
270 filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.3));
277 text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
287 0%, 100% { transform: scale(1); opacity: 1; }
288 50% { transform: scale(1.02); opacity: 0.9; }
291 @keyframes pulse-active {
292 0%, 100% { transform: scale(1); }
293 50% { transform: scale(1.05); }
296 document.head.appendChild(style);
300WasmDropHandler& WasmDropHandler::GetInstance() {
302 instance_ = std::unique_ptr<WasmDropHandler>(
new WasmDropHandler());
307WasmDropHandler::WasmDropHandler() =
default;
308WasmDropHandler::~WasmDropHandler() {
309 if (initialized_ && enabled_) {
310 disableDropZone_impl();
314absl::Status WasmDropHandler::Initialize(
const std::string& element_id,
315 DropCallback on_drop,
316 ErrorCallback on_error) {
317 if (!IsSupported()) {
318 return absl::FailedPreconditionError(
319 "Drag and drop not supported in this browser");
323 injectDropZoneStyles();
327 drop_callback_ = on_drop;
330 error_callback_ = on_error;
334 element_id_ = element_id;
335 setupDropZone_impl(element_id.c_str());
340 return absl::OkStatus();
343void WasmDropHandler::SetDropCallback(DropCallback on_drop) {
344 drop_callback_ = on_drop;
347void WasmDropHandler::SetErrorCallback(ErrorCallback on_error) {
348 error_callback_ = on_error;
351void WasmDropHandler::SetEnabled(
bool enabled) {
352 if (enabled_ != enabled) {
355 disableDropZone_impl();
356 }
else if (initialized_) {
357 setupDropZone_impl(element_id_.c_str());
362void WasmDropHandler::SetOverlayVisible(
bool visible) {
363 setOverlayVisible_impl(visible);
366void WasmDropHandler::SetOverlayText(
const std::string& text) {
367 setOverlayText_impl(text.c_str());
370bool WasmDropHandler::IsSupported() {
371 return isDragDropSupported();
374bool WasmDropHandler::IsValidRomFile(
const std::string& filename) {
376 size_t dot_pos = filename.find_last_of(
'.');
377 if (dot_pos == std::string::npos) {
381 std::string ext = filename.substr(dot_pos + 1);
384 std::transform(ext.begin(), ext.end(), ext.begin(),
385 [](
unsigned char c) { return std::tolower(c); });
387 return ext ==
"sfc" || ext ==
"smc" || ext ==
"zip" ||
388 ext ==
"pal" || ext ==
"tpl";
391void WasmDropHandler::HandleDroppedFile(
const char* filename,
392 const uint8_t* data,
size_t size) {
393 auto& instance = GetInstance();
396 if (!IsValidRomFile(filename)) {
397 HandleDropError(
"Invalid file format");
402 if (instance.drop_callback_) {
403 std::vector<uint8_t> file_data(data, data + size);
404 instance.drop_callback_(filename, file_data);
406 emscripten_log(EM_LOG_WARN,
"No drop callback registered for file: %s",
411 instance.drag_counter_ = 0;
414void WasmDropHandler::HandleDropError(
const char* error_message) {
415 auto& instance = GetInstance();
417 if (instance.error_callback_) {
418 instance.error_callback_(error_message);
420 emscripten_log(EM_LOG_ERROR,
"Drop error: %s", error_message);
424 instance.drag_counter_ = 0;
427void WasmDropHandler::HandleDragEnter() {
428 auto& instance = GetInstance();
429 instance.drag_counter_++;
432void WasmDropHandler::HandleDragLeave() {
433 auto& instance = GetInstance();
434 instance.drag_counter_--;
437 if (instance.drag_counter_ <= 0) {
438 instance.drag_counter_ = 0;
449void yazeHandleDroppedFile(
const char* filename,
const uint8_t* data,
451 yaze::platform::WasmDropHandler::HandleDroppedFile(filename, data, size);
455void yazeHandleDropError(
const char* error_message) {
456 yaze::platform::WasmDropHandler::HandleDropError(error_message);
460void yazeHandleDragEnter() {
461 yaze::platform::WasmDropHandler::HandleDragEnter();
465void yazeHandleDragLeave() {
466 yaze::platform::WasmDropHandler::HandleDragLeave();
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");} })