Compare commits
No commits in common. "bea3fe1b747b3af5d8e074638ea3a0b184c155e9" and "048dcadaa2886d1b7621058622bf04b03683e4b7" have entirely different histories.
bea3fe1b74
...
048dcadaa2
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<template class="EnvironmentsDialog" parent="AdwDialog">
|
<template class="EnvironmentsDialog" parent="AdwDialog">
|
||||||
<property name="title">Manage Environments</property>
|
<property name="title">Manage Environments</property>
|
||||||
<property name="content-width">1200</property>
|
<property name="content-width">1100</property>
|
||||||
<property name="content-height">600</property>
|
<property name="content-height">600</property>
|
||||||
<property name="follows-content-size">false</property>
|
<property name="follows-content-size">false</property>
|
||||||
|
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</child>
|
</child>
|
||||||
|
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="vexpand">true</property>
|
<property name="vexpand">true</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwClamp">
|
<object class="AdwClamp">
|
||||||
|
|||||||
@ -34,7 +34,6 @@ class EnvironmentsDialog(Adw.Dialog):
|
|||||||
|
|
||||||
table_container = Gtk.Template.Child()
|
table_container = Gtk.Template.Child()
|
||||||
add_environment_button = Gtk.Template.Child()
|
add_environment_button = Gtk.Template.Child()
|
||||||
scrolled_window = Gtk.Template.Child()
|
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
'environments-updated': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
'environments-updated': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||||
@ -49,10 +48,6 @@ class EnvironmentsDialog(Adw.Dialog):
|
|||||||
self.header_row = None
|
self.header_row = None
|
||||||
self.data_rows = []
|
self.data_rows = []
|
||||||
self._populate_table()
|
self._populate_table()
|
||||||
self.connect('map', self._scroll_to_start)
|
|
||||||
|
|
||||||
def _scroll_to_start(self, widget):
|
|
||||||
self.scrolled_window.get_hadjustment().set_value(0)
|
|
||||||
|
|
||||||
def _populate_table(self):
|
def _populate_table(self):
|
||||||
"""Populate the transposed environment-variable table."""
|
"""Populate the transposed environment-variable table."""
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
<property name="title" translatable="yes">Preferences</property>
|
<property name="title" translatable="yes">Preferences</property>
|
||||||
<property name="modal">True</property>
|
<property name="modal">True</property>
|
||||||
<property name="default-width">600</property>
|
<property name="default-width">600</property>
|
||||||
|
<property name="default-height">400</property>
|
||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesPage">
|
<object class="AdwPreferencesPage">
|
||||||
|
|||||||
@ -48,8 +48,6 @@ def _unique_filename(base: str, exclude: set) -> str:
|
|||||||
class ProjectManager:
|
class ProjectManager:
|
||||||
"""Manages project and saved request persistence."""
|
"""Manages project and saved request persistence."""
|
||||||
|
|
||||||
_INDEX_FILE = '_index.json'
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.data_dir = Path(GLib.get_user_data_dir()) / 'cz.bugsy.roster'
|
self.data_dir = Path(GLib.get_user_data_dir()) / 'cz.bugsy.roster'
|
||||||
self.projects_dir = self.data_dir / 'projects'
|
self.projects_dir = self.data_dir / 'projects'
|
||||||
@ -82,29 +80,8 @@ class ProjectManager:
|
|||||||
|
|
||||||
return self._load_from_dirs()
|
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]:
|
def _load_from_dirs(self) -> List[Project]:
|
||||||
projects_by_id = {}
|
projects = []
|
||||||
self._project_dirs.clear()
|
self._project_dirs.clear()
|
||||||
self._request_filenames.clear()
|
self._request_filenames.clear()
|
||||||
|
|
||||||
@ -168,24 +145,12 @@ class ProjectManager:
|
|||||||
environments=[Environment.from_dict(e) for e in meta.get('environments', [])],
|
environments=[Environment.from_dict(e) for e in meta.get('environments', [])],
|
||||||
sensitive_variables=meta.get('sensitive_variables', []),
|
sensitive_variables=meta.get('sensitive_variables', []),
|
||||||
)
|
)
|
||||||
projects_by_id[project.id] = project
|
projects.append(project)
|
||||||
self._project_dirs[project.id] = proj_dir.name
|
self._project_dirs[project.id] = proj_dir.name
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error loading project %s: %s", proj_dir, 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
|
self._projects_cache = projects
|
||||||
return projects
|
return projects
|
||||||
|
|
||||||
@ -334,10 +299,6 @@ class ProjectManager:
|
|||||||
)
|
)
|
||||||
projects.append(project)
|
projects.append(project)
|
||||||
self._save_project(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
|
return project
|
||||||
|
|
||||||
def update_project(self, project_id: str, new_name: str = None, new_icon: str = None):
|
def update_project(self, project_id: str, new_name: str = None, new_icon: str = None):
|
||||||
@ -371,9 +332,6 @@ class ProjectManager:
|
|||||||
|
|
||||||
if self._legacy_mode:
|
if self._legacy_mode:
|
||||||
self._save_all_legacy()
|
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):
|
def on_secrets_deleted(success):
|
||||||
if callback:
|
if callback:
|
||||||
@ -436,52 +394,6 @@ class ProjectManager:
|
|||||||
self._save_project(p)
|
self._save_project(p)
|
||||||
break
|
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:
|
def add_environment(self, project_id: str, name: str) -> Environment:
|
||||||
projects = self.load_projects()
|
projects = self.load_projects()
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
now = datetime.now(timezone.utc).isoformat()
|
||||||
|
|||||||
@ -4,16 +4,6 @@
|
|||||||
<requires lib="libadwaita" version="1.0"/>
|
<requires lib="libadwaita" version="1.0"/>
|
||||||
|
|
||||||
<menu id="project_menu">
|
<menu id="project_menu">
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label">Move Up</attribute>
|
|
||||||
<attribute name="action">project.move-up</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label">Move Down</attribute>
|
|
||||||
<attribute name="action">project.move-down</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label">Manage Environments</attribute>
|
<attribute name="label">Manage Environments</attribute>
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
@ -41,17 +41,11 @@ class ProjectItem(Gtk.Box):
|
|||||||
'request-load-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
'request-load-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||||
'request-delete-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
'request-delete-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||||
'manage-environments-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
'manage-environments-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||||
'move-up-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
||||||
'move-down-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
||||||
'request-move-up-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
|
||||||
'request-move-down-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
|
||||||
'request-move-to-project-requested': (GObject.SIGNAL_RUN_FIRST, None, (object, GObject.TYPE_STRING)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, project, other_projects=None):
|
def __init__(self, project):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.project = project
|
self.project = project
|
||||||
self.other_projects = other_projects or []
|
|
||||||
self.expanded = False
|
self.expanded = False
|
||||||
self._setup_actions()
|
self._setup_actions()
|
||||||
self._populate()
|
self._populate()
|
||||||
@ -65,14 +59,6 @@ class ProjectItem(Gtk.Box):
|
|||||||
"""Setup action group for menu."""
|
"""Setup action group for menu."""
|
||||||
actions = Gio.SimpleActionGroup.new()
|
actions = Gio.SimpleActionGroup.new()
|
||||||
|
|
||||||
self._move_up_action = Gio.SimpleAction.new("move-up", None)
|
|
||||||
self._move_up_action.connect("activate", lambda *_: self.emit('move-up-requested'))
|
|
||||||
actions.add_action(self._move_up_action)
|
|
||||||
|
|
||||||
self._move_down_action = Gio.SimpleAction.new("move-down", None)
|
|
||||||
self._move_down_action.connect("activate", lambda *_: self.emit('move-down-requested'))
|
|
||||||
actions.add_action(self._move_down_action)
|
|
||||||
|
|
||||||
action = Gio.SimpleAction.new("manage-environments", None)
|
action = Gio.SimpleAction.new("manage-environments", None)
|
||||||
action.connect("activate", lambda *_: self.emit('manage-environments-requested'))
|
action.connect("activate", lambda *_: self.emit('manage-environments-requested'))
|
||||||
actions.add_action(action)
|
actions.add_action(action)
|
||||||
@ -96,25 +82,16 @@ class ProjectItem(Gtk.Box):
|
|||||||
self.name_label.set_text(self.project.name)
|
self.name_label.set_text(self.project.name)
|
||||||
self.project_icon.set_from_icon_name(self.project.icon)
|
self.project_icon.set_from_icon_name(self.project.icon)
|
||||||
|
|
||||||
|
# Clear and add requests
|
||||||
while child := self.requests_listbox.get_first_child():
|
while child := self.requests_listbox.get_first_child():
|
||||||
self.requests_listbox.remove(child)
|
self.requests_listbox.remove(child)
|
||||||
|
|
||||||
total = len(self.project.requests)
|
for saved_request in self.project.requests:
|
||||||
for i, saved_request in enumerate(self.project.requests):
|
|
||||||
item = RequestItem(saved_request)
|
item = RequestItem(saved_request)
|
||||||
item.set_can_move_up(i > 0)
|
|
||||||
item.set_can_move_down(i < total - 1)
|
|
||||||
item.set_other_projects(self.other_projects)
|
|
||||||
item.connect('load-requested',
|
item.connect('load-requested',
|
||||||
lambda w, sr=saved_request: self.emit('request-load-requested', sr))
|
lambda w, sr=saved_request: self.emit('request-load-requested', sr))
|
||||||
item.connect('delete-requested',
|
item.connect('delete-requested',
|
||||||
lambda w, sr=saved_request: self.emit('request-delete-requested', sr))
|
lambda w, sr=saved_request: self.emit('request-delete-requested', sr))
|
||||||
item.connect('move-up-requested',
|
|
||||||
lambda w, sr=saved_request: self.emit('request-move-up-requested', sr))
|
|
||||||
item.connect('move-down-requested',
|
|
||||||
lambda w, sr=saved_request: self.emit('request-move-down-requested', sr))
|
|
||||||
item.connect('move-to-project-requested',
|
|
||||||
lambda w, pid, sr=saved_request: self.emit('request-move-to-project-requested', sr, pid))
|
|
||||||
self.requests_listbox.append(item)
|
self.requests_listbox.append(item)
|
||||||
|
|
||||||
def _on_header_clicked(self, gesture, n_press, x, y):
|
def _on_header_clicked(self, gesture, n_press, x, y):
|
||||||
@ -122,14 +99,6 @@ class ProjectItem(Gtk.Box):
|
|||||||
self.expanded = not self.expanded
|
self.expanded = not self.expanded
|
||||||
self.requests_revealer.set_reveal_child(self.expanded)
|
self.requests_revealer.set_reveal_child(self.expanded)
|
||||||
|
|
||||||
def set_can_move_up(self, can: bool):
|
def refresh(self):
|
||||||
self._move_up_action.set_enabled(can)
|
"""Refresh from project data."""
|
||||||
|
|
||||||
def set_can_move_down(self, can: bool):
|
|
||||||
self._move_down_action.set_enabled(can)
|
|
||||||
|
|
||||||
def refresh(self, other_projects=None):
|
|
||||||
"""Refresh from project data, optionally updating the other_projects list."""
|
|
||||||
if other_projects is not None:
|
|
||||||
self.other_projects = other_projects
|
|
||||||
self._populate()
|
self._populate()
|
||||||
|
|||||||
@ -27,12 +27,12 @@
|
|||||||
</child>
|
</child>
|
||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuButton" id="menu_button">
|
<object class="GtkButton" id="delete_button">
|
||||||
<property name="icon-name">view-more-symbolic</property>
|
<property name="icon-name">edit-delete-symbolic</property>
|
||||||
<property name="tooltip-text">Options</property>
|
<property name="tooltip-text">Delete request</property>
|
||||||
|
<signal name="clicked" handler="on_delete_clicked"/>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat"/>
|
<class name="flat"/>
|
||||||
<class name="request-menu-button"/>
|
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
@ -18,7 +18,7 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from gi.repository import Gtk, GObject, Gio, GLib
|
from gi.repository import Gtk, GObject
|
||||||
|
|
||||||
_METHOD_CSS = {
|
_METHOD_CSS = {
|
||||||
'GET': 'accent',
|
'GET': 'accent',
|
||||||
@ -37,14 +37,10 @@ class RequestItem(Gtk.Box):
|
|||||||
|
|
||||||
method_label = Gtk.Template.Child()
|
method_label = Gtk.Template.Child()
|
||||||
name_label = Gtk.Template.Child()
|
name_label = Gtk.Template.Child()
|
||||||
menu_button = Gtk.Template.Child()
|
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
'load-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
'load-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||||
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||||
'move-up-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
||||||
'move-down-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
|
||||||
'move-to-project-requested': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_STRING,)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, saved_request):
|
def __init__(self, saved_request):
|
||||||
@ -60,92 +56,16 @@ class RequestItem(Gtk.Box):
|
|||||||
break
|
break
|
||||||
self.name_label.set_text(display_name)
|
self.name_label.set_text(display_name)
|
||||||
|
|
||||||
self._setup_menu()
|
# Add click gesture for loading
|
||||||
|
|
||||||
# Click gesture for loading
|
|
||||||
gesture = Gtk.GestureClick.new()
|
gesture = Gtk.GestureClick.new()
|
||||||
gesture.connect('released', self._on_clicked)
|
gesture.connect('released', self._on_clicked)
|
||||||
self.add_controller(gesture)
|
self.add_controller(gesture)
|
||||||
|
|
||||||
# Show/hide menu button on hover
|
|
||||||
motion = Gtk.EventControllerMotion.new()
|
|
||||||
motion.connect('enter', self._on_hover_enter)
|
|
||||||
motion.connect('leave', self._on_hover_leave)
|
|
||||||
self.add_controller(motion)
|
|
||||||
|
|
||||||
def _setup_menu(self):
|
|
||||||
actions = Gio.SimpleActionGroup.new()
|
|
||||||
|
|
||||||
self._move_up_action = Gio.SimpleAction.new("move-up", None)
|
|
||||||
self._move_up_action.connect("activate", lambda *_: self.emit('move-up-requested'))
|
|
||||||
actions.add_action(self._move_up_action)
|
|
||||||
|
|
||||||
self._move_down_action = Gio.SimpleAction.new("move-down", None)
|
|
||||||
self._move_down_action.connect("activate", lambda *_: self.emit('move-down-requested'))
|
|
||||||
actions.add_action(self._move_down_action)
|
|
||||||
|
|
||||||
move_to = Gio.SimpleAction.new("move-to-project", GLib.VariantType.new("s"))
|
|
||||||
move_to.connect("activate", self._on_move_to_project_activated)
|
|
||||||
actions.add_action(move_to)
|
|
||||||
|
|
||||||
delete = Gio.SimpleAction.new("delete", None)
|
|
||||||
delete.connect("activate", lambda *_: self.emit('delete-requested'))
|
|
||||||
actions.add_action(delete)
|
|
||||||
|
|
||||||
self.insert_action_group("request", actions)
|
|
||||||
|
|
||||||
# Build menu model
|
|
||||||
menu = Gio.Menu()
|
|
||||||
|
|
||||||
reorder_section = Gio.Menu()
|
|
||||||
reorder_section.append("Move Up", "request.move-up")
|
|
||||||
reorder_section.append("Move Down", "request.move-down")
|
|
||||||
menu.append_section(None, reorder_section)
|
|
||||||
|
|
||||||
self._move_to_submenu = Gio.Menu()
|
|
||||||
move_section = Gio.Menu()
|
|
||||||
move_section.append_submenu("Move to Project", self._move_to_submenu)
|
|
||||||
menu.append_section(None, move_section)
|
|
||||||
|
|
||||||
delete_section = Gio.Menu()
|
|
||||||
delete_section.append("Delete", "request.delete")
|
|
||||||
menu.append_section(None, delete_section)
|
|
||||||
|
|
||||||
self.menu_button.set_menu_model(menu)
|
|
||||||
|
|
||||||
# Hidden by default; shown on hover via EventControllerMotion.
|
|
||||||
# When the popover closes, hide the button again.
|
|
||||||
self.menu_button.set_opacity(0.0)
|
|
||||||
popover = self.menu_button.get_popover()
|
|
||||||
if popover:
|
|
||||||
popover.connect('closed', lambda p: self.menu_button.set_opacity(0.0))
|
|
||||||
|
|
||||||
def _on_move_to_project_activated(self, action, param):
|
|
||||||
self.emit('move-to-project-requested', param.get_string())
|
|
||||||
|
|
||||||
def _on_clicked(self, gesture, n_press, x, y):
|
def _on_clicked(self, gesture, n_press, x, y):
|
||||||
"""Load request on row click (but not if the menu button was clicked)."""
|
"""Handle click to load request."""
|
||||||
alloc = self.menu_button.get_allocation()
|
|
||||||
if alloc.x <= x <= alloc.x + alloc.width and alloc.y <= y <= alloc.y + alloc.height:
|
|
||||||
return
|
|
||||||
self.emit('load-requested')
|
self.emit('load-requested')
|
||||||
|
|
||||||
def _on_hover_enter(self, controller, x, y):
|
@Gtk.Template.Callback()
|
||||||
self.menu_button.set_opacity(1.0)
|
def on_delete_clicked(self, button):
|
||||||
|
"""Handle delete button click."""
|
||||||
def _on_hover_leave(self, controller):
|
self.emit('delete-requested')
|
||||||
popover = self.menu_button.get_popover()
|
|
||||||
if not (popover and popover.get_visible()):
|
|
||||||
self.menu_button.set_opacity(0.0)
|
|
||||||
|
|
||||||
def set_can_move_up(self, can: bool):
|
|
||||||
self._move_up_action.set_enabled(can)
|
|
||||||
|
|
||||||
def set_can_move_down(self, can: bool):
|
|
||||||
self._move_down_action.set_enabled(can)
|
|
||||||
|
|
||||||
def set_other_projects(self, projects):
|
|
||||||
"""Rebuild the 'Move to Project' submenu from the given project list."""
|
|
||||||
self._move_to_submenu.remove_all()
|
|
||||||
for p in projects:
|
|
||||||
self._move_to_submenu.append(p.name, f"request.move-to-project::{p.id}")
|
|
||||||
|
|||||||
@ -961,45 +961,20 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
def _load_projects(self) -> None:
|
def _load_projects(self) -> None:
|
||||||
"""Load and display projects."""
|
"""Load and display projects."""
|
||||||
# Remember which projects are currently expanded.
|
|
||||||
# GtkListBox wraps each child in a GtkListBoxRow, so we call get_child()
|
|
||||||
# on the row to reach the actual ProjectItem widget.
|
|
||||||
expanded_ids = set()
|
|
||||||
row = self.projects_listbox.get_first_child()
|
|
||||||
while row:
|
|
||||||
project_item = row.get_child()
|
|
||||||
if isinstance(project_item, ProjectItem) and project_item.expanded:
|
|
||||||
expanded_ids.add(project_item.project.id)
|
|
||||||
row = row.get_next_sibling()
|
|
||||||
|
|
||||||
# Clear existing
|
# Clear existing
|
||||||
while child := self.projects_listbox.get_first_child():
|
while child := self.projects_listbox.get_first_child():
|
||||||
self.projects_listbox.remove(child)
|
self.projects_listbox.remove(child)
|
||||||
|
|
||||||
# Load and populate
|
# Load and populate
|
||||||
projects = self.project_manager.load_projects()
|
projects = self.project_manager.load_projects()
|
||||||
total = len(projects)
|
for project in projects:
|
||||||
for i, project in enumerate(projects):
|
item = ProjectItem(project)
|
||||||
other_projects = [p for p in projects if p.id != project.id]
|
|
||||||
item = ProjectItem(project, other_projects)
|
|
||||||
item.set_can_move_up(i > 0)
|
|
||||||
item.set_can_move_down(i < total - 1)
|
|
||||||
item.connect('edit-requested', self._on_project_edit, project)
|
item.connect('edit-requested', self._on_project_edit, project)
|
||||||
item.connect('delete-requested', self._on_project_delete, project)
|
item.connect('delete-requested', self._on_project_delete, project)
|
||||||
item.connect('add-request-requested', self._on_add_to_project, project)
|
item.connect('add-request-requested', self._on_add_to_project, project)
|
||||||
item.connect('request-load-requested', self._on_load_request)
|
item.connect('request-load-requested', self._on_load_request)
|
||||||
item.connect('request-delete-requested', self._on_delete_request, project)
|
item.connect('request-delete-requested', self._on_delete_request, project)
|
||||||
item.connect('manage-environments-requested', self._on_manage_environments, project)
|
item.connect('manage-environments-requested', self._on_manage_environments, project)
|
||||||
item.connect('move-up-requested', self._on_project_move_up, project)
|
|
||||||
item.connect('move-down-requested', self._on_project_move_down, project)
|
|
||||||
item.connect('request-move-up-requested', self._on_request_move_up, project)
|
|
||||||
item.connect('request-move-down-requested', self._on_request_move_down, project)
|
|
||||||
item.connect('request-move-to-project-requested', self._on_request_move_to_project, project)
|
|
||||||
if project.id in expanded_ids:
|
|
||||||
item.expanded = True
|
|
||||||
item.requests_revealer.set_transition_duration(0)
|
|
||||||
item.requests_revealer.set_reveal_child(True)
|
|
||||||
GLib.idle_add(lambda r=item.requests_revealer: (r.set_transition_duration(250), False)[1])
|
|
||||||
self.projects_listbox.append(item)
|
self.projects_listbox.append(item)
|
||||||
|
|
||||||
def on_add_project_clicked(self, button):
|
def on_add_project_clicked(self, button):
|
||||||
@ -1137,33 +1112,6 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
dialog.connect('environments-updated', on_environments_updated)
|
dialog.connect('environments-updated', on_environments_updated)
|
||||||
dialog.present(self)
|
dialog.present(self)
|
||||||
|
|
||||||
def _on_project_move_up(self, widget, project):
|
|
||||||
self.project_manager.reorder_project(project.id, -1)
|
|
||||||
self._load_projects()
|
|
||||||
|
|
||||||
def _on_project_move_down(self, widget, project):
|
|
||||||
self.project_manager.reorder_project(project.id, +1)
|
|
||||||
self._load_projects()
|
|
||||||
|
|
||||||
def _on_request_move_up(self, widget, saved_request, project):
|
|
||||||
self.project_manager.reorder_request(project.id, saved_request.id, -1)
|
|
||||||
self._load_projects()
|
|
||||||
|
|
||||||
def _on_request_move_down(self, widget, saved_request, project):
|
|
||||||
self.project_manager.reorder_request(project.id, saved_request.id, +1)
|
|
||||||
self._load_projects()
|
|
||||||
|
|
||||||
def _on_request_move_to_project(self, widget, saved_request, target_project_id, source_project):
|
|
||||||
self.project_manager.move_request_to_project(
|
|
||||||
saved_request.id, source_project.id, target_project_id
|
|
||||||
)
|
|
||||||
self._load_projects()
|
|
||||||
# Find target project name for toast
|
|
||||||
projects = self.project_manager.load_projects()
|
|
||||||
target = next((p for p in projects if p.id == target_project_id), None)
|
|
||||||
if target:
|
|
||||||
self._show_toast(f"Moved to '{target.name}'")
|
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def on_save_request_clicked(self, button):
|
def on_save_request_clicked(self, button):
|
||||||
"""Save current request to a project."""
|
"""Save current request to a project."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user