yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
collaboration_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <ctime>
5#include <iomanip>
6#include <sstream>
7
8#include "imgui/imgui.h"
9
10namespace yaze {
11
12namespace gui {
13
15 : rom_(nullptr),
16 version_mgr_(nullptr),
17 approval_mgr_(nullptr),
18 selected_tab_(0),
19 selected_rom_sync_(-1),
20 selected_snapshot_(-1),
21 selected_proposal_(-1),
22 show_sync_details_(false),
23 show_snapshot_preview_(true),
24 auto_scroll_(true),
25 filter_pending_only_(false) {
26 // Initialize search filter
27 search_filter_[0] = '\0';
28
29 // Initialize colors
30 colors_.sync_applied = ImVec4(0.2f, 0.8f, 0.2f, 1.0f);
31 colors_.sync_pending = ImVec4(0.8f, 0.8f, 0.2f, 1.0f);
32 colors_.sync_error = ImVec4(0.8f, 0.2f, 0.2f, 1.0f);
33 colors_.proposal_pending = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
34 colors_.proposal_approved = ImVec4(0.2f, 0.8f, 0.2f, 1.0f);
35 colors_.proposal_rejected = ImVec4(0.8f, 0.3f, 0.3f, 1.0f);
36 colors_.proposal_applied = ImVec4(0.2f, 0.6f, 0.8f, 1.0f);
37}
38
40 // Cleanup any OpenGL textures
41 for (auto& snapshot : snapshots_) {
42 if (snapshot.texture_id) {
43 // Note: Actual texture cleanup would depend on your renderer
44 // This is a placeholder
45 snapshot.texture_id = nullptr;
46 }
47 }
48}
49
51 Rom* rom, net::RomVersionManager* version_mgr,
52 net::ProposalApprovalManager* approval_mgr) {
53 rom_ = rom;
54 version_mgr_ = version_mgr;
55 approval_mgr_ = approval_mgr;
56}
57
58void CollaborationPanel::Render(bool* p_open) {
59 if (!ImGui::Begin("Collaboration", p_open, ImGuiWindowFlags_None)) {
60 ImGui::End();
61 return;
62 }
63
64 // Tabs for different collaboration features
65 if (ImGui::BeginTabBar("CollaborationTabs")) {
66 if (ImGui::BeginTabItem("ROM Sync")) {
67 selected_tab_ = 0;
69 ImGui::EndTabItem();
70 }
71
72 if (ImGui::BeginTabItem("Version History")) {
73 selected_tab_ = 1;
75 ImGui::EndTabItem();
76 }
77
78 if (ImGui::BeginTabItem("Snapshots")) {
79 selected_tab_ = 2;
81 ImGui::EndTabItem();
82 }
83
84 if (ImGui::BeginTabItem("Proposals")) {
85 selected_tab_ = 3;
87 ImGui::EndTabItem();
88 }
89
90 if (ImGui::BeginTabItem("🔒 Approvals")) {
91 selected_tab_ = 4;
93 ImGui::EndTabItem();
94 }
95
96 ImGui::EndTabBar();
97 }
98
99 ImGui::End();
100}
101
103 ImGui::TextWrapped("ROM Synchronization History");
104 ImGui::Separator();
105
106 // Toolbar
107 if (ImGui::Button("Clear History")) {
108 rom_syncs_.clear();
109 }
110 ImGui::SameLine();
111 ImGui::Checkbox("Auto-scroll", &auto_scroll_);
112 ImGui::SameLine();
113 ImGui::Checkbox("Show Details", &show_sync_details_);
114
115 ImGui::Separator();
116
117 // Stats
118 int applied_count = 0;
119 int pending_count = 0;
120 int error_count = 0;
121
122 for (const auto& sync : rom_syncs_) {
123 if (sync.applied)
124 applied_count++;
125 else if (!sync.error_message.empty())
126 error_count++;
127 else
128 pending_count++;
129 }
130
131 ImGui::Text("Total: %zu | ", rom_syncs_.size());
132 ImGui::SameLine();
133 ImGui::TextColored(colors_.sync_applied, "Applied: %d", applied_count);
134 ImGui::SameLine();
135 ImGui::TextColored(colors_.sync_pending, "Pending: %d", pending_count);
136 ImGui::SameLine();
137 ImGui::TextColored(colors_.sync_error, "Errors: %d", error_count);
138
139 ImGui::Separator();
140
141 // Sync list
142 if (ImGui::BeginChild("SyncList", ImVec2(0, 0), true)) {
143 for (size_t i = 0; i < rom_syncs_.size(); ++i) {
145 }
146
147 if (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
148 ImGui::SetScrollHereY(1.0f);
149 }
150 }
151 ImGui::EndChild();
152}
153
155 ImGui::TextWrapped("Shared Snapshots Gallery");
156 ImGui::Separator();
157
158 // Toolbar
159 if (ImGui::Button("Clear Gallery")) {
160 snapshots_.clear();
161 }
162 ImGui::SameLine();
163 ImGui::Checkbox("Show Preview", &show_snapshot_preview_);
164 ImGui::SameLine();
165 ImGui::InputText("Search", search_filter_, sizeof(search_filter_));
166
167 ImGui::Separator();
168
169 // Snapshot grid
170 if (ImGui::BeginChild("SnapshotGrid", ImVec2(0, 0), true)) {
171 float thumbnail_size = 150.0f;
172 float padding = 10.0f;
173 float cell_size = thumbnail_size + padding;
174
175 int columns =
176 std::max(1, (int)((ImGui::GetContentRegionAvail().x) / cell_size));
177
178 for (size_t i = 0; i < snapshots_.size(); ++i) {
179 // Filter by search
180 if (search_filter_[0] != '\0') {
181 std::string search_lower = search_filter_;
182 std::string sender_lower = snapshots_[i].sender;
183 std::transform(search_lower.begin(), search_lower.end(),
184 search_lower.begin(), ::tolower);
185 std::transform(sender_lower.begin(), sender_lower.end(),
186 sender_lower.begin(), ::tolower);
187
188 if (sender_lower.find(search_lower) == std::string::npos &&
189 snapshots_[i].snapshot_type.find(search_lower) ==
190 std::string::npos) {
191 continue;
192 }
193 }
194
196
197 // Grid layout
198 if ((i + 1) % columns != 0 && i < snapshots_.size() - 1) {
199 ImGui::SameLine();
200 }
201 }
202 }
203 ImGui::EndChild();
204}
205
207 ImGui::TextWrapped("AI Proposals & Suggestions");
208 ImGui::Separator();
209
210 // Toolbar
211 if (ImGui::Button("Clear All")) {
212 proposals_.clear();
213 }
214 ImGui::SameLine();
215 ImGui::Checkbox("Pending Only", &filter_pending_only_);
216 ImGui::SameLine();
217 ImGui::InputText("Search", search_filter_, sizeof(search_filter_));
218
219 ImGui::Separator();
220
221 // Stats
222 int pending = 0, approved = 0, rejected = 0, applied = 0;
223 for (const auto& proposal : proposals_) {
224 if (proposal.status == "pending")
225 pending++;
226 else if (proposal.status == "approved")
227 approved++;
228 else if (proposal.status == "rejected")
229 rejected++;
230 else if (proposal.status == "applied")
231 applied++;
232 }
233
234 ImGui::Text("Total: %zu", proposals_.size());
235 ImGui::SameLine();
236 ImGui::TextColored(colors_.proposal_pending, " | Pending: %d", pending);
237 ImGui::SameLine();
238 ImGui::TextColored(colors_.proposal_approved, " | Approved: %d", approved);
239 ImGui::SameLine();
240 ImGui::TextColored(colors_.proposal_rejected, " | Rejected: %d", rejected);
241 ImGui::SameLine();
242 ImGui::TextColored(colors_.proposal_applied, " | Applied: %d", applied);
243
244 ImGui::Separator();
245
246 // Proposal list
247 if (ImGui::BeginChild("ProposalList", ImVec2(0, 0), true)) {
248 for (size_t i = 0; i < proposals_.size(); ++i) {
249 // Filter
250 if (filter_pending_only_ && proposals_[i].status != "pending") {
251 continue;
252 }
253
254 if (search_filter_[0] != '\0') {
255 std::string search_lower = search_filter_;
256 std::string sender_lower = proposals_[i].sender;
257 std::string desc_lower = proposals_[i].description;
258 std::transform(search_lower.begin(), search_lower.end(),
259 search_lower.begin(), ::tolower);
260 std::transform(sender_lower.begin(), sender_lower.end(),
261 sender_lower.begin(), ::tolower);
262 std::transform(desc_lower.begin(), desc_lower.end(), desc_lower.begin(),
263 ::tolower);
264
265 if (sender_lower.find(search_lower) == std::string::npos &&
266 desc_lower.find(search_lower) == std::string::npos) {
267 continue;
268 }
269 }
270
272 }
273 }
274 ImGui::EndChild();
275}
276
278 int index) {
279 ImGui::PushID(index);
280
281 // Status indicator
282 ImVec4 status_color;
283 const char* status_icon;
284
285 if (entry.applied) {
286 status_color = colors_.sync_applied;
287 status_icon = "[✓]";
288 } else if (!entry.error_message.empty()) {
289 status_color = colors_.sync_error;
290 status_icon = "[✗]";
291 } else {
292 status_color = colors_.sync_pending;
293 status_icon = "[◷]";
294 }
295
296 ImGui::TextColored(status_color, "%s", status_icon);
297 ImGui::SameLine();
298
299 // Entry info
300 ImGui::Text("%s - %s (%s)", FormatTimestamp(entry.timestamp).c_str(),
301 entry.sender.c_str(), FormatFileSize(entry.diff_size).c_str());
302
303 // Details on hover or if enabled
304 if (show_sync_details_ || ImGui::IsItemHovered()) {
305 ImGui::Indent();
306 ImGui::TextWrapped("ROM Hash: %s", entry.rom_hash.substr(0, 16).c_str());
307 if (!entry.error_message.empty()) {
308 ImGui::TextColored(colors_.sync_error, "Error: %s",
309 entry.error_message.c_str());
310 }
311 ImGui::Unindent();
312 }
313
314 ImGui::Separator();
315 ImGui::PopID();
316}
317
319 int index) {
320 ImGui::PushID(index);
321
322 ImGui::BeginGroup();
323
324 // Thumbnail placeholder or actual image
325 if (show_snapshot_preview_ && entry.is_image && entry.texture_id) {
326 ImGui::Image(entry.texture_id, ImVec2(150, 150));
327 } else {
328 // Placeholder
329 ImGui::BeginChild("SnapshotPlaceholder", ImVec2(150, 150), true);
330 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 50);
331 ImGui::TextWrapped("%s", entry.snapshot_type.c_str());
332 ImGui::EndChild();
333 }
334
335 // Info
336 ImGui::TextWrapped("%s", entry.sender.c_str());
337 ImGui::Text("%s", FormatTimestamp(entry.timestamp).c_str());
338 ImGui::Text("%s", FormatFileSize(entry.data_size).c_str());
339
340 // Actions
341 if (ImGui::SmallButton("View")) {
342 selected_snapshot_ = index;
343 // TODO: Open snapshot viewer
344 }
345 ImGui::SameLine();
346 if (ImGui::SmallButton("Export")) {
347 // TODO: Export snapshot to file
348 }
349
350 ImGui::EndGroup();
351
352 ImGui::PopID();
353}
354
356 int index) {
357 ImGui::PushID(index);
358
359 // Status icon and color
360 const char* icon = GetProposalStatusIcon(entry.status);
361 ImVec4 color = GetProposalStatusColor(entry.status);
362
363 ImGui::TextColored(color, "%s", icon);
364 ImGui::SameLine();
365
366 // Collapsible header
367 bool is_open = ImGui::TreeNode(entry.description.c_str());
368
369 if (is_open) {
370 ImGui::Indent();
371
372 ImGui::Text("From: %s", entry.sender.c_str());
373 ImGui::Text("Time: %s", FormatTimestamp(entry.timestamp).c_str());
374 ImGui::Text("Status: %s", entry.status.c_str());
375
376 ImGui::Separator();
377
378 // Proposal data
379 ImGui::TextWrapped("%s", entry.proposal_data.c_str());
380
381 // Actions for pending proposals
382 if (entry.status == "pending") {
383 ImGui::Separator();
384 if (ImGui::Button("✓ Approve")) {
385 // TODO: Send approval to server
386 }
387 ImGui::SameLine();
388 if (ImGui::Button("✗ Reject")) {
389 // TODO: Send rejection to server
390 }
391 ImGui::SameLine();
392 if (ImGui::Button("â–ļ Apply Now")) {
393 // TODO: Execute proposal
394 }
395 }
396
397 ImGui::Unindent();
398 ImGui::TreePop();
399 }
400
401 ImGui::Separator();
402 ImGui::PopID();
403}
404
406 rom_syncs_.push_back(entry);
407}
408
410 snapshots_.push_back(entry);
411}
412
414 proposals_.push_back(entry);
415}
416
417void CollaborationPanel::UpdateProposalStatus(const std::string& proposal_id,
418 const std::string& status) {
419 for (auto& proposal : proposals_) {
420 if (proposal.proposal_id == proposal_id) {
421 proposal.status = status;
422 break;
423 }
424 }
425}
426
428 rom_syncs_.clear();
429 snapshots_.clear();
430 proposals_.clear();
431}
432
433ProposalEntry* CollaborationPanel::GetProposal(const std::string& proposal_id) {
434 for (auto& proposal : proposals_) {
435 if (proposal.proposal_id == proposal_id) {
436 return &proposal;
437 }
438 }
439 return nullptr;
440}
441
442std::string CollaborationPanel::FormatTimestamp(int64_t timestamp) {
443 std::time_t time = timestamp / 1000; // Convert ms to seconds
444 std::tm* tm = std::localtime(&time);
445
446 char buffer[32];
447 std::strftime(buffer, sizeof(buffer), "%H:%M:%S", tm);
448 return std::string(buffer);
449}
450
451std::string CollaborationPanel::FormatFileSize(size_t bytes) {
452 const char* units[] = {"B", "KB", "MB", "GB"};
453 int unit_index = 0;
454 double size = static_cast<double>(bytes);
455
456 while (size >= 1024.0 && unit_index < 3) {
457 size /= 1024.0;
458 unit_index++;
459 }
460
461 char buffer[32];
462 snprintf(buffer, sizeof(buffer), "%.1f %s", size, units[unit_index]);
463 return std::string(buffer);
464}
465
467 const std::string& status) {
468 if (status == "pending")
469 return "[◷]";
470 if (status == "approved")
471 return "[✓]";
472 if (status == "rejected")
473 return "[✗]";
474 if (status == "applied")
475 return "[âœĻ]";
476 return "[?]";
477}
478
479ImVec4 CollaborationPanel::GetProposalStatusColor(const std::string& status) {
480 if (status == "pending")
481 return colors_.proposal_pending;
482 if (status == "approved")
483 return colors_.proposal_approved;
484 if (status == "rejected")
485 return colors_.proposal_rejected;
486 if (status == "applied")
487 return colors_.proposal_applied;
488 return ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
489}
490
492 if (!version_mgr_) {
493 ImGui::TextWrapped("Version management not initialized");
494 return;
495 }
496
497 ImGui::TextWrapped("ROM Version History & Protection");
498 ImGui::Separator();
499
500 // Stats
501 auto stats = version_mgr_->GetStats();
502 ImGui::Text("Total Snapshots: %zu", stats.total_snapshots);
503 ImGui::SameLine();
504 ImGui::TextColored(colors_.sync_applied, "Safe Points: %zu",
505 stats.safe_points);
506 ImGui::SameLine();
507 ImGui::TextColored(colors_.sync_pending, "Auto-Backups: %zu",
508 stats.auto_backups);
509
510 ImGui::Text("Storage Used: %s",
511 FormatFileSize(stats.total_storage_bytes).c_str());
512
513 ImGui::Separator();
514
515 // Toolbar
516 if (ImGui::Button("💾 Create Checkpoint")) {
517 auto result =
518 version_mgr_->CreateSnapshot("Manual checkpoint", "user", true);
519 // TODO: Show result in UI
520 }
521 ImGui::SameLine();
522 if (ImGui::Button("đŸ›Ąī¸ Mark Current as Safe Point")) {
523 std::string current_hash = version_mgr_->GetCurrentHash();
524 // TODO: Find snapshot with this hash and mark as safe
525 }
526 ImGui::SameLine();
527 if (ImGui::Button("🔍 Check for Corruption")) {
528 auto result = version_mgr_->DetectCorruption();
529 // TODO: Show result
530 }
531
532 ImGui::Separator();
533
534 // Version list
535 if (ImGui::BeginChild("VersionList", ImVec2(0, 0), true)) {
536 auto snapshots = version_mgr_->GetSnapshots();
537
538 for (size_t i = 0; i < snapshots.size(); ++i) {
539 RenderVersionSnapshot(snapshots[i], i);
540 }
541 }
542 ImGui::EndChild();
543}
544
546 if (!approval_mgr_) {
547 ImGui::TextWrapped("Approval management not initialized");
548 return;
549 }
550
551 ImGui::TextWrapped("Proposal Approval System");
552 ImGui::Separator();
553
554 // Pending proposals that need votes
555 auto pending = approval_mgr_->GetPendingProposals();
556
557 if (pending.empty()) {
558 ImGui::TextWrapped("No proposals pending approval.");
559 return;
560 }
561
562 ImGui::Text("Pending Proposals: %zu", pending.size());
563 ImGui::Separator();
564
565 if (ImGui::BeginChild("ApprovalList", ImVec2(0, 0), true)) {
566 for (size_t i = 0; i < pending.size(); ++i) {
567 RenderApprovalProposal(pending[i], i);
568 }
569 }
570 ImGui::EndChild();
571}
572
574 int index) {
575 ImGui::PushID(index);
576
577 // Icon based on type
578 const char* icon;
579 ImVec4 color;
580
581 if (snapshot.is_safe_point) {
582 icon = "đŸ›Ąī¸";
583 color = colors_.sync_applied;
584 } else if (snapshot.is_checkpoint) {
585 icon = "💾";
586 color = colors_.proposal_approved;
587 } else {
588 icon = "📝";
589 color = colors_.sync_pending;
590 }
591
592 ImGui::TextColored(color, "%s", icon);
593 ImGui::SameLine();
594
595 // Collapsible header
596 bool is_open = ImGui::TreeNode(snapshot.description.c_str());
597
598 if (is_open) {
599 ImGui::Indent();
600
601 ImGui::Text("Creator: %s", snapshot.creator.c_str());
602 ImGui::Text("Time: %s", FormatTimestamp(snapshot.timestamp).c_str());
603 ImGui::Text("Hash: %s", snapshot.rom_hash.substr(0, 16).c_str());
604 ImGui::Text("Size: %s", FormatFileSize(snapshot.compressed_size).c_str());
605
606 if (snapshot.is_safe_point) {
607 ImGui::TextColored(colors_.sync_applied, "✓ Safe Point (Host Verified)");
608 }
609
610 ImGui::Separator();
611
612 // Actions
613 if (ImGui::Button("â†Šī¸ Restore This Version")) {
614 auto result = version_mgr_->RestoreSnapshot(snapshot.snapshot_id);
615 // TODO: Show result
616 }
617 ImGui::SameLine();
618 if (!snapshot.is_safe_point && ImGui::Button("đŸ›Ąī¸ Mark as Safe")) {
620 }
621 ImGui::SameLine();
622 if (!snapshot.is_safe_point && ImGui::Button("đŸ—‘ī¸ Delete")) {
624 }
625
626 ImGui::Unindent();
627 ImGui::TreePop();
628 }
629
630 ImGui::Separator();
631 ImGui::PopID();
632}
633
635 const net::ProposalApprovalManager::ApprovalStatus& status, int index) {
636 ImGui::PushID(index);
637
638 // Status indicator
639 ImGui::TextColored(colors_.proposal_pending, "[âŗ]");
640 ImGui::SameLine();
641
642 // Proposal ID (shortened)
643 std::string short_id = status.proposal_id.substr(0, 8);
644 bool is_open =
645 ImGui::TreeNode(absl::StrFormat("Proposal %s", short_id.c_str()).c_str());
646
647 if (is_open) {
648 ImGui::Indent();
649
650 ImGui::Text("Created: %s", FormatTimestamp(status.created_at).c_str());
651 ImGui::Text("Snapshot Before: %s",
652 status.snapshot_before.substr(0, 8).c_str());
653
654 ImGui::Separator();
655 ImGui::TextWrapped("Votes:");
656
657 for (const auto& [username, approved] : status.votes) {
658 ImVec4 vote_color =
659 approved ? colors_.proposal_approved : colors_.proposal_rejected;
660 const char* vote_icon = approved ? "✓" : "✗";
661 ImGui::TextColored(vote_color, " %s %s", vote_icon, username.c_str());
662 }
663
664 ImGui::Separator();
665
666 // Voting actions
667 if (ImGui::Button("✓ Approve")) {
668 // TODO: Send approval vote
669 // approval_mgr_->VoteOnProposal(status.proposal_id, "current_user",
670 // true);
671 }
672 ImGui::SameLine();
673 if (ImGui::Button("✗ Reject")) {
674 // TODO: Send rejection vote
675 // approval_mgr_->VoteOnProposal(status.proposal_id, "current_user",
676 // false);
677 }
678 ImGui::SameLine();
679 if (ImGui::Button("â†Šī¸ Rollback")) {
680 // Restore snapshot from before this proposal
682 }
683
684 ImGui::Unindent();
685 ImGui::TreePop();
686 }
687
688 ImGui::Separator();
689 ImGui::PopID();
690}
691
692} // namespace gui
693
694} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
std::string FormatFileSize(size_t bytes)
ProposalEntry * GetProposal(const std::string &proposal_id)
net::RomVersionManager * version_mgr_
void RenderVersionSnapshot(const net::RomSnapshot &snapshot, int index)
void RenderProposalEntry(const ProposalEntry &entry, int index)
std::string FormatTimestamp(int64_t timestamp)
void AddSnapshot(const SnapshotEntry &entry)
void AddProposal(const ProposalEntry &entry)
void UpdateProposalStatus(const std::string &proposal_id, const std::string &status)
const char * GetProposalStatusIcon(const std::string &status)
std::vector< ProposalEntry > proposals_
void RenderSnapshotEntry(const SnapshotEntry &entry, int index)
void AddRomSync(const RomSyncEntry &entry)
void RenderApprovalProposal(const net::ProposalApprovalManager::ApprovalStatus &status, int index)
struct yaze::gui::CollaborationPanel::@0 colors_
ImVec4 GetProposalStatusColor(const std::string &status)
void Render(bool *p_open=nullptr)
std::vector< RomSyncEntry > rom_syncs_
net::ProposalApprovalManager * approval_mgr_
void RenderRomSyncEntry(const RomSyncEntry &entry, int index)
std::vector< SnapshotEntry > snapshots_
void Initialize(Rom *rom, net::RomVersionManager *version_mgr, net::ProposalApprovalManager *approval_mgr)
Manages proposal approval workflow for collaborative sessions.
std::vector< ApprovalStatus > GetPendingProposals() const
Manages ROM versioning, snapshots, and rollback capabilities.
absl::StatusOr< std::string > CreateSnapshot(const std::string &description, const std::string &creator, bool is_checkpoint=false)
absl::StatusOr< bool > DetectCorruption()
absl::Status RestoreSnapshot(const std::string &snapshot_id)
absl::Status MarkAsSafePoint(const std::string &snapshot_id)
absl::Status DeleteSnapshot(const std::string &snapshot_id)
std::vector< RomSnapshot > GetSnapshots(bool safe_points_only=false) const
Represents an AI-generated proposal.
Represents a ROM synchronization event.
Represents a shared snapshot (image, map state, etc.)
Represents a versioned snapshot of ROM state.