Add environment management for projects with variable support
This commit is contained in:
parent
5fa104db69
commit
dfe2a9a360
137
src/environments-dialog.ui
Normal file
137
src/environments-dialog.ui
Normal file
@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="libadwaita" version="1.0"/>
|
||||
|
||||
<template class="EnvironmentsDialog" parent="AdwDialog">
|
||||
<property name="title">Manage Environments</property>
|
||||
<property name="content-width">700</property>
|
||||
<property name="content-height">500</property>
|
||||
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="show-title">true</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<property name="content">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">true</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="spacing">24</property>
|
||||
|
||||
<!-- Variables Section -->
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">12</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Variables</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="hexpand">True</property>
|
||||
<style>
|
||||
<class name="title-4"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="add_variable_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="tooltip-text">Add variable</property>
|
||||
<signal name="clicked" handler="on_add_variable_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<child>
|
||||
<object class="GtkListBox" id="variables_listbox">
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="boxed-list"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Environments Section -->
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">12</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Environments</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="hexpand">True</property>
|
||||
<style>
|
||||
<class name="title-4"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="add_environment_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="tooltip-text">Add environment</property>
|
||||
<signal name="clicked" handler="on_add_environment_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<child>
|
||||
<object class="GtkListBox" id="environments_listbox">
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="boxed-list"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
233
src/environments_dialog.py
Normal file
233
src/environments_dialog.py
Normal file
@ -0,0 +1,233 @@
|
||||
# environments_dialog.py
|
||||
#
|
||||
# Copyright 2025 Pavel Baksy
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from gi.repository import Adw, Gtk, GObject
|
||||
from .widgets.variable_row import VariableRow
|
||||
from .widgets.environment_row import EnvironmentRow
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/cz/vesp/roster/environments-dialog.ui')
|
||||
class EnvironmentsDialog(Adw.Dialog):
|
||||
"""Dialog for managing project environments and variables."""
|
||||
|
||||
__gtype_name__ = 'EnvironmentsDialog'
|
||||
|
||||
variables_listbox = Gtk.Template.Child()
|
||||
add_variable_button = Gtk.Template.Child()
|
||||
environments_listbox = Gtk.Template.Child()
|
||||
add_environment_button = Gtk.Template.Child()
|
||||
|
||||
__gsignals__ = {
|
||||
'environments-updated': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
}
|
||||
|
||||
def __init__(self, project, project_manager):
|
||||
super().__init__()
|
||||
self.project = project
|
||||
self.project_manager = project_manager
|
||||
self._populate_variables()
|
||||
self._populate_environments()
|
||||
|
||||
def _populate_variables(self):
|
||||
"""Populate variables list."""
|
||||
# Clear existing
|
||||
while child := self.variables_listbox.get_first_child():
|
||||
self.variables_listbox.remove(child)
|
||||
|
||||
# Add variables
|
||||
for var_name in self.project.variable_names:
|
||||
row = VariableRow(var_name)
|
||||
row.connect('remove-requested', self._on_variable_remove, var_name)
|
||||
row.connect('changed', self._on_variable_changed, var_name, row)
|
||||
self.variables_listbox.append(row)
|
||||
|
||||
def _populate_environments(self):
|
||||
"""Populate environments list."""
|
||||
# Clear existing
|
||||
while child := self.environments_listbox.get_first_child():
|
||||
self.environments_listbox.remove(child)
|
||||
|
||||
# Add environments
|
||||
for env in self.project.environments:
|
||||
row = EnvironmentRow(env, self.project.variable_names)
|
||||
row.connect('edit-requested', self._on_environment_edit, env)
|
||||
row.connect('delete-requested', self._on_environment_delete, env)
|
||||
row.connect('value-changed', self._on_environment_value_changed, env)
|
||||
self.environments_listbox.append(row)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_add_variable_clicked(self, button):
|
||||
"""Add new variable."""
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("New Variable")
|
||||
dialog.set_body("Enter variable name:")
|
||||
|
||||
entry = Gtk.Entry()
|
||||
entry.set_placeholder_text("Variable name")
|
||||
dialog.set_extra_child(entry)
|
||||
|
||||
dialog.add_response("cancel", "Cancel")
|
||||
dialog.add_response("add", "Add")
|
||||
dialog.set_response_appearance("add", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("add")
|
||||
dialog.set_close_response("cancel")
|
||||
|
||||
def on_response(dlg, response):
|
||||
if response == "add":
|
||||
name = entry.get_text().strip()
|
||||
if name and name not in self.project.variable_names:
|
||||
self.project_manager.add_variable(self.project.id, name)
|
||||
# Reload project data
|
||||
self._reload_project()
|
||||
self._populate_variables()
|
||||
self._populate_environments()
|
||||
self.emit('environments-updated')
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_variable_remove(self, widget, var_name):
|
||||
"""Remove variable with confirmation."""
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("Delete Variable?")
|
||||
dialog.set_body(f"Delete variable '{var_name}' from all environments? This cannot be undone.")
|
||||
|
||||
dialog.add_response("cancel", "Cancel")
|
||||
dialog.add_response("delete", "Delete")
|
||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_close_response("cancel")
|
||||
|
||||
def on_response(dlg, response):
|
||||
if response == "delete":
|
||||
self.project_manager.delete_variable(self.project.id, var_name)
|
||||
self._reload_project()
|
||||
self._populate_variables()
|
||||
self._populate_environments()
|
||||
self.emit('environments-updated')
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_variable_changed(self, widget, old_name, row):
|
||||
"""Handle variable name change."""
|
||||
new_name = row.get_variable_name()
|
||||
if new_name and new_name != old_name and new_name not in self.project.variable_names:
|
||||
self.project_manager.rename_variable(self.project.id, old_name, new_name)
|
||||
self._reload_project()
|
||||
self._populate_variables()
|
||||
self._populate_environments()
|
||||
self.emit('environments-updated')
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_add_environment_clicked(self, button):
|
||||
"""Add new environment."""
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("New Environment")
|
||||
dialog.set_body("Enter environment name:")
|
||||
|
||||
entry = Gtk.Entry()
|
||||
entry.set_placeholder_text("Environment name (e.g., Production)")
|
||||
dialog.set_extra_child(entry)
|
||||
|
||||
dialog.add_response("cancel", "Cancel")
|
||||
dialog.add_response("add", "Add")
|
||||
dialog.set_response_appearance("add", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("add")
|
||||
dialog.set_close_response("cancel")
|
||||
|
||||
def on_response(dlg, response):
|
||||
if response == "add":
|
||||
name = entry.get_text().strip()
|
||||
if name:
|
||||
self.project_manager.add_environment(self.project.id, name)
|
||||
self._reload_project()
|
||||
self._populate_environments()
|
||||
self.emit('environments-updated')
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_environment_edit(self, widget, environment):
|
||||
"""Edit environment name."""
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("Edit Environment")
|
||||
dialog.set_body("Enter new name:")
|
||||
|
||||
entry = Gtk.Entry()
|
||||
entry.set_text(environment.name)
|
||||
dialog.set_extra_child(entry)
|
||||
|
||||
dialog.add_response("cancel", "Cancel")
|
||||
dialog.add_response("save", "Save")
|
||||
dialog.set_response_appearance("save", Adw.ResponseAppearance.SUGGESTED)
|
||||
|
||||
def on_response(dlg, response):
|
||||
if response == "save":
|
||||
new_name = entry.get_text().strip()
|
||||
if new_name:
|
||||
self.project_manager.update_environment(self.project.id, environment.id, name=new_name)
|
||||
self._reload_project()
|
||||
self._populate_environments()
|
||||
self.emit('environments-updated')
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_environment_delete(self, widget, environment):
|
||||
"""Delete environment with confirmation."""
|
||||
# Prevent deletion of last environment
|
||||
if len(self.project.environments) <= 1:
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("Cannot Delete")
|
||||
dialog.set_body("Each project must have at least one environment.")
|
||||
dialog.add_response("ok", "OK")
|
||||
dialog.present(self)
|
||||
return
|
||||
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("Delete Environment?")
|
||||
dialog.set_body(f"Delete environment '{environment.name}'? This cannot be undone.")
|
||||
|
||||
dialog.add_response("cancel", "Cancel")
|
||||
dialog.add_response("delete", "Delete")
|
||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_close_response("cancel")
|
||||
|
||||
def on_response(dlg, response):
|
||||
if response == "delete":
|
||||
self.project_manager.delete_environment(self.project.id, environment.id)
|
||||
self._reload_project()
|
||||
self._populate_environments()
|
||||
self.emit('environments-updated')
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_environment_value_changed(self, widget, var_name, value, environment):
|
||||
"""Handle environment variable value change."""
|
||||
self.project_manager.update_environment_variable(self.project.id, environment.id, var_name, value)
|
||||
self.emit('environments-updated')
|
||||
|
||||
def _reload_project(self):
|
||||
"""Reload project data from manager."""
|
||||
projects = self.project_manager.load_projects()
|
||||
for p in projects:
|
||||
if p.id == self.project.id:
|
||||
self.project = p
|
||||
break
|
||||
@ -37,6 +37,7 @@ roster_sources = [
|
||||
'tab_manager.py',
|
||||
'constants.py',
|
||||
'icon_picker_dialog.py',
|
||||
'environments_dialog.py',
|
||||
'preferences_dialog.py',
|
||||
'request_tab_widget.py',
|
||||
]
|
||||
@ -50,6 +51,8 @@ widgets_sources = [
|
||||
'widgets/history_item.py',
|
||||
'widgets/project_item.py',
|
||||
'widgets/request_item.py',
|
||||
'widgets/variable_row.py',
|
||||
'widgets/environment_row.py',
|
||||
]
|
||||
|
||||
install_data(widgets_sources, install_dir: moduledir / 'widgets')
|
||||
|
||||
@ -121,14 +121,51 @@ class SavedRequest:
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Environment:
|
||||
"""An environment with variable values."""
|
||||
id: str # UUID
|
||||
name: str # e.g., "production", "integration", "stage"
|
||||
variables: Dict[str, str] # variable_name -> value
|
||||
created_at: str # ISO format
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'variables': self.variables,
|
||||
'created_at': self.created_at
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
"""Create instance from dictionary."""
|
||||
return cls(
|
||||
id=data['id'],
|
||||
name=data['name'],
|
||||
variables=data.get('variables', {}),
|
||||
created_at=data['created_at']
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Project:
|
||||
"""A project containing saved requests."""
|
||||
"""A project containing saved requests and environments."""
|
||||
id: str # UUID
|
||||
name: str
|
||||
requests: List[SavedRequest]
|
||||
created_at: str # ISO format
|
||||
icon: str = "folder-symbolic" # Icon name
|
||||
variable_names: List[str] = None # List of variable names defined for this project
|
||||
environments: List[Environment] = None # List of environments
|
||||
|
||||
def __post_init__(self):
|
||||
"""Initialize optional fields."""
|
||||
if self.variable_names is None:
|
||||
self.variable_names = []
|
||||
if self.environments is None:
|
||||
self.environments = []
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
@ -137,7 +174,9 @@ class Project:
|
||||
'name': self.name,
|
||||
'requests': [req.to_dict() for req in self.requests],
|
||||
'created_at': self.created_at,
|
||||
'icon': self.icon
|
||||
'icon': self.icon,
|
||||
'variable_names': self.variable_names,
|
||||
'environments': [env.to_dict() for env in self.environments]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@ -148,7 +187,9 @@ class Project:
|
||||
name=data['name'],
|
||||
requests=[SavedRequest.from_dict(r) for r in data.get('requests', [])],
|
||||
created_at=data['created_at'],
|
||||
icon=data.get('icon', 'folder-symbolic') # Default for old data
|
||||
icon=data.get('icon', 'folder-symbolic'), # Default for old data
|
||||
variable_names=data.get('variable_names', []),
|
||||
environments=[Environment.from_dict(e) for e in data.get('environments', [])]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ from datetime import datetime, timezone
|
||||
import gi
|
||||
gi.require_version('GLib', '2.0')
|
||||
from gi.repository import GLib
|
||||
from .models import Project, SavedRequest, HttpRequest
|
||||
from .models import Project, SavedRequest, HttpRequest, Environment
|
||||
|
||||
|
||||
class ProjectManager:
|
||||
@ -69,13 +69,25 @@ class ProjectManager:
|
||||
print(f"Error saving projects: {e}")
|
||||
|
||||
def add_project(self, name: str) -> Project:
|
||||
"""Create new project."""
|
||||
"""Create new project with default environment."""
|
||||
projects = self.load_projects()
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Create default environment
|
||||
default_env = Environment(
|
||||
id=str(uuid.uuid4()),
|
||||
name="Default",
|
||||
variables={},
|
||||
created_at=now
|
||||
)
|
||||
|
||||
project = Project(
|
||||
id=str(uuid.uuid4()),
|
||||
name=name,
|
||||
requests=[],
|
||||
created_at=datetime.now(timezone.utc).isoformat()
|
||||
created_at=now,
|
||||
variable_names=[],
|
||||
environments=[default_env]
|
||||
)
|
||||
projects.append(project)
|
||||
self.save_projects(projects)
|
||||
@ -154,3 +166,104 @@ class ProjectManager:
|
||||
p.requests = [r for r in p.requests if r.id != request_id]
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
def add_environment(self, project_id: str, name: str) -> Environment:
|
||||
"""Add environment to a project."""
|
||||
projects = self.load_projects()
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
environment = None
|
||||
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
# Create environment with empty values for all variables
|
||||
variables = {var_name: "" for var_name in p.variable_names}
|
||||
environment = Environment(
|
||||
id=str(uuid.uuid4()),
|
||||
name=name,
|
||||
variables=variables,
|
||||
created_at=now
|
||||
)
|
||||
p.environments.append(environment)
|
||||
break
|
||||
|
||||
self.save_projects(projects)
|
||||
return environment
|
||||
|
||||
def update_environment(self, project_id: str, env_id: str, name: str = None, variables: dict = None):
|
||||
"""Update an environment's name and/or variables."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
for env in p.environments:
|
||||
if env.id == env_id:
|
||||
if name is not None:
|
||||
env.name = name
|
||||
if variables is not None:
|
||||
env.variables = variables
|
||||
break
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
def delete_environment(self, project_id: str, env_id: str):
|
||||
"""Delete an environment."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
p.environments = [e for e in p.environments if e.id != env_id]
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
def add_variable(self, project_id: str, variable_name: str):
|
||||
"""Add a variable to the project and all environments."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
if variable_name not in p.variable_names:
|
||||
p.variable_names.append(variable_name)
|
||||
# Add empty value to all environments
|
||||
for env in p.environments:
|
||||
env.variables[variable_name] = ""
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
def rename_variable(self, project_id: str, old_name: str, new_name: str):
|
||||
"""Rename a variable in the project and all environments."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
# Update variable_names list
|
||||
if old_name in p.variable_names:
|
||||
idx = p.variable_names.index(old_name)
|
||||
p.variable_names[idx] = new_name
|
||||
# Update all environments
|
||||
for env in p.environments:
|
||||
if old_name in env.variables:
|
||||
env.variables[new_name] = env.variables.pop(old_name)
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
def delete_variable(self, project_id: str, variable_name: str):
|
||||
"""Delete a variable from the project and all environments."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
# Remove from variable_names
|
||||
if variable_name in p.variable_names:
|
||||
p.variable_names.remove(variable_name)
|
||||
# Remove from all environments
|
||||
for env in p.environments:
|
||||
env.variables.pop(variable_name, None)
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
def update_environment_variable(self, project_id: str, env_id: str, variable_name: str, value: str):
|
||||
"""Update a single variable value in an environment."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
for env in p.environments:
|
||||
if env.id == env_id:
|
||||
env.variables[variable_name] = value
|
||||
break
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
@ -5,9 +5,12 @@
|
||||
<file preprocess="xml-stripblanks">shortcuts-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">preferences-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">icon-picker-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">environments-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/header-row.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/history-item.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/project-item.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/request-item.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/variable-row.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/environment-row.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
@ -21,5 +21,7 @@ from .header_row import HeaderRow
|
||||
from .history_item import HistoryItem
|
||||
from .project_item import ProjectItem
|
||||
from .request_item import RequestItem
|
||||
from .variable_row import VariableRow
|
||||
from .environment_row import EnvironmentRow
|
||||
|
||||
__all__ = ['HeaderRow', 'HistoryItem', 'ProjectItem', 'RequestItem']
|
||||
__all__ = ['HeaderRow', 'HistoryItem', 'ProjectItem', 'RequestItem', 'VariableRow', 'EnvironmentRow']
|
||||
|
||||
52
src/widgets/environment-row.ui
Normal file
52
src/widgets/environment-row.ui
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="EnvironmentRow" parent="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkLabel" id="name_label">
|
||||
<property name="xalign">0</property>
|
||||
<property name="width-chars">15</property>
|
||||
<style>
|
||||
<class name="heading"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkBox" id="values_box">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="edit_button">
|
||||
<property name="icon-name">document-edit-symbolic</property>
|
||||
<property name="tooltip-text">Edit environment</property>
|
||||
<signal name="clicked" handler="on_edit_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="delete_button">
|
||||
<property name="icon-name">edit-delete-symbolic</property>
|
||||
<property name="tooltip-text">Delete environment</property>
|
||||
<signal name="clicked" handler="on_delete_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
86
src/widgets/environment_row.py
Normal file
86
src/widgets/environment_row.py
Normal file
@ -0,0 +1,86 @@
|
||||
# environment_row.py
|
||||
#
|
||||
# Copyright 2025 Pavel Baksy
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from gi.repository import Gtk, GObject
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/cz/vesp/roster/widgets/environment-row.ui')
|
||||
class EnvironmentRow(Gtk.Box):
|
||||
"""Widget for displaying and editing an environment with its variables."""
|
||||
|
||||
__gtype_name__ = 'EnvironmentRow'
|
||||
|
||||
name_label = Gtk.Template.Child()
|
||||
values_box = Gtk.Template.Child()
|
||||
edit_button = Gtk.Template.Child()
|
||||
delete_button = Gtk.Template.Child()
|
||||
|
||||
__gsignals__ = {
|
||||
'edit-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'value-changed': (GObject.SIGNAL_RUN_FIRST, None, (str, str)) # variable_name, value
|
||||
}
|
||||
|
||||
def __init__(self, environment, variable_names):
|
||||
super().__init__()
|
||||
self.environment = environment
|
||||
self.variable_names = variable_names
|
||||
self.value_entries = {}
|
||||
self._populate()
|
||||
|
||||
def _populate(self):
|
||||
"""Populate with environment data."""
|
||||
self.name_label.set_text(self.environment.name)
|
||||
|
||||
# Clear values box
|
||||
while child := self.values_box.get_first_child():
|
||||
self.values_box.remove(child)
|
||||
|
||||
# Create entry for each variable
|
||||
for var_name in self.variable_names:
|
||||
entry = Gtk.Entry()
|
||||
entry.set_placeholder_text(var_name)
|
||||
entry.set_text(self.environment.variables.get(var_name, ""))
|
||||
entry.set_hexpand(True)
|
||||
entry.connect('changed', self._on_value_changed, var_name)
|
||||
self.values_box.append(entry)
|
||||
self.value_entries[var_name] = entry
|
||||
|
||||
def _on_value_changed(self, entry, var_name):
|
||||
"""Handle value entry changes."""
|
||||
self.emit('value-changed', var_name, entry.get_text())
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_edit_clicked(self, button):
|
||||
"""Handle edit button click."""
|
||||
self.emit('edit-requested')
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_delete_clicked(self, button):
|
||||
"""Handle delete button click."""
|
||||
self.emit('delete-requested')
|
||||
|
||||
def refresh(self, variable_names):
|
||||
"""Refresh widget with updated variable names."""
|
||||
self.variable_names = variable_names
|
||||
self._populate()
|
||||
|
||||
def get_values(self):
|
||||
"""Get all variable values."""
|
||||
return {var_name: entry.get_text() for var_name, entry in self.value_entries.items()}
|
||||
@ -5,6 +5,10 @@
|
||||
|
||||
<menu id="project_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label">Manage Environments</attribute>
|
||||
<attribute name="action">project.manage-environments</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label">Add Request</attribute>
|
||||
<attribute name="action">project.add-request</attribute>
|
||||
|
||||
@ -38,6 +38,7 @@ class ProjectItem(Gtk.Box):
|
||||
'add-request-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'request-load-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||
'request-delete-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||
'manage-environments-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
}
|
||||
|
||||
def __init__(self, project):
|
||||
@ -56,6 +57,10 @@ class ProjectItem(Gtk.Box):
|
||||
"""Setup action group for menu."""
|
||||
actions = Gio.SimpleActionGroup.new()
|
||||
|
||||
action = Gio.SimpleAction.new("manage-environments", None)
|
||||
action.connect("activate", lambda *_: self.emit('manage-environments-requested'))
|
||||
actions.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("add-request", None)
|
||||
action.connect("activate", lambda *_: self.emit('add-request-requested'))
|
||||
actions.add_action(action)
|
||||
|
||||
30
src/widgets/variable-row.ui
Normal file
30
src/widgets/variable-row.ui
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="VariableRow" parent="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="margin-start">6</property>
|
||||
<property name="margin-end">6</property>
|
||||
<property name="margin-top">3</property>
|
||||
<property name="margin-bottom">3</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkEntry" id="name_entry">
|
||||
<property name="placeholder-text">Variable name</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="icon-name">edit-delete-symbolic</property>
|
||||
<property name="tooltip-text">Remove variable</property>
|
||||
<signal name="clicked" handler="on_remove_clicked" swapped="no"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
57
src/widgets/variable_row.py
Normal file
57
src/widgets/variable_row.py
Normal file
@ -0,0 +1,57 @@
|
||||
# variable_row.py
|
||||
#
|
||||
# Copyright 2025 Pavel Baksy
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from gi.repository import Gtk, GObject
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/cz/vesp/roster/widgets/variable-row.ui')
|
||||
class VariableRow(Gtk.Box):
|
||||
"""Widget for editing a variable name."""
|
||||
|
||||
__gtype_name__ = 'VariableRow'
|
||||
|
||||
name_entry = Gtk.Template.Child()
|
||||
remove_button = Gtk.Template.Child()
|
||||
|
||||
__gsignals__ = {
|
||||
'remove-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'changed': (GObject.SIGNAL_RUN_FIRST, None, ())
|
||||
}
|
||||
|
||||
def __init__(self, variable_name=""):
|
||||
super().__init__()
|
||||
self.name_entry.set_text(variable_name)
|
||||
self.name_entry.connect('changed', self._on_entry_changed)
|
||||
|
||||
def _on_entry_changed(self, entry):
|
||||
"""Handle changes to name entry."""
|
||||
self.emit('changed')
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_remove_clicked(self, button):
|
||||
"""Handle remove button click."""
|
||||
self.emit('remove-requested')
|
||||
|
||||
def get_variable_name(self):
|
||||
"""Return variable name."""
|
||||
return self.name_entry.get_text().strip()
|
||||
|
||||
def set_variable_name(self, name: str):
|
||||
"""Set variable name."""
|
||||
self.name_entry.set_text(name)
|
||||
@ -26,6 +26,7 @@ from .history_manager import HistoryManager
|
||||
from .project_manager import ProjectManager
|
||||
from .tab_manager import TabManager
|
||||
from .icon_picker_dialog import IconPickerDialog
|
||||
from .environments_dialog import EnvironmentsDialog
|
||||
from .request_tab_widget import RequestTabWidget
|
||||
from .widgets.history_item import HistoryItem
|
||||
from .widgets.project_item import ProjectItem
|
||||
@ -528,6 +529,7 @@ class RosterWindow(Adw.ApplicationWindow):
|
||||
item.connect('add-request-requested', self._on_add_to_project, project)
|
||||
item.connect('request-load-requested', self._on_load_request)
|
||||
item.connect('request-delete-requested', self._on_delete_request, project)
|
||||
item.connect('manage-environments-requested', self._on_manage_environments, project)
|
||||
self.projects_listbox.append(item)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
@ -628,6 +630,17 @@ class RosterWindow(Adw.ApplicationWindow):
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_manage_environments(self, widget, project):
|
||||
"""Show environments management dialog."""
|
||||
dialog = EnvironmentsDialog(project, self.project_manager)
|
||||
|
||||
def on_environments_updated(dlg):
|
||||
# Reload projects to reflect changes
|
||||
self._load_projects()
|
||||
|
||||
dialog.connect('environments-updated', on_environments_updated)
|
||||
dialog.present(self)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_save_request_clicked(self, button):
|
||||
"""Save current request to a project."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user