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) return null;
31 const len = lengthBytesUTF8(value) + 1;
32 const ptr = _malloc(len);
33 stringToUTF8(value, ptr, len);
34 return ptr;
35 } catch (e) {
36 console.error('Failed to read from localStorage:', e);
37 return null;
38 }
39});
40
41EM_JS(void, localStorage_removeItem, (const char* key), {
42 try {
43 localStorage.removeItem(UTF8ToString(key));
44 } catch (e) {
45 console.error('Failed to remove from localStorage:', e);
46 }
47});
48
49EM_JS(int, localStorage_hasItem, (const char* key), {
50 try {
51 return localStorage.getItem(UTF8ToString(key)) !== null ? 1 : 0;
52 } catch (e) {
53 console.error('Failed to check localStorage:', e);
54 return 0;
55 }
56});
57
58EM_JS(void, localStorage_clear, (), {
59 try {
60 // Only clear yaze-specific keys
61 const keys = [];
62 for (let i = 0; i < localStorage.length; i++) {
63 const key = localStorage.key(i);
64 if (key && key.startsWith('yaze_')) {
65 keys.push(key);
66 }
67 }
68 keys.forEach(key => localStorage.removeItem(key));
69 } catch (e) {
70 console.error('Failed to clear localStorage:', e);
71 }
72});
73
74// Theme Management
75
76absl::Status WasmSettings::SaveTheme(const std::string& theme) {
77 localStorage_setItem(kThemeKey, theme.c_str());
78 return absl::OkStatus();
79}
80
81std::string WasmSettings::LoadTheme() {
82 char* theme = localStorage_getItem(kThemeKey);
83 if (!theme) {
84 return "dark"; // Default theme
85 }
86 std::string result(theme);
87 free(theme);
88 return result;
89}
90
91std::string WasmSettings::GetCurrentThemeData() {
93}
94
95absl::Status WasmSettings::LoadUserFont(const std::string& name,
96 const std::string& data, float size) {
97 return LoadFontFromMemory(name, data, size);
98}
99
100// Recent Files Management
101
102nlohmann::json WasmSettings::RecentFilesToJson(
103 const std::vector<RecentFile>& files) {
104 nlohmann::json json_array = nlohmann::json::array();
105 for (const auto& file : files) {
106 nlohmann::json entry;
107 entry["filename"] = file.filename;
108 entry["timestamp"] =
109 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()) return files;
121
122 for (const auto& entry : json) {
123 if (entry.contains("filename") && entry.contains("timestamp")) {
124 RecentFile file;
125 file.filename = entry["filename"].get<std::string>();
126 auto ms = std::chrono::milliseconds(entry["timestamp"].get<int64_t>());
127 file.timestamp = std::chrono::system_clock::time_point(ms);
128 files.push_back(file);
129 }
130 }
131 return files;
132}
133
134absl::Status WasmSettings::AddRecentFile(
135 const std::string& filename,
136 std::chrono::system_clock::time_point timestamp) {
137 // Load existing recent files
138 char* json_str = localStorage_getItem(kRecentFilesKey);
139 std::vector<RecentFile> files;
140
141 if (json_str) {
142 try {
143 nlohmann::json json = nlohmann::json::parse(json_str);
144 files = JsonToRecentFiles(json);
145 } catch (const std::exception& e) {
146 // Ignore parse errors and start fresh
147 emscripten_log(EM_LOG_WARN, "Failed to parse recent files: %s", e.what());
148 }
149 free(json_str);
150 }
151
152 // Remove existing entry if present
153 files.erase(
154 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(
215 std::remove_if(files.begin(), files.end(),
216 [&filename](const RecentFile& f) {
217 return f.filename == filename;
218 }),
219 files.end());
220
221 nlohmann::json new_json = RecentFilesToJson(files);
222 localStorage_setItem(kRecentFilesKey, new_json.dump().c_str());
223 } catch (const std::exception& e) {
224 free(json_str);
225 return absl::InternalError(
226 absl::StrFormat("Failed to remove recent file: %s", e.what()));
227 }
228
229 free(json_str);
230 return absl::OkStatus();
231}
232
233// Workspace Layout Management
234
235absl::Status WasmSettings::SaveWorkspace(const std::string& name,
236 const std::string& layout_json) {
237 std::string key = absl::StrCat(kWorkspacePrefix, name);
238 return WasmStorage::SaveProject(key, layout_json);
239}
240
241absl::StatusOr<std::string> WasmSettings::LoadWorkspace(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(const std::string& editor_id,
283 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()).count();
326 localStorage_setItem(kLastSaveTimeKey, std::to_string(ms).c_str());
327
328 return absl::OkStatus();
329}
330
331absl::StatusOr<nlohmann::json> WasmSettings::LoadSetting(const std::string& key) {
332 std::string storage_key = absl::StrCat(kSettingsPrefix, key);
333 char* value = localStorage_getItem(storage_key.c_str());
334
335 if (!value) {
336 return absl::NotFoundError(absl::StrFormat("Setting '%s' not found", key));
337 }
338
339 try {
340 nlohmann::json json = nlohmann::json::parse(value);
341 free(value);
342 return json;
343 } catch (const std::exception& e) {
344 free(value);
345 return absl::InvalidArgumentError(
346 absl::StrFormat("Failed to parse setting '%s': %s", key, e.what()));
347 }
348}
349
350bool WasmSettings::HasSetting(const std::string& key) {
351 std::string storage_key = absl::StrCat(kSettingsPrefix, key);
352 return localStorage_hasItem(storage_key.c_str()) == 1;
353}
354
355absl::Status WasmSettings::SaveAllSettings(const nlohmann::json& settings) {
356 if (!settings.is_object()) {
357 return absl::InvalidArgumentError("Settings must be a JSON object");
358 }
359
360 for (auto it = settings.begin(); it != settings.end(); ++it) {
361 auto status = SaveSetting(it.key(), it.value());
362 if (!status.ok()) {
363 return status;
364 }
365 }
366
367 return absl::OkStatus();
368}
369
370absl::StatusOr<nlohmann::json> WasmSettings::LoadAllSettings() {
371 nlohmann::json settings = nlohmann::json::object();
372
373 // This is a simplified implementation since we can't easily iterate localStorage
374 // from C++. In practice, you'd maintain a list of known setting keys.
375 // For now, we'll just return common settings if they exist.
376
377 std::vector<std::string> common_keys = {
378 "show_grid", "grid_size", "auto_save", "auto_save_interval",
379 "show_tooltips", "confirm_on_delete", "default_editor",
380 "animation_speed", "zoom_level", "show_minimap"
381 };
382
383 for (const auto& key : common_keys) {
384 if (HasSetting(key)) {
385 auto result = LoadSetting(key);
386 if (result.ok()) {
387 settings[key] = *result;
388 }
389 }
390 }
391
392 return settings;
393}
394
395absl::Status WasmSettings::ClearAllSettings() {
396 localStorage_clear();
397 return absl::OkStatus();
398}
399
400// Utility
401
402absl::StatusOr<std::string> WasmSettings::ExportSettings() {
403 nlohmann::json export_data = nlohmann::json::object();
404
405 // Export theme
406 export_data["theme"] = LoadTheme();
407
408 // Export recent files
409 char* recent_json = localStorage_getItem(kRecentFilesKey);
410 if (recent_json) {
411 try {
412 export_data["recent_files"] = nlohmann::json::parse(recent_json);
413 } catch (...) {
414 // Ignore parse errors
415 }
416 free(recent_json);
417 }
418
419 // Export active workspace
420 export_data["active_workspace"] = GetActiveWorkspace();
421
422 // Export workspaces
423 nlohmann::json workspaces = nlohmann::json::object();
424 for (const auto& name : ListWorkspaces()) {
425 auto workspace_data = LoadWorkspace(name);
426 if (workspace_data.ok()) {
427 workspaces[name] = nlohmann::json::parse(*workspace_data);
428 }
429 }
430 export_data["workspaces"] = workspaces;
431
432 // Export general settings
433 auto all_settings = LoadAllSettings();
434 if (all_settings.ok()) {
435 export_data["settings"] = *all_settings;
436 }
437
438 return export_data.dump(2); // Pretty print with 2 spaces
439}
440
441absl::Status WasmSettings::ImportSettings(const std::string& json_str) {
442 try {
443 nlohmann::json import_data = nlohmann::json::parse(json_str);
444
445 // Import theme
446 if (import_data.contains("theme")) {
447 SaveTheme(import_data["theme"].get<std::string>());
448 }
449
450 // Import recent files
451 if (import_data.contains("recent_files")) {
452 localStorage_setItem(kRecentFilesKey,
453 import_data["recent_files"].dump().c_str());
454 }
455
456 // Import active workspace
457 if (import_data.contains("active_workspace")) {
458 SetActiveWorkspace(import_data["active_workspace"].get<std::string>());
459 }
460
461 // Import workspaces
462 if (import_data.contains("workspaces") && import_data["workspaces"].is_object()) {
463 for (auto it = import_data["workspaces"].begin();
464 it != import_data["workspaces"].end(); ++it) {
465 SaveWorkspace(it.key(), it.value().dump());
466 }
467 }
468
469 // Import general settings
470 if (import_data.contains("settings") && import_data["settings"].is_object()) {
471 SaveAllSettings(import_data["settings"]);
472 }
473
474 return absl::OkStatus();
475 } catch (const std::exception& e) {
476 return absl::InvalidArgumentError(
477 absl::StrFormat("Failed to import settings: %s", e.what()));
478 }
479}
480
481absl::StatusOr<std::chrono::system_clock::time_point> WasmSettings::GetLastSaveTime() {
482 char* time_str = localStorage_getItem(kLastSaveTimeKey);
483 if (!time_str) {
484 return absl::NotFoundError("No save time recorded");
485 }
486
487 try {
488 int64_t ms = std::stoll(time_str);
489 free(time_str);
490 return std::chrono::system_clock::time_point(std::chrono::milliseconds(ms));
491 } catch (const std::exception& e) {
492 free(time_str);
493 return absl::InvalidArgumentError(
494 absl::StrFormat("Failed to parse save time: %s", e.what()));
495 }
496}
497
498} // namespace platform
499} // namespace yaze
500
501#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)