yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_settings.cc
Go to the documentation of this file.
1#ifdef __EMSCRIPTEN__
2
4
5#include <emscripten.h>
6#include <algorithm>
7#include <sstream>
8
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_format.h"
14
15namespace yaze {
16namespace platform {
17
18// JavaScript localStorage interface using EM_JS
19EM_JS(void, localStorage_setItem, (const char* key, const char* value), {
20 try {
21 localStorage.setItem(UTF8ToString(key), UTF8ToString(value));
22 } catch (e) {
23 console.error('Failed to save to localStorage:', e);
24 }
25});
26
27EM_JS(char*, localStorage_getItem, (const char* key), {
28 try {
29 const value = localStorage.getItem(UTF8ToString(key));
30 if (value == null)
31 return null;
32 const len = lengthBytesUTF8(value) + 1;
33 const ptr = _malloc(len);
34 stringToUTF8(value, ptr, len);
35 return ptr;
36 } catch (e) {
37 console.error('Failed to read from localStorage:', e);
38 return null;
39 }
40});
41
42EM_JS(void, localStorage_removeItem, (const char* key), {
43 try {
44 localStorage.removeItem(UTF8ToString(key));
45 } catch (e) {
46 console.error('Failed to remove from localStorage:', e);
47 }
48});
49
50EM_JS(int, localStorage_hasItem, (const char* key), {
51 try {
52 return localStorage.getItem(UTF8ToString(key)) != null ? 1 : 0;
53 } catch (e) {
54 console.error('Failed to check localStorage:', e);
55 return 0;
56 }
57});
58
59EM_JS(void, localStorage_clear, (), {
60 try {
61 // Only clear yaze-specific keys
62 const keys = [];
63 for (let i = 0; i < localStorage.length; i++) {
64 const key = localStorage.key(i);
65 if (key && key.startsWith('yaze_')) {
66 keys.push(key);
67 }
68 }
69 keys.forEach(function(key) { localStorage.removeItem(key); });
70 } catch (e) {
71 console.error('Failed to clear localStorage:', e);
72 }
73});
74
75// Theme Management
76
77absl::Status WasmSettings::SaveTheme(const std::string& theme) {
78 localStorage_setItem(kThemeKey, theme.c_str());
79 return absl::OkStatus();
80}
81
82std::string WasmSettings::LoadTheme() {
83 char* theme = localStorage_getItem(kThemeKey);
84 if (!theme) {
85 return "dark"; // Default theme
86 }
87 std::string result(theme);
88 free(theme);
89 return result;
90}
91
92std::string WasmSettings::GetCurrentThemeData() {
94}
95
96absl::Status WasmSettings::LoadUserFont(const std::string& name,
97 const std::string& data, float size) {
98 return LoadFontFromMemory(name, data, size);
99}
100
101// Recent Files Management
102
103nlohmann::json WasmSettings::RecentFilesToJson(
104 const std::vector<RecentFile>& files) {
105 nlohmann::json json_array = nlohmann::json::array();
106 for (const auto& file : files) {
107 nlohmann::json entry;
108 entry["filename"] = file.filename;
109 entry["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
110 file.timestamp.time_since_epoch())
111 .count();
112 json_array.push_back(entry);
113 }
114 return json_array;
115}
116
117std::vector<WasmSettings::RecentFile> WasmSettings::JsonToRecentFiles(
118 const nlohmann::json& json) {
119 std::vector<RecentFile> files;
120 if (!json.is_array())
121 return files;
122
123 for (const auto& entry : json) {
124 if (entry.contains("filename") && entry.contains("timestamp")) {
125 RecentFile file;
126 file.filename = entry["filename"].get<std::string>();
127 auto ms = std::chrono::milliseconds(entry["timestamp"].get<int64_t>());
128 file.timestamp = std::chrono::system_clock::time_point(ms);
129 files.push_back(file);
130 }
131 }
132 return files;
133}
134
135absl::Status WasmSettings::AddRecentFile(
136 const std::string& filename,
137 std::chrono::system_clock::time_point timestamp) {
138 // Load existing recent files
139 char* json_str = localStorage_getItem(kRecentFilesKey);
140 std::vector<RecentFile> files;
141
142 if (json_str) {
143 try {
144 nlohmann::json json = nlohmann::json::parse(json_str);
145 files = JsonToRecentFiles(json);
146 } catch (const std::exception& e) {
147 // Ignore parse errors and start fresh
148 emscripten_log(EM_LOG_WARN, "Failed to parse recent files: %s", e.what());
149 }
150 free(json_str);
151 }
152
153 // Remove existing entry if present
154 files.erase(std::remove_if(files.begin(), files.end(),
155 [&filename](const RecentFile& f) {
156 return f.filename == filename;
157 }),
158 files.end());
159
160 // Add new entry at the beginning
161 files.insert(files.begin(), {filename, timestamp});
162
163 // Limit to 20 recent files
164 if (files.size() > 20) {
165 files.resize(20);
166 }
167
168 // Save back to localStorage
169 nlohmann::json json = RecentFilesToJson(files);
170 localStorage_setItem(kRecentFilesKey, json.dump().c_str());
171
172 return absl::OkStatus();
173}
174
175std::vector<std::string> WasmSettings::GetRecentFiles(size_t max_count) {
176 std::vector<std::string> result;
177
178 char* json_str = localStorage_getItem(kRecentFilesKey);
179 if (!json_str) {
180 return result;
181 }
182
183 try {
184 nlohmann::json json = nlohmann::json::parse(json_str);
185 std::vector<RecentFile> files = JsonToRecentFiles(json);
186
187 size_t count = std::min(max_count, files.size());
188 for (size_t i = 0; i < count; ++i) {
189 result.push_back(files[i].filename);
190 }
191 } catch (const std::exception& e) {
192 emscripten_log(EM_LOG_WARN, "Failed to parse recent files: %s", e.what());
193 }
194
195 free(json_str);
196 return result;
197}
198
199absl::Status WasmSettings::ClearRecentFiles() {
200 localStorage_removeItem(kRecentFilesKey);
201 return absl::OkStatus();
202}
203
204absl::Status WasmSettings::RemoveRecentFile(const std::string& filename) {
205 char* json_str = localStorage_getItem(kRecentFilesKey);
206 if (!json_str) {
207 return absl::OkStatus(); // Nothing to remove
208 }
209
210 try {
211 nlohmann::json json = nlohmann::json::parse(json_str);
212 std::vector<RecentFile> files = JsonToRecentFiles(json);
213
214 files.erase(std::remove_if(files.begin(), files.end(),
215 [&filename](const RecentFile& f) {
216 return f.filename == filename;
217 }),
218 files.end());
219
220 nlohmann::json new_json = RecentFilesToJson(files);
221 localStorage_setItem(kRecentFilesKey, new_json.dump().c_str());
222 } catch (const std::exception& e) {
223 free(json_str);
224 return absl::InternalError(
225 absl::StrFormat("Failed to remove recent file: %s", e.what()));
226 }
227
228 free(json_str);
229 return absl::OkStatus();
230}
231
232// Workspace Layout Management
233
234absl::Status WasmSettings::SaveWorkspace(const std::string& name,
235 const std::string& layout_json) {
236 std::string key = absl::StrCat(kWorkspacePrefix, name);
237 return WasmStorage::SaveProject(key, layout_json);
238}
239
240absl::StatusOr<std::string> WasmSettings::LoadWorkspace(
241 const std::string& name) {
242 std::string key = absl::StrCat(kWorkspacePrefix, name);
243 return WasmStorage::LoadProject(key);
244}
245
246std::vector<std::string> WasmSettings::ListWorkspaces() {
247 std::vector<std::string> all_projects = WasmStorage::ListProjects();
248 std::vector<std::string> workspaces;
249
250 const std::string prefix(kWorkspacePrefix);
251 for (const auto& project : all_projects) {
252 if (project.find(prefix) == 0) {
253 workspaces.push_back(project.substr(prefix.length()));
254 }
255 }
256
257 return workspaces;
258}
259
260absl::Status WasmSettings::DeleteWorkspace(const std::string& name) {
261 std::string key = absl::StrCat(kWorkspacePrefix, name);
262 return WasmStorage::DeleteProject(key);
263}
264
265absl::Status WasmSettings::SetActiveWorkspace(const std::string& name) {
266 localStorage_setItem(kActiveWorkspaceKey, name.c_str());
267 return absl::OkStatus();
268}
269
270std::string WasmSettings::GetActiveWorkspace() {
271 char* workspace = localStorage_getItem(kActiveWorkspaceKey);
272 if (!workspace) {
273 return "default";
274 }
275 std::string result(workspace);
276 free(workspace);
277 return result;
278}
279
280// Undo History Persistence
281
282absl::Status WasmSettings::SaveUndoHistory(
283 const std::string& editor_id, const std::vector<uint8_t>& history) {
284 std::string key = absl::StrCat(kUndoHistoryPrefix, editor_id);
285 return WasmStorage::SaveRom(key, history); // Use binary storage
286}
287
288absl::StatusOr<std::vector<uint8_t>> WasmSettings::LoadUndoHistory(
289 const std::string& editor_id) {
290 std::string key = absl::StrCat(kUndoHistoryPrefix, editor_id);
291 return WasmStorage::LoadRom(key);
292}
293
294absl::Status WasmSettings::ClearUndoHistory(const std::string& editor_id) {
295 std::string key = absl::StrCat(kUndoHistoryPrefix, editor_id);
296 return WasmStorage::DeleteRom(key);
297}
298
299absl::Status WasmSettings::ClearAllUndoHistory() {
300 std::vector<std::string> all_roms = WasmStorage::ListRoms();
301 const std::string prefix(kUndoHistoryPrefix);
302
303 for (const auto& rom : all_roms) {
304 if (rom.find(prefix) == 0) {
305 auto status = WasmStorage::DeleteRom(rom);
306 if (!status.ok()) {
307 return status;
308 }
309 }
310 }
311
312 return absl::OkStatus();
313}
314
315// General Settings
316
317absl::Status WasmSettings::SaveSetting(const std::string& key,
318 const nlohmann::json& value) {
319 std::string storage_key = absl::StrCat(kSettingsPrefix, key);
320 localStorage_setItem(storage_key.c_str(), value.dump().c_str());
321
322 // Update last save time
323 auto now = std::chrono::system_clock::now();
324 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
325 now.time_since_epoch())
326 .count();
327 localStorage_setItem(kLastSaveTimeKey, std::to_string(ms).c_str());
328
329 return absl::OkStatus();
330}
331
332absl::StatusOr<nlohmann::json> WasmSettings::LoadSetting(
333 const std::string& key) {
334 std::string storage_key = absl::StrCat(kSettingsPrefix, key);
335 char* value = localStorage_getItem(storage_key.c_str());
336
337 if (!value) {
338 return absl::NotFoundError(absl::StrFormat("Setting '%s' not found", key));
339 }
340
341 try {
342 nlohmann::json json = nlohmann::json::parse(value);
343 free(value);
344 return json;
345 } catch (const std::exception& e) {
346 free(value);
347 return absl::InvalidArgumentError(
348 absl::StrFormat("Failed to parse setting '%s': %s", key, e.what()));
349 }
350}
351
352bool WasmSettings::HasSetting(const std::string& key) {
353 std::string storage_key = absl::StrCat(kSettingsPrefix, key);
354 return localStorage_hasItem(storage_key.c_str()) == 1;
355}
356
357absl::Status WasmSettings::SaveAllSettings(const nlohmann::json& settings) {
358 if (!settings.is_object()) {
359 return absl::InvalidArgumentError("Settings must be a JSON object");
360 }
361
362 for (auto it = settings.begin(); it != settings.end(); ++it) {
363 auto status = SaveSetting(it.key(), it.value());
364 if (!status.ok()) {
365 return status;
366 }
367 }
368
369 return absl::OkStatus();
370}
371
372absl::StatusOr<nlohmann::json> WasmSettings::LoadAllSettings() {
373 nlohmann::json settings = nlohmann::json::object();
374
375 // This is a simplified implementation since we can't easily iterate localStorage
376 // from C++. In practice, you'd maintain a list of known setting keys.
377 // For now, we'll just return common settings if they exist.
378
379 std::vector<std::string> common_keys = {
380 "show_grid", "grid_size", "auto_save",
381 "auto_save_interval", "show_tooltips", "confirm_on_delete",
382 "default_editor", "animation_speed", "zoom_level",
383 "show_minimap"};
384
385 for (const auto& key : common_keys) {
386 if (HasSetting(key)) {
387 auto result = LoadSetting(key);
388 if (result.ok()) {
389 settings[key] = *result;
390 }
391 }
392 }
393
394 return settings;
395}
396
397absl::Status WasmSettings::ClearAllSettings() {
398 localStorage_clear();
399 return absl::OkStatus();
400}
401
402// Utility
403
404absl::StatusOr<std::string> WasmSettings::ExportSettings() {
405 nlohmann::json export_data = nlohmann::json::object();
406
407 // Export theme
408 export_data["theme"] = LoadTheme();
409
410 // Export recent files
411 char* recent_json = localStorage_getItem(kRecentFilesKey);
412 if (recent_json) {
413 try {
414 export_data["recent_files"] = nlohmann::json::parse(recent_json);
415 } catch (...) {
416 // Ignore parse errors
417 }
418 free(recent_json);
419 }
420
421 // Export active workspace
422 export_data["active_workspace"] = GetActiveWorkspace();
423
424 // Export workspaces
425 nlohmann::json workspaces = nlohmann::json::object();
426 for (const auto& name : ListWorkspaces()) {
427 auto workspace_data = LoadWorkspace(name);
428 if (workspace_data.ok()) {
429 workspaces[name] = nlohmann::json::parse(*workspace_data);
430 }
431 }
432 export_data["workspaces"] = workspaces;
433
434 // Export general settings
435 auto all_settings = LoadAllSettings();
436 if (all_settings.ok()) {
437 export_data["settings"] = *all_settings;
438 }
439
440 return export_data.dump(2); // Pretty print with 2 spaces
441}
442
443absl::Status WasmSettings::ImportSettings(const std::string& json_str) {
444 try {
445 nlohmann::json import_data = nlohmann::json::parse(json_str);
446
447 // Import theme
448 if (import_data.contains("theme")) {
449 SaveTheme(import_data["theme"].get<std::string>());
450 }
451
452 // Import recent files
453 if (import_data.contains("recent_files")) {
454 localStorage_setItem(kRecentFilesKey,
455 import_data["recent_files"].dump().c_str());
456 }
457
458 // Import active workspace
459 if (import_data.contains("active_workspace")) {
460 SetActiveWorkspace(import_data["active_workspace"].get<std::string>());
461 }
462
463 // Import workspaces
464 if (import_data.contains("workspaces") &&
465 import_data["workspaces"].is_object()) {
466 for (auto it = import_data["workspaces"].begin();
467 it != import_data["workspaces"].end(); ++it) {
468 SaveWorkspace(it.key(), it.value().dump());
469 }
470 }
471
472 // Import general settings
473 if (import_data.contains("settings") &&
474 import_data["settings"].is_object()) {
475 SaveAllSettings(import_data["settings"]);
476 }
477
478 return absl::OkStatus();
479 } catch (const std::exception& e) {
480 return absl::InvalidArgumentError(
481 absl::StrFormat("Failed to import settings: %s", e.what()));
482 }
483}
484
485absl::StatusOr<std::chrono::system_clock::time_point>
486WasmSettings::GetLastSaveTime() {
487 char* time_str = localStorage_getItem(kLastSaveTimeKey);
488 if (!time_str) {
489 return absl::NotFoundError("No save time recorded");
490 }
491
492 try {
493 int64_t ms = std::stoll(time_str);
494 free(time_str);
495 return std::chrono::system_clock::time_point(std::chrono::milliseconds(ms));
496 } catch (const std::exception& e) {
497 free(time_str);
498 return absl::InvalidArgumentError(
499 absl::StrFormat("Failed to parse save time: %s", e.what()));
500 }
501}
502
503} // namespace platform
504} // namespace yaze
505
506#endif // __EMSCRIPTEN__
static ThemeManager & Get()
std::string ExportCurrentThemeJson() const
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");} })
absl::Status LoadFontFromMemory(const std::string &name, const std::string &data, float size_pixels)