feat: Add Collaboration Panel for enhanced team features
- Introduced `CollaborationPanel` class to manage ROM synchronization history, shared snapshots, and AI-generated proposals. - Implemented UI components for displaying and interacting with collaboration data, including filtering and status indicators. - Updated `README.md` to document the new collaboration features and their functionalities. - Added necessary header and implementation files for the collaboration panel functionality.
This commit is contained in:
@@ -895,6 +895,7 @@ The AI response appears in your chat history and can reference specific details
|
|||||||
- **Multimodal Vision Testing**: Comprehensive test suite for Gemini vision capabilities with screenshot integration
|
- **Multimodal Vision Testing**: Comprehensive test suite for Gemini vision capabilities with screenshot integration
|
||||||
- **AI-Controlled GUI Automation**: Natural language parsing (`AIActionParser`) and test script generation (`GuiActionGenerator`) for automated tile placement
|
- **AI-Controlled GUI Automation**: Natural language parsing (`AIActionParser`) and test script generation (`GuiActionGenerator`) for automated tile placement
|
||||||
- **gRPC Windows Build Optimization**: vcpkg integration for 10-20x faster Windows builds, removed abseil-cpp submodule
|
- **gRPC Windows Build Optimization**: vcpkg integration for 10-20x faster Windows builds, removed abseil-cpp submodule
|
||||||
|
- **Collaboration UI Panel**: New `CollaborationPanel` widget with ROM sync history, snapshot gallery, and proposal management
|
||||||
- **Improved Documentation**: Consolidated architecture, enhancement plans, and build instructions with JSON-first approach
|
- **Improved Documentation**: Consolidated architecture, enhancement plans, and build instructions with JSON-first approach
|
||||||
|
|
||||||
## 12. Troubleshooting
|
## 12. Troubleshooting
|
||||||
|
|||||||
445
src/app/gui/widgets/collaboration_panel.cc
Normal file
445
src/app/gui/widgets/collaboration_panel.cc
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
#include "app/gui/widgets/collaboration_panel.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
CollaborationPanel::CollaborationPanel()
|
||||||
|
: selected_tab_(0),
|
||||||
|
selected_rom_sync_(-1),
|
||||||
|
selected_snapshot_(-1),
|
||||||
|
selected_proposal_(-1),
|
||||||
|
show_sync_details_(false),
|
||||||
|
show_snapshot_preview_(true),
|
||||||
|
auto_scroll_(true),
|
||||||
|
filter_pending_only_(false) {
|
||||||
|
|
||||||
|
// Initialize search filter
|
||||||
|
search_filter_[0] = '\0';
|
||||||
|
|
||||||
|
// Initialize colors
|
||||||
|
colors_.sync_applied = ImVec4(0.2f, 0.8f, 0.2f, 1.0f);
|
||||||
|
colors_.sync_pending = ImVec4(0.8f, 0.8f, 0.2f, 1.0f);
|
||||||
|
colors_.sync_error = ImVec4(0.8f, 0.2f, 0.2f, 1.0f);
|
||||||
|
colors_.proposal_pending = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
||||||
|
colors_.proposal_approved = ImVec4(0.2f, 0.8f, 0.2f, 1.0f);
|
||||||
|
colors_.proposal_rejected = ImVec4(0.8f, 0.3f, 0.3f, 1.0f);
|
||||||
|
colors_.proposal_applied = ImVec4(0.2f, 0.6f, 0.8f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollaborationPanel::~CollaborationPanel() {
|
||||||
|
// Cleanup any OpenGL textures
|
||||||
|
for (auto& snapshot : snapshots_) {
|
||||||
|
if (snapshot.texture_id) {
|
||||||
|
// Note: Actual texture cleanup would depend on your renderer
|
||||||
|
// This is a placeholder
|
||||||
|
snapshot.texture_id = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::Render(bool* p_open) {
|
||||||
|
if (!ImGui::Begin("Collaboration", p_open, ImGuiWindowFlags_None)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tabs for different collaboration features
|
||||||
|
if (ImGui::BeginTabBar("CollaborationTabs")) {
|
||||||
|
if (ImGui::BeginTabItem("ROM Sync")) {
|
||||||
|
selected_tab_ = 0;
|
||||||
|
RenderRomSyncTab();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem("Snapshots")) {
|
||||||
|
selected_tab_ = 1;
|
||||||
|
RenderSnapshotsTab();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem("Proposals")) {
|
||||||
|
selected_tab_ = 2;
|
||||||
|
RenderProposalsTab();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::RenderRomSyncTab() {
|
||||||
|
ImGui::TextWrapped("ROM Synchronization History");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
if (ImGui::Button("Clear History")) {
|
||||||
|
rom_syncs_.clear();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Auto-scroll", &auto_scroll_);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Show Details", &show_sync_details_);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
int applied_count = 0;
|
||||||
|
int pending_count = 0;
|
||||||
|
int error_count = 0;
|
||||||
|
|
||||||
|
for (const auto& sync : rom_syncs_) {
|
||||||
|
if (sync.applied) applied_count++;
|
||||||
|
else if (!sync.error_message.empty()) error_count++;
|
||||||
|
else pending_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Total: %zu | ", rom_syncs_.size());
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.sync_applied, "Applied: %d", applied_count);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.sync_pending, "Pending: %d", pending_count);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.sync_error, "Errors: %d", error_count);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Sync list
|
||||||
|
if (ImGui::BeginChild("SyncList", ImVec2(0, 0), true)) {
|
||||||
|
for (size_t i = 0; i < rom_syncs_.size(); ++i) {
|
||||||
|
RenderRomSyncEntry(rom_syncs_[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
|
||||||
|
ImGui::SetScrollHereY(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::RenderSnapshotsTab() {
|
||||||
|
ImGui::TextWrapped("Shared Snapshots Gallery");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
if (ImGui::Button("Clear Gallery")) {
|
||||||
|
snapshots_.clear();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Show Preview", &show_snapshot_preview_);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputText("Search", search_filter_, sizeof(search_filter_));
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Snapshot grid
|
||||||
|
if (ImGui::BeginChild("SnapshotGrid", ImVec2(0, 0), true)) {
|
||||||
|
float thumbnail_size = 150.0f;
|
||||||
|
float padding = 10.0f;
|
||||||
|
float cell_size = thumbnail_size + padding;
|
||||||
|
|
||||||
|
int columns = std::max(1, (int)((ImGui::GetContentRegionAvail().x) / cell_size));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < snapshots_.size(); ++i) {
|
||||||
|
// Filter by search
|
||||||
|
if (search_filter_[0] != '\0') {
|
||||||
|
std::string search_lower = search_filter_;
|
||||||
|
std::string sender_lower = snapshots_[i].sender;
|
||||||
|
std::transform(search_lower.begin(), search_lower.end(), search_lower.begin(), ::tolower);
|
||||||
|
std::transform(sender_lower.begin(), sender_lower.end(), sender_lower.begin(), ::tolower);
|
||||||
|
|
||||||
|
if (sender_lower.find(search_lower) == std::string::npos &&
|
||||||
|
snapshots_[i].snapshot_type.find(search_lower) == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSnapshotEntry(snapshots_[i], i);
|
||||||
|
|
||||||
|
// Grid layout
|
||||||
|
if ((i + 1) % columns != 0 && i < snapshots_.size() - 1) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::RenderProposalsTab() {
|
||||||
|
ImGui::TextWrapped("AI Proposals & Suggestions");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
if (ImGui::Button("Clear All")) {
|
||||||
|
proposals_.clear();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Pending Only", &filter_pending_only_);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputText("Search", search_filter_, sizeof(search_filter_));
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
int pending = 0, approved = 0, rejected = 0, applied = 0;
|
||||||
|
for (const auto& proposal : proposals_) {
|
||||||
|
if (proposal.status == "pending") pending++;
|
||||||
|
else if (proposal.status == "approved") approved++;
|
||||||
|
else if (proposal.status == "rejected") rejected++;
|
||||||
|
else if (proposal.status == "applied") applied++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Total: %zu", proposals_.size());
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.proposal_pending, " | Pending: %d", pending);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.proposal_approved, " | Approved: %d", approved);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.proposal_rejected, " | Rejected: %d", rejected);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(colors_.proposal_applied, " | Applied: %d", applied);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Proposal list
|
||||||
|
if (ImGui::BeginChild("ProposalList", ImVec2(0, 0), true)) {
|
||||||
|
for (size_t i = 0; i < proposals_.size(); ++i) {
|
||||||
|
// Filter
|
||||||
|
if (filter_pending_only_ && proposals_[i].status != "pending") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search_filter_[0] != '\0') {
|
||||||
|
std::string search_lower = search_filter_;
|
||||||
|
std::string sender_lower = proposals_[i].sender;
|
||||||
|
std::string desc_lower = proposals_[i].description;
|
||||||
|
std::transform(search_lower.begin(), search_lower.end(), search_lower.begin(), ::tolower);
|
||||||
|
std::transform(sender_lower.begin(), sender_lower.end(), sender_lower.begin(), ::tolower);
|
||||||
|
std::transform(desc_lower.begin(), desc_lower.end(), desc_lower.begin(), ::tolower);
|
||||||
|
|
||||||
|
if (sender_lower.find(search_lower) == std::string::npos &&
|
||||||
|
desc_lower.find(search_lower) == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderProposalEntry(proposals_[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::RenderRomSyncEntry(const RomSyncEntry& entry, int index) {
|
||||||
|
ImGui::PushID(index);
|
||||||
|
|
||||||
|
// Status indicator
|
||||||
|
ImVec4 status_color;
|
||||||
|
const char* status_icon;
|
||||||
|
|
||||||
|
if (entry.applied) {
|
||||||
|
status_color = colors_.sync_applied;
|
||||||
|
status_icon = "[✓]";
|
||||||
|
} else if (!entry.error_message.empty()) {
|
||||||
|
status_color = colors_.sync_error;
|
||||||
|
status_icon = "[✗]";
|
||||||
|
} else {
|
||||||
|
status_color = colors_.sync_pending;
|
||||||
|
status_icon = "[◷]";
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextColored(status_color, "%s", status_icon);
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
// Entry info
|
||||||
|
ImGui::Text("%s - %s (%s)",
|
||||||
|
FormatTimestamp(entry.timestamp).c_str(),
|
||||||
|
entry.sender.c_str(),
|
||||||
|
FormatFileSize(entry.diff_size).c_str());
|
||||||
|
|
||||||
|
// Details on hover or if enabled
|
||||||
|
if (show_sync_details_ || ImGui::IsItemHovered()) {
|
||||||
|
ImGui::Indent();
|
||||||
|
ImGui::TextWrapped("ROM Hash: %s", entry.rom_hash.substr(0, 16).c_str());
|
||||||
|
if (!entry.error_message.empty()) {
|
||||||
|
ImGui::TextColored(colors_.sync_error, "Error: %s", entry.error_message.c_str());
|
||||||
|
}
|
||||||
|
ImGui::Unindent();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::RenderSnapshotEntry(const SnapshotEntry& entry, int index) {
|
||||||
|
ImGui::PushID(index);
|
||||||
|
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
// Thumbnail placeholder or actual image
|
||||||
|
if (show_snapshot_preview_ && entry.is_image && entry.texture_id) {
|
||||||
|
ImGui::Image(entry.texture_id, ImVec2(150, 150));
|
||||||
|
} else {
|
||||||
|
// Placeholder
|
||||||
|
ImGui::BeginChild("SnapshotPlaceholder", ImVec2(150, 150), true);
|
||||||
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 50);
|
||||||
|
ImGui::TextWrapped("%s", entry.snapshot_type.c_str());
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info
|
||||||
|
ImGui::TextWrapped("%s", entry.sender.c_str());
|
||||||
|
ImGui::Text("%s", FormatTimestamp(entry.timestamp).c_str());
|
||||||
|
ImGui::Text("%s", FormatFileSize(entry.data_size).c_str());
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
if (ImGui::SmallButton("View")) {
|
||||||
|
selected_snapshot_ = index;
|
||||||
|
// TODO: Open snapshot viewer
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton("Export")) {
|
||||||
|
// TODO: Export snapshot to file
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::RenderProposalEntry(const ProposalEntry& entry, int index) {
|
||||||
|
ImGui::PushID(index);
|
||||||
|
|
||||||
|
// Status icon and color
|
||||||
|
const char* icon = GetProposalStatusIcon(entry.status);
|
||||||
|
ImVec4 color = GetProposalStatusColor(entry.status);
|
||||||
|
|
||||||
|
ImGui::TextColored(color, "%s", icon);
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
// Collapsible header
|
||||||
|
bool is_open = ImGui::TreeNode(entry.description.c_str());
|
||||||
|
|
||||||
|
if (is_open) {
|
||||||
|
ImGui::Indent();
|
||||||
|
|
||||||
|
ImGui::Text("From: %s", entry.sender.c_str());
|
||||||
|
ImGui::Text("Time: %s", FormatTimestamp(entry.timestamp).c_str());
|
||||||
|
ImGui::Text("Status: %s", entry.status.c_str());
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Proposal data
|
||||||
|
ImGui::TextWrapped("%s", entry.proposal_data.c_str());
|
||||||
|
|
||||||
|
// Actions for pending proposals
|
||||||
|
if (entry.status == "pending") {
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::Button("✓ Approve")) {
|
||||||
|
// TODO: Send approval to server
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("✗ Reject")) {
|
||||||
|
// TODO: Send rejection to server
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("▶ Apply Now")) {
|
||||||
|
// TODO: Execute proposal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Unindent();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::AddRomSync(const RomSyncEntry& entry) {
|
||||||
|
rom_syncs_.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::AddSnapshot(const SnapshotEntry& entry) {
|
||||||
|
snapshots_.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::AddProposal(const ProposalEntry& entry) {
|
||||||
|
proposals_.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::UpdateProposalStatus(const std::string& proposal_id, const std::string& status) {
|
||||||
|
for (auto& proposal : proposals_) {
|
||||||
|
if (proposal.proposal_id == proposal_id) {
|
||||||
|
proposal.status = status;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollaborationPanel::Clear() {
|
||||||
|
rom_syncs_.clear();
|
||||||
|
snapshots_.clear();
|
||||||
|
proposals_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProposalEntry* CollaborationPanel::GetProposal(const std::string& proposal_id) {
|
||||||
|
for (auto& proposal : proposals_) {
|
||||||
|
if (proposal.proposal_id == proposal_id) {
|
||||||
|
return &proposal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CollaborationPanel::FormatTimestamp(int64_t timestamp) {
|
||||||
|
std::time_t time = timestamp / 1000; // Convert ms to seconds
|
||||||
|
std::tm* tm = std::localtime(&time);
|
||||||
|
|
||||||
|
char buffer[32];
|
||||||
|
std::strftime(buffer, sizeof(buffer), "%H:%M:%S", tm);
|
||||||
|
return std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CollaborationPanel::FormatFileSize(size_t bytes) {
|
||||||
|
const char* units[] = {"B", "KB", "MB", "GB"};
|
||||||
|
int unit_index = 0;
|
||||||
|
double size = static_cast<double>(bytes);
|
||||||
|
|
||||||
|
while (size >= 1024.0 && unit_index < 3) {
|
||||||
|
size /= 1024.0;
|
||||||
|
unit_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[32];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%.1f %s", size, units[unit_index]);
|
||||||
|
return std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* CollaborationPanel::GetProposalStatusIcon(const std::string& status) {
|
||||||
|
if (status == "pending") return "[◷]";
|
||||||
|
if (status == "approved") return "[✓]";
|
||||||
|
if (status == "rejected") return "[✗]";
|
||||||
|
if (status == "applied") return "[✦]";
|
||||||
|
return "[?]";
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec4 CollaborationPanel::GetProposalStatusColor(const std::string& status) {
|
||||||
|
if (status == "pending") return colors_.proposal_pending;
|
||||||
|
if (status == "approved") return colors_.proposal_approved;
|
||||||
|
if (status == "rejected") return colors_.proposal_rejected;
|
||||||
|
if (status == "applied") return colors_.proposal_applied;
|
||||||
|
return ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
169
src/app/gui/widgets/collaboration_panel.h
Normal file
169
src/app/gui/widgets/collaboration_panel.h
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_WIDGETS_COLLABORATION_PANEL_H_
|
||||||
|
#define YAZE_APP_GUI_WIDGETS_COLLABORATION_PANEL_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#ifdef YAZE_WITH_JSON
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct RomSyncEntry
|
||||||
|
* @brief Represents a ROM synchronization event
|
||||||
|
*/
|
||||||
|
struct RomSyncEntry {
|
||||||
|
std::string sync_id;
|
||||||
|
std::string sender;
|
||||||
|
std::string rom_hash;
|
||||||
|
int64_t timestamp;
|
||||||
|
size_t diff_size;
|
||||||
|
bool applied;
|
||||||
|
std::string error_message;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct SnapshotEntry
|
||||||
|
* @brief Represents a shared snapshot (image, map state, etc.)
|
||||||
|
*/
|
||||||
|
struct SnapshotEntry {
|
||||||
|
std::string snapshot_id;
|
||||||
|
std::string sender;
|
||||||
|
std::string snapshot_type; // "screenshot", "map_state", "tile_data", etc.
|
||||||
|
int64_t timestamp;
|
||||||
|
size_t data_size;
|
||||||
|
std::vector<uint8_t> data; // Base64-decoded image or JSON data
|
||||||
|
bool is_image;
|
||||||
|
|
||||||
|
// For images: decoded texture data
|
||||||
|
void* texture_id = nullptr;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct ProposalEntry
|
||||||
|
* @brief Represents an AI-generated proposal
|
||||||
|
*/
|
||||||
|
struct ProposalEntry {
|
||||||
|
std::string proposal_id;
|
||||||
|
std::string sender;
|
||||||
|
std::string description;
|
||||||
|
std::string proposal_data; // JSON or formatted text
|
||||||
|
std::string status; // "pending", "approved", "rejected", "applied"
|
||||||
|
int64_t timestamp;
|
||||||
|
|
||||||
|
#ifdef YAZE_WITH_JSON
|
||||||
|
nlohmann::json metadata;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CollaborationPanel
|
||||||
|
* @brief ImGui panel for collaboration features
|
||||||
|
*
|
||||||
|
* Displays:
|
||||||
|
* - ROM sync history and status
|
||||||
|
* - Shared snapshots gallery
|
||||||
|
* - Proposal management and voting
|
||||||
|
*/
|
||||||
|
class CollaborationPanel {
|
||||||
|
public:
|
||||||
|
CollaborationPanel();
|
||||||
|
~CollaborationPanel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the collaboration panel
|
||||||
|
*/
|
||||||
|
void Render(bool* p_open = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a ROM sync event
|
||||||
|
*/
|
||||||
|
void AddRomSync(const RomSyncEntry& entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a snapshot
|
||||||
|
*/
|
||||||
|
void AddSnapshot(const SnapshotEntry& entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a proposal
|
||||||
|
*/
|
||||||
|
void AddProposal(const ProposalEntry& entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update proposal status
|
||||||
|
*/
|
||||||
|
void UpdateProposalStatus(const std::string& proposal_id, const std::string& status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all collaboration data
|
||||||
|
*/
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get proposal by ID
|
||||||
|
*/
|
||||||
|
ProposalEntry* GetProposal(const std::string& proposal_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RenderRomSyncTab();
|
||||||
|
void RenderSnapshotsTab();
|
||||||
|
void RenderProposalsTab();
|
||||||
|
|
||||||
|
void RenderRomSyncEntry(const RomSyncEntry& entry, int index);
|
||||||
|
void RenderSnapshotEntry(const SnapshotEntry& entry, int index);
|
||||||
|
void RenderProposalEntry(const ProposalEntry& entry, int index);
|
||||||
|
|
||||||
|
// Tab selection
|
||||||
|
int selected_tab_;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
std::vector<RomSyncEntry> rom_syncs_;
|
||||||
|
std::vector<SnapshotEntry> snapshots_;
|
||||||
|
std::vector<ProposalEntry> proposals_;
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
int selected_rom_sync_;
|
||||||
|
int selected_snapshot_;
|
||||||
|
int selected_proposal_;
|
||||||
|
bool show_sync_details_;
|
||||||
|
bool show_snapshot_preview_;
|
||||||
|
bool auto_scroll_;
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
char search_filter_[256];
|
||||||
|
bool filter_pending_only_;
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
struct {
|
||||||
|
ImVec4 sync_applied;
|
||||||
|
ImVec4 sync_pending;
|
||||||
|
ImVec4 sync_error;
|
||||||
|
ImVec4 proposal_pending;
|
||||||
|
ImVec4 proposal_approved;
|
||||||
|
ImVec4 proposal_rejected;
|
||||||
|
ImVec4 proposal_applied;
|
||||||
|
} colors_;
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
std::string FormatTimestamp(int64_t timestamp);
|
||||||
|
std::string FormatFileSize(size_t bytes);
|
||||||
|
const char* GetProposalStatusIcon(const std::string& status);
|
||||||
|
ImVec4 GetProposalStatusColor(const std::string& status);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_WIDGETS_COLLABORATION_PANEL_H_
|
||||||
Reference in New Issue
Block a user