diff --git a/src/project_manager.py b/src/project_manager.py
index 97f24dc..f7441b0 100644
--- a/src/project_manager.py
+++ b/src/project_manager.py
@@ -48,6 +48,8 @@ def _unique_filename(base: str, exclude: set) -> str:
class ProjectManager:
"""Manages project and saved request persistence."""
+ _INDEX_FILE = '_index.json'
+
def __init__(self):
self.data_dir = Path(GLib.get_user_data_dir()) / 'cz.bugsy.roster'
self.projects_dir = self.data_dir / 'projects'
@@ -80,8 +82,29 @@ class ProjectManager:
return self._load_from_dirs()
+ def _load_project_order(self) -> List[str]:
+ """Load project order from _index.json. Returns list of project IDs."""
+ index_file = self.projects_dir / self._INDEX_FILE
+ if not index_file.exists():
+ return []
+ try:
+ with open(index_file, 'r') as f:
+ return json.load(f)
+ except Exception as e:
+ logger.error("Error loading project order: %s", e)
+ return []
+
+ def _save_project_order(self, project_ids: List[str]):
+ """Save project order to _index.json."""
+ index_file = self.projects_dir / self._INDEX_FILE
+ try:
+ with open(index_file, 'w') as f:
+ json.dump(project_ids, f, indent=2)
+ except Exception as e:
+ logger.error("Error saving project order: %s", e)
+
def _load_from_dirs(self) -> List[Project]:
- projects = []
+ projects_by_id = {}
self._project_dirs.clear()
self._request_filenames.clear()
@@ -145,12 +168,24 @@ class ProjectManager:
environments=[Environment.from_dict(e) for e in meta.get('environments', [])],
sensitive_variables=meta.get('sensitive_variables', []),
)
- projects.append(project)
+ projects_by_id[project.id] = project
self._project_dirs[project.id] = proj_dir.name
except Exception as e:
logger.error("Error loading project %s: %s", proj_dir, e)
+ # Apply saved order, then append any projects not yet in the order file
+ ordered_ids = self._load_project_order()
+ projects = []
+ seen: set = set()
+ for pid in ordered_ids:
+ if pid in projects_by_id and pid not in seen:
+ projects.append(projects_by_id[pid])
+ seen.add(pid)
+ for pid, project in projects_by_id.items():
+ if pid not in seen:
+ projects.append(project)
+
self._projects_cache = projects
return projects
@@ -299,6 +334,10 @@ class ProjectManager:
)
projects.append(project)
self._save_project(project)
+ if not self._legacy_mode:
+ order = self._load_project_order()
+ order.append(project.id)
+ self._save_project_order(order)
return project
def update_project(self, project_id: str, new_name: str = None, new_icon: str = None):
@@ -332,6 +371,9 @@ class ProjectManager:
if self._legacy_mode:
self._save_all_legacy()
+ else:
+ order = self._load_project_order()
+ self._save_project_order([pid for pid in order if pid != project_id])
def on_secrets_deleted(success):
if callback:
@@ -394,6 +436,52 @@ class ProjectManager:
self._save_project(p)
break
+ def reorder_project(self, project_id: str, direction: int):
+ """Move a project up (direction=-1) or down (direction=+1)."""
+ projects = self.load_projects()
+ idx = next((i for i, p in enumerate(projects) if p.id == project_id), None)
+ if idx is None:
+ return
+ new_idx = idx + direction
+ if new_idx < 0 or new_idx >= len(projects):
+ return
+ projects[idx], projects[new_idx] = projects[new_idx], projects[idx]
+ self._projects_cache = projects
+ if not self._legacy_mode:
+ self._save_project_order([p.id for p in projects])
+
+ def reorder_request(self, project_id: str, request_id: str, direction: int):
+ """Move a request up (direction=-1) or down (direction=+1) within its project."""
+ projects = self.load_projects()
+ for p in projects:
+ if p.id != project_id:
+ continue
+ idx = next((i for i, r in enumerate(p.requests) if r.id == request_id), None)
+ if idx is None:
+ return
+ new_idx = idx + direction
+ if new_idx < 0 or new_idx >= len(p.requests):
+ return
+ p.requests[idx], p.requests[new_idx] = p.requests[new_idx], p.requests[idx]
+ self._save_project(p)
+ return
+
+ def move_request_to_project(self, request_id: str, source_project_id: str, target_project_id: str):
+ """Move a request from one project to another."""
+ projects = self.load_projects()
+ source = next((p for p in projects if p.id == source_project_id), None)
+ target = next((p for p in projects if p.id == target_project_id), None)
+ if not source or not target:
+ return
+ req = next((r for r in source.requests if r.id == request_id), None)
+ if not req:
+ return
+ # Write to target first so the file is never lost if source save fails
+ target.requests.append(req)
+ self._save_project(target)
+ source.requests = [r for r in source.requests if r.id != request_id]
+ self._save_project(source)
+
def add_environment(self, project_id: str, name: str) -> Environment:
projects = self.load_projects()
now = datetime.now(timezone.utc).isoformat()
diff --git a/src/widgets/project-item.ui b/src/widgets/project-item.ui
index 5057023..d0bcd4b 100644
--- a/src/widgets/project-item.ui
+++ b/src/widgets/project-item.ui
@@ -4,6 +4,16 @@