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