Add cURL export functionality with custom icon and extensible architecture
This commit is contained in:
parent
8c842ee3f1
commit
b9b9619425
2
data/icons/hicolor/symbolic/apps/export-symbolic.svg
Normal file
2
data/icons/hicolor/symbolic/apps/export-symbolic.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 4 5.992188 h 1 c 0.257812 0 0.527344 -0.128907 0.71875 -0.3125 l 1.28125 -1.28125 v 6.59375 h 2 v -6.59375 l 1.28125 1.28125 c 0.191406 0.183593 0.410156 0.3125 0.71875 0.3125 h 1 v -1 c 0 -0.308594 -0.089844 -0.550782 -0.28125 -0.75 l -3.71875 -3.65625 l -3.71875 3.65625 c -0.191406 0.199218 -0.28125 0.441406 -0.28125 0.75 z m 0 0"/><path d="m 4.039062 6.957031 c -1.664062 0 -3.03125 1.371094 -3.03125 3.035157 v 1.972656 c 0 1.664062 1.367188 3.035156 3.03125 3.035156 h 7.917969 c 1.664063 0 3.03125 -1.371094 3.03125 -3.035156 v -1.972656 c 0 -1.664063 -1.367187 -3.035157 -3.03125 -3.035157 h -0.957031 v 2 h 0.957031 c 0.589844 0 1.03125 0.445313 1.03125 1.035157 v 1.972656 c 0 0.589844 -0.441406 1.035156 -1.03125 1.035156 h -7.917969 c -0.589843 0 -1.03125 -0.445312 -1.03125 -1.035156 v -1.972656 c 0 -0.589844 0.441407 -1.035157 1.03125 -1.035157 h 0.960938 v -2 z m 0 0"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@ -11,3 +11,9 @@ install_data(
|
|||||||
symbolic_dir / ('@0@-symbolic.svg').format(application_id),
|
symbolic_dir / ('@0@-symbolic.svg').format(application_id),
|
||||||
install_dir: get_option('datadir') / 'icons' / symbolic_dir
|
install_dir: get_option('datadir') / 'icons' / symbolic_dir
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Install custom export icon
|
||||||
|
install_data(
|
||||||
|
symbolic_dir / 'export-symbolic.svg',
|
||||||
|
install_dir: get_option('datadir') / 'icons' / symbolic_dir
|
||||||
|
)
|
||||||
|
|||||||
60
src/export-dialog.ui
Normal file
60
src/export-dialog.ui
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<requires lib="Adw" version="1.0"/>
|
||||||
|
|
||||||
|
<template class="ExportDialog" parent="AdwDialog">
|
||||||
|
<property name="title">Export Request</property>
|
||||||
|
<property name="content-width">600</property>
|
||||||
|
<property name="content-height">400</property>
|
||||||
|
|
||||||
|
<property name="child">
|
||||||
|
<object class="AdwToolbarView">
|
||||||
|
<child type="top">
|
||||||
|
<object class="AdwHeaderBar">
|
||||||
|
<property name="show-title">true</property>
|
||||||
|
<child type="end">
|
||||||
|
<object class="GtkButton" id="copy_button">
|
||||||
|
<property name="label">Copy</property>
|
||||||
|
<property name="tooltip-text">Copy to Clipboard</property>
|
||||||
|
<signal name="clicked" handler="on_copy_clicked"/>
|
||||||
|
<style>
|
||||||
|
<class name="suggested-action"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<property name="content">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">12</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>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="vexpand">true</property>
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="export_textview">
|
||||||
|
<property name="editable">false</property>
|
||||||
|
<property name="monospace">true</property>
|
||||||
|
<property name="wrap-mode">none</property>
|
||||||
|
<property name="left-margin">12</property>
|
||||||
|
<property name="right-margin">12</property>
|
||||||
|
<property name="top-margin">12</property>
|
||||||
|
<property name="bottom-margin">12</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
90
src/export_dialog.py
Normal file
90
src/export_dialog.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# export_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, Gdk, GLib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path='/cz/bugsy/roster/export-dialog.ui')
|
||||||
|
class ExportDialog(Adw.Dialog):
|
||||||
|
"""Dialog for exporting HTTP requests in various formats."""
|
||||||
|
|
||||||
|
__gtype_name__ = 'ExportDialog'
|
||||||
|
|
||||||
|
export_textview = Gtk.Template.Child()
|
||||||
|
copy_button = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, request, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize export dialog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: HttpRequest to export (should have variables substituted)
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.request = request
|
||||||
|
self._populate_export()
|
||||||
|
|
||||||
|
def _populate_export(self):
|
||||||
|
"""Generate and display the export content."""
|
||||||
|
try:
|
||||||
|
# Get cURL exporter
|
||||||
|
from .exporters import ExporterRegistry
|
||||||
|
exporter = ExporterRegistry.get_exporter('curl')
|
||||||
|
|
||||||
|
# Generate cURL command
|
||||||
|
curl_command = exporter.export(self.request)
|
||||||
|
|
||||||
|
# Display in textview
|
||||||
|
buffer = self.export_textview.get_buffer()
|
||||||
|
buffer.set_text(curl_command)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Export generation failed: {e}")
|
||||||
|
buffer = self.export_textview.get_buffer()
|
||||||
|
buffer.set_text(f"Error generating export: {str(e)}")
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def on_copy_clicked(self, button):
|
||||||
|
"""Copy export content to clipboard."""
|
||||||
|
# Get text from buffer
|
||||||
|
buffer = self.export_textview.get_buffer()
|
||||||
|
start = buffer.get_start_iter()
|
||||||
|
end = buffer.get_end_iter()
|
||||||
|
text = buffer.get_text(start, end, False)
|
||||||
|
|
||||||
|
# Copy to clipboard using Gdk.Clipboard API
|
||||||
|
display = Gdk.Display.get_default()
|
||||||
|
clipboard = display.get_clipboard()
|
||||||
|
clipboard.set(text)
|
||||||
|
|
||||||
|
# Visual feedback: temporarily change button label
|
||||||
|
original_label = button.get_label()
|
||||||
|
button.set_label("Copied!")
|
||||||
|
|
||||||
|
# Reset after 2 seconds
|
||||||
|
def reset_label():
|
||||||
|
button.set_label(original_label)
|
||||||
|
return False # Don't repeat
|
||||||
|
|
||||||
|
GLib.timeout_add(2000, reset_label)
|
||||||
|
|
||||||
|
logger.debug("Export copied to clipboard")
|
||||||
24
src/exporters/__init__.py
Normal file
24
src/exporters/__init__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# exporters package
|
||||||
|
#
|
||||||
|
# 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 .registry import ExporterRegistry
|
||||||
|
from .base_exporter import BaseExporter
|
||||||
|
from .curl_exporter import CurlExporter
|
||||||
|
|
||||||
|
__all__ = ['ExporterRegistry', 'BaseExporter', 'CurlExporter']
|
||||||
50
src/exporters/base_exporter.py
Normal file
50
src/exporters/base_exporter.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# base_exporter.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 abc import ABC, abstractmethod
|
||||||
|
from ..models import HttpRequest
|
||||||
|
|
||||||
|
|
||||||
|
class BaseExporter(ABC):
|
||||||
|
"""Abstract base class for request exporters."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def export(self, request: HttpRequest) -> str:
|
||||||
|
"""
|
||||||
|
Export HTTP request to target format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: HttpRequest with method, url, headers, body
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted export string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def format_name(self) -> str:
|
||||||
|
"""Human-readable format name (e.g., 'cURL', 'Insomnia')."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def file_extension(self) -> str:
|
||||||
|
"""File extension for export (e.g., 'sh', 'json')."""
|
||||||
|
pass
|
||||||
70
src/exporters/curl_exporter.py
Normal file
70
src/exporters/curl_exporter.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# curl_exporter.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
|
||||||
|
|
||||||
|
import shlex
|
||||||
|
from .base_exporter import BaseExporter
|
||||||
|
from ..models import HttpRequest
|
||||||
|
|
||||||
|
|
||||||
|
class CurlExporter(BaseExporter):
|
||||||
|
"""Export HTTP requests as cURL commands."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def format_name(self) -> str:
|
||||||
|
return "cURL"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def file_extension(self) -> str:
|
||||||
|
return "sh"
|
||||||
|
|
||||||
|
def export(self, request: HttpRequest) -> str:
|
||||||
|
"""
|
||||||
|
Generate cURL command with proper shell escaping.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
curl -X POST \
|
||||||
|
'https://api.example.com/endpoint' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data '{"key": "value"}'
|
||||||
|
"""
|
||||||
|
parts = ["curl"]
|
||||||
|
|
||||||
|
# Add HTTP method (skip if GET - it's default)
|
||||||
|
if request.method != "GET":
|
||||||
|
parts.append(f"-X {request.method}")
|
||||||
|
|
||||||
|
# Add URL (always quoted for safety)
|
||||||
|
parts.append(shlex.quote(request.url))
|
||||||
|
|
||||||
|
# Add headers
|
||||||
|
for key, value in request.headers.items():
|
||||||
|
header_str = f"{key}: {value}"
|
||||||
|
parts.append(f"-H {shlex.quote(header_str)}")
|
||||||
|
|
||||||
|
# Add body for POST/PUT/PATCH/DELETE methods
|
||||||
|
if request.body and request.method in ["POST", "PUT", "PATCH", "DELETE"]:
|
||||||
|
parts.append(f"--data {shlex.quote(request.body)}")
|
||||||
|
|
||||||
|
# Format as multiline with backslash continuations
|
||||||
|
return " \\\n ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
# Auto-register on import
|
||||||
|
from .registry import ExporterRegistry
|
||||||
|
ExporterRegistry.register('curl', CurlExporter)
|
||||||
46
src/exporters/registry.py
Normal file
46
src/exporters/registry.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# registry.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 typing import Dict, List, Tuple, Type
|
||||||
|
from .base_exporter import BaseExporter
|
||||||
|
|
||||||
|
|
||||||
|
class ExporterRegistry:
|
||||||
|
"""Registry for managing export formats."""
|
||||||
|
|
||||||
|
_exporters: Dict[str, Type[BaseExporter]] = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls, format_id: str, exporter_class: Type[BaseExporter]):
|
||||||
|
"""Register an exporter class."""
|
||||||
|
cls._exporters[format_id] = exporter_class
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_exporter(cls, format_id: str) -> BaseExporter:
|
||||||
|
"""Get exporter instance by format ID."""
|
||||||
|
exporter_class = cls._exporters.get(format_id)
|
||||||
|
if not exporter_class:
|
||||||
|
raise ValueError(f"Unknown export format: {format_id}")
|
||||||
|
return exporter_class()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_formats(cls) -> List[Tuple[str, str]]:
|
||||||
|
"""Get all registered formats as [(id, name), ...]."""
|
||||||
|
return [(format_id, cls._exporters[format_id]().format_name)
|
||||||
|
for format_id in cls._exporters.keys()]
|
||||||
@ -32,8 +32,9 @@
|
|||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="label">Projects</property>
|
<property name="label">Projects</property>
|
||||||
|
<property name="margin-start">6</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="heading"/>
|
<class name="title"/>
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -96,6 +97,17 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
|
<child type="start">
|
||||||
|
<object class="GtkButton" id="export_request_button">
|
||||||
|
<property name="icon-name">export-symbolic</property>
|
||||||
|
<property name="tooltip-text">Export as cURL</property>
|
||||||
|
<signal name="clicked" handler="on_export_request_clicked"/>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
<!-- Right side buttons -->
|
<!-- Right side buttons -->
|
||||||
<child type="end">
|
<child type="end">
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
|||||||
@ -39,6 +39,7 @@ roster_sources = [
|
|||||||
'constants.py',
|
'constants.py',
|
||||||
'icon_picker_dialog.py',
|
'icon_picker_dialog.py',
|
||||||
'environments_dialog.py',
|
'environments_dialog.py',
|
||||||
|
'export_dialog.py',
|
||||||
'preferences_dialog.py',
|
'preferences_dialog.py',
|
||||||
'request_tab_widget.py',
|
'request_tab_widget.py',
|
||||||
'script_executor.py',
|
'script_executor.py',
|
||||||
@ -46,6 +47,16 @@ roster_sources = [
|
|||||||
|
|
||||||
install_data(roster_sources, install_dir: moduledir)
|
install_data(roster_sources, install_dir: moduledir)
|
||||||
|
|
||||||
|
# Install exporters submodule
|
||||||
|
exporters_sources = [
|
||||||
|
'exporters/__init__.py',
|
||||||
|
'exporters/base_exporter.py',
|
||||||
|
'exporters/curl_exporter.py',
|
||||||
|
'exporters/registry.py',
|
||||||
|
]
|
||||||
|
|
||||||
|
install_data(exporters_sources, install_dir: moduledir / 'exporters')
|
||||||
|
|
||||||
# Install widgets submodule
|
# Install widgets submodule
|
||||||
widgets_sources = [
|
widgets_sources = [
|
||||||
'widgets/__init__.py',
|
'widgets/__init__.py',
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
<file preprocess="xml-stripblanks">preferences-dialog.ui</file>
|
<file preprocess="xml-stripblanks">preferences-dialog.ui</file>
|
||||||
<file preprocess="xml-stripblanks">icon-picker-dialog.ui</file>
|
<file preprocess="xml-stripblanks">icon-picker-dialog.ui</file>
|
||||||
<file preprocess="xml-stripblanks">environments-dialog.ui</file>
|
<file preprocess="xml-stripblanks">environments-dialog.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">export-dialog.ui</file>
|
||||||
|
<file alias="icons/export-symbolic.svg">../data/icons/hicolor/symbolic/apps/export-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">widgets/header-row.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/history-item.ui</file>
|
||||||
<file preprocess="xml-stripblanks">widgets/project-item.ui</file>
|
<file preprocess="xml-stripblanks">widgets/project-item.ui</file>
|
||||||
|
|||||||
@ -55,6 +55,7 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
# Top bar widgets
|
# Top bar widgets
|
||||||
save_request_button = Gtk.Template.Child()
|
save_request_button = Gtk.Template.Child()
|
||||||
|
export_request_button = Gtk.Template.Child()
|
||||||
new_request_button = Gtk.Template.Child()
|
new_request_button = Gtk.Template.Child()
|
||||||
tab_view = Gtk.Template.Child()
|
tab_view = Gtk.Template.Child()
|
||||||
tab_bar = Gtk.Template.Child()
|
tab_bar = Gtk.Template.Child()
|
||||||
@ -336,7 +337,7 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
"""Create a new tab with RequestTabWidget."""
|
"""Create a new tab with RequestTabWidget."""
|
||||||
# Create tab in tab manager
|
# Create tab in tab manager
|
||||||
if not request:
|
if not request:
|
||||||
request = HttpRequest(method="GET", url="", headers={}, body="", syntax="RAW")
|
request = HttpRequest(method="GET", url="", headers=HttpRequest.default_headers(), body="", syntax="RAW")
|
||||||
|
|
||||||
tab = self.tab_manager.create_tab(name, request, saved_request_id, response,
|
tab = self.tab_manager.create_tab(name, request, saved_request_id, response,
|
||||||
project_id, selected_environment_id, scripts)
|
project_id, selected_environment_id, scripts)
|
||||||
@ -1078,6 +1079,47 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
dialog.connect("response", on_response)
|
dialog.connect("response", on_response)
|
||||||
dialog.present(self)
|
dialog.present(self)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def on_export_request_clicked(self, button):
|
||||||
|
"""Handle Export button click - export request as cURL command."""
|
||||||
|
# Get current tab
|
||||||
|
page = self.tab_view.get_selected_page()
|
||||||
|
if not page:
|
||||||
|
self._show_toast("No active request")
|
||||||
|
return
|
||||||
|
|
||||||
|
widget = self.page_to_widget.get(page)
|
||||||
|
if not widget:
|
||||||
|
self._show_toast("No active request")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get request from widget (raw with {{variables}})
|
||||||
|
request = widget.get_request()
|
||||||
|
|
||||||
|
# Validate URL
|
||||||
|
if not request.url.strip():
|
||||||
|
self._show_toast("Cannot export: URL is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply variable substitution if environment is selected
|
||||||
|
substituted_request = request
|
||||||
|
if widget.selected_environment_id:
|
||||||
|
env = widget.get_selected_environment()
|
||||||
|
if env:
|
||||||
|
from .variable_substitution import VariableSubstitution
|
||||||
|
substituted_request, undefined = VariableSubstitution.substitute_request(request, env)
|
||||||
|
|
||||||
|
# Warn about undefined variables
|
||||||
|
if undefined:
|
||||||
|
logger.warning(f"Undefined variables in export: {', '.join(undefined)}")
|
||||||
|
# Show warning toast but continue with export
|
||||||
|
self._show_toast(f"Warning: {len(undefined)} undefined variable(s)")
|
||||||
|
|
||||||
|
# Show export dialog with substituted request
|
||||||
|
from .export_dialog import ExportDialog
|
||||||
|
dialog = ExportDialog(substituted_request)
|
||||||
|
dialog.present(self)
|
||||||
|
|
||||||
def _mark_tab_as_saved(self, saved_request_id, name, request, scripts=None):
|
def _mark_tab_as_saved(self, saved_request_id, name, request, scripts=None):
|
||||||
"""Mark the current tab as saved (clear modified flag)."""
|
"""Mark the current tab as saved (clear modified flag)."""
|
||||||
page = self.tab_view.get_selected_page()
|
page = self.tab_view.get_selected_page()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user