From d1667f9e0dc2e240169171c2878dd5fb0043cff9 Mon Sep 17 00:00:00 2001 From: Claude Project Manager Date: Tue, 8 Jul 2025 10:33:20 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20Aktivit=C3=A4tenstatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE_PROJECT_README.md | 7 +- data/projects.json | 4 +- gui/main_window.py | 6 ++ gui/project_tile.py | 166 +++++++++++++++++++++++++++++++------- services/activity_sync.py | 61 +++++++++++--- 5 files changed, 196 insertions(+), 48 deletions(-) diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index 6dfb8d0..86b0871 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main` -- **Files**: 220 files -- **Size**: 76.9 MB -- **Last Modified**: 2025-07-07 22:34 +- **Files**: 227 files +- **Size**: 77.0 MB +- **Last Modified**: 2025-07-08 08:19 ## Technology Stack @@ -196,3 +196,4 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-07-07 21:50:23 - README updated on 2025-07-07 22:12:28 - README updated on 2025-07-07 22:34:45 +- README updated on 2025-07-08 08:19:16 diff --git a/data/projects.json b/data/projects.json index 1e61693..7fb8d89 100644 --- a/data/projects.json +++ b/data/projects.json @@ -51,12 +51,12 @@ "name": "ClaudeProjectManager-main", "path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main", "created_at": "2025-07-07T21:38:23.820122", - "last_accessed": "2025-07-07T22:34:45.602409", + "last_accessed": "2025-07-08T08:19:16.600227", "readme_path": "C:/Users/hendr/Desktop/IntelSight/ClaudeProjectManager-main\\CLAUDE_PROJECT_README.md", "description": "", "tags": [], "gitea_repo": null } ], - "last_updated": "2025-07-07T22:34:45.602409" + "last_updated": "2025-07-08T08:19:16.600227" } \ No newline at end of file diff --git a/gui/main_window.py b/gui/main_window.py index 9f63efb..5ba1409 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -2966,10 +2966,16 @@ class MainWindow: def update_activity_status(self): """Periodic update of activity status""" from services.activity_sync import activity_service + from utils.logger import logger + + logger.debug("Periodic activity status update triggered") if activity_service.connected: # Update display with current activities + logger.debug(f"Updating activity display with {len(activity_service.activities)} activities") self.update_activity_display(activity_service.activities) + else: + logger.debug("Activity service not connected, skipping update") # Schedule next update self.root.after(5000, self.update_activity_status) # Update every 5 seconds diff --git a/gui/project_tile.py b/gui/project_tile.py index 4b507bf..51d2269 100644 --- a/gui/project_tile.py +++ b/gui/project_tile.py @@ -25,7 +25,9 @@ class ProjectTile(ctk.CTkFrame): width=TILE_SIZE['width'], height=TILE_SIZE['height'], fg_color=COLORS['bg_vps'] if is_vps else COLORS['bg_tile'], - corner_radius=10 + corner_radius=10, + border_width=3, + border_color=COLORS['bg_vps'] if is_vps else COLORS['bg_tile'] ) self.project = project @@ -38,6 +40,9 @@ class ProjectTile(ctk.CTkFrame): self.is_vps = is_vps self.is_running = is_running self.is_selected = False + self.activity_animation_id = None + self.activity_pulse_value = 0 + self.has_team_activity = False self.grid_propagate(False) self.setup_ui() @@ -582,39 +587,52 @@ pause def _start_activity(self): """Start activity for this project""" - # Use main_window reference if available - if hasattr(self, 'main_window') and self.main_window: - self.main_window.start_activity(self.project) - return - - # Otherwise try to find in widget hierarchy - widget = self - while widget: - if hasattr(widget, 'start_activity'): - widget.start_activity(self.project) - return - widget = widget.master if hasattr(widget, 'master') else None + from services.activity_sync import activity_service - # If not found, log error - logger.error("Could not find start_activity method in widget hierarchy") + logger.info(f"Starting activity for project: {self.project.name}") + + # Update UI optimistically immediately + self.update_activity_status(True, activity_service.user_name, True) + + success = activity_service.start_activity( + self.project.name, + self.project.path, + self.project.description if hasattr(self.project, 'description') else "" + ) + + logger.info(f"Activity start result for {self.project.name}: success={success}") + + if not success: + # Revert on failure + logger.error(f"Failed to start activity for {self.project.name}, reverting UI") + self.update_activity_status(False) + from tkinter import messagebox + messagebox.showerror( + "Fehler", + "Aktivität konnte nicht gestartet werden." + ) def _stop_activity(self): """Stop current activity""" - # Use main_window reference if available - if hasattr(self, 'main_window') and self.main_window: - self.main_window.stop_activity() - return - - # Otherwise try to find in widget hierarchy - widget = self - while widget: - if hasattr(widget, 'stop_activity'): - widget.stop_activity() - return - widget = widget.master if hasattr(widget, 'master') else None + from services.activity_sync import activity_service - # If not found, log error - logger.error("Could not find stop_activity method in widget hierarchy") + logger.info(f"Stopping activity for project: {self.project.name}") + + # Update UI optimistically immediately + self.update_activity_status(False) + + success = activity_service.stop_activity() + + logger.info(f"Activity stop result: success={success}") + + if not success: + # Revert on failure - check if we're still the active project + current = activity_service.get_current_activity() + if current and current.get('projectName') == self.project.name: + logger.error(f"Failed to stop activity for {self.project.name}, reverting UI") + self.update_activity_status(True, activity_service.user_name, True) + else: + logger.error(f"Failed to stop activity, but no current activity found") def _toggle_activity(self): """Toggle activity for this project""" @@ -653,7 +671,17 @@ pause def update_activity_status(self, is_active: bool = False, user_name: str = None, is_own_activity: bool = False): """Update activity indicator on tile""" + logger.debug(f"update_activity_status called for {self.project.name}: is_active={is_active}, user_name={user_name}, is_own_activity={is_own_activity}") + if is_active: + # Start border animation for team activity + if not is_own_activity: + self.has_team_activity = True + self._start_activity_animation() + else: + self.has_team_activity = False + self._stop_activity_animation() + # Show indicator with appropriate color if is_own_activity: # Green for own activity @@ -680,6 +708,10 @@ pause # Hide indicator self.activity_indicator.pack_forget() + # Stop border animation + self.has_team_activity = False + self._stop_activity_animation() + # Update activity button if exists if hasattr(self, 'activity_button'): self.activity_button.configure(text="▶") @@ -725,7 +757,16 @@ pause """Check if this project has active users""" from services.activity_sync import activity_service - # Get all activities for this project + logger.debug(f"check_activity called for project: {self.project.name}") + + # First check if this is our own current activity + current_activity = activity_service.get_current_activity() + is_own_current = (current_activity and + current_activity.get('projectName') == self.project.name) + + logger.debug(f"Current activity check - is_own_current: {is_own_current}, current_activity: {current_activity}") + + # Get all activities for this project from server active_users = [] is_own_activity = False has_other_users = False @@ -740,13 +781,76 @@ pause else: has_other_users = True + logger.debug(f"Server activities - active_users: {active_users}, is_own_activity: {is_own_activity}, has_other_users: {has_other_users}") + + # If we have a local current activity, ensure it's included + if is_own_current: + is_own_activity = True + if activity_service.user_name not in active_users: + active_users.append(activity_service.user_name) + logger.debug(f"Added local user to active_users: {activity_service.user_name}") + if active_users: # Show indicator with all active users user_text = ", ".join(active_users) # If both own and others are active, show as others (orange) to indicate collaboration - self.update_activity_status(True, user_text, is_own_activity and not has_other_users) + final_is_own = is_own_activity and not has_other_users + logger.info(f"Updating activity status for {self.project.name}: active=True, users={user_text}, is_own={final_is_own}") + self.update_activity_status(True, user_text, final_is_own) else: + logger.info(f"Updating activity status for {self.project.name}: active=False") self.update_activity_status(False) + + def _start_activity_animation(self): + """Start animated border for team activity""" + if self.activity_animation_id: + return # Animation already running + + def animate(): + if not self.has_team_activity: + self.activity_animation_id = None + return + + # Calculate pulsing color + self.activity_pulse_value = (self.activity_pulse_value + 5) % 100 + pulse = abs(50 - self.activity_pulse_value) / 50 # 0 to 1 pulsing + + # Interpolate between orange and a brighter orange + base_color = COLORS['accent_warning'] # Orange + bright_factor = 0.3 + (0.7 * pulse) # Pulse between 0.3 and 1.0 brightness + + # Create pulsing border color + if base_color.startswith('#'): + # Convert hex to RGB, apply brightness, convert back + r = int(base_color[1:3], 16) + g = int(base_color[3:5], 16) + b = int(base_color[5:7], 16) + + # Apply brightness + r = min(255, int(r + (255 - r) * (1 - bright_factor))) + g = min(255, int(g + (255 - g) * (1 - bright_factor))) + b = min(255, int(b + (255 - b) * (1 - bright_factor))) + + pulse_color = f"#{r:02x}{g:02x}{b:02x}" + else: + pulse_color = base_color + + self.configure(border_color=pulse_color) + + # Continue animation + self.activity_animation_id = self.after(50, animate) + + animate() + + def _stop_activity_animation(self): + """Stop animated border""" + if self.activity_animation_id: + self.after_cancel(self.activity_animation_id) + self.activity_animation_id = None + + # Reset border to default + default_color = COLORS['bg_vps'] if self.is_vps else COLORS['bg_tile'] + self.configure(border_color=default_color) class AddProjectTile(ctk.CTkFrame): """Special tile for adding new projects""" diff --git a/services/activity_sync.py b/services/activity_sync.py index d34609d..d9c83fd 100644 --- a/services/activity_sync.py +++ b/services/activity_sync.py @@ -85,10 +85,23 @@ class ActivitySyncService: @self.sio.event def activities_update(data): """Handle activities update from server""" - self.activities = data - logger.debug(f"Received activities update: {len(data)} activities") + logger.debug(f"Received raw activities update: {len(data)} items") + + # Filter out inactive entries and ensure we only keep active ones + active_activities = [ + activity for activity in data + if activity.get('isActive', False) + ] + + # Log details about the activities + for activity in active_activities: + logger.debug(f"Active: {activity.get('userName')} on {activity.get('projectName')}") + + self.activities = active_activities + logger.info(f"Activities update: {len(active_activities)} active (of {len(data)} total)") + if self.on_activities_update: - self.on_activities_update(data) + self.on_activities_update(active_activities) @self.sio.event def connect_error(data): @@ -131,36 +144,57 @@ class ActivitySyncService: def start_activity(self, project_name: str, project_path: str, description: str = ""): """Start a new activity""" + logger.debug(f"start_activity called for: {project_name}") + if not self.connected or not self.sio: logger.warning("Not connected to activity server") return False try: + # Set current activity immediately + self.current_activity = { + 'projectName': project_name, + 'projectPath': project_path, + 'userId': self.user_id, + 'userName': self.user_name, + 'isActive': True + } + logger.debug(f"Set current_activity: {self.current_activity}") + + # Emit to server self.sio.emit('activity-start', { 'projectName': project_name, 'projectPath': project_path, 'description': description }) - self.current_activity = { - 'projectName': project_name, - 'projectPath': project_path - } + logger.info(f"Started activity for project: {project_name}") return True except Exception as e: logger.error(f"Failed to start activity: {e}") + self.current_activity = None return False def stop_activity(self): """Stop the current activity""" + logger.debug(f"stop_activity called, current: {self.current_activity}") + if not self.connected or not self.sio: logger.warning("Not connected to activity server") return False try: - self.sio.emit('activity-stop') + # Store project name for logging + project_name = self.current_activity.get('projectName') if self.current_activity else 'None' + + # Clear current activity immediately self.current_activity = None - logger.info("Stopped current activity") + logger.debug("Cleared current_activity") + + # Emit to server + self.sio.emit('activity-stop') + + logger.info(f"Stopped activity for project: {project_name}") return True except Exception as e: logger.error(f"Failed to stop activity: {e}") @@ -179,7 +213,9 @@ class ActivitySyncService: ) if response.status_code == 200: data = response.json() - return data.get('activities', []) + all_activities = data.get('activities', []) + # Filter to only return active activities + return [a for a in all_activities if a.get('isActive', False)] else: logger.error(f"Failed to fetch activities: {response.status_code}") return [] @@ -204,17 +240,18 @@ class ActivitySyncService: def get_current_activity(self) -> Optional[Dict]: """Get current user's activity""" + logger.debug(f"get_current_activity called, returning: {self.current_activity}") return self.current_activity def _fetch_initial_activities(self): """Fetch initial activities after connection""" try: - activities = self.get_activities() + activities = self.get_activities() # Already filtered to only active if activities: self.activities = activities if self.on_activities_update: self.on_activities_update(activities) - logger.info(f"Fetched {len(activities)} initial activities") + logger.info(f"Fetched {len(activities)} active activities") except Exception as e: logger.error(f"Failed to fetch initial activities: {e}")