v1.9.2: S3 right-click context menu — copy presigned download link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog
|
||||
|
||||
import customtkinter as ctk
|
||||
@@ -205,6 +206,23 @@ class S3Tab(ctk.CTkFrame):
|
||||
# Double-click to enter prefix (folder) or download file
|
||||
self._tree.bind("<Double-1>", self._on_double_click)
|
||||
|
||||
# Right-click context menu
|
||||
self._ctx_menu = tk.Menu(self._tree, tearoff=0)
|
||||
self._ctx_menu.add_command(
|
||||
label=icon_text("copy", t("s3_copy_link")),
|
||||
command=self._copy_link,
|
||||
)
|
||||
self._ctx_menu.add_command(
|
||||
label=icon_text("download", t("s3_download")),
|
||||
command=self._download,
|
||||
)
|
||||
self._ctx_menu.add_separator()
|
||||
self._ctx_menu.add_command(
|
||||
label=icon_text("delete", t("s3_delete")),
|
||||
command=self._delete,
|
||||
)
|
||||
self._tree.bind("<Button-3>", self._on_right_click)
|
||||
|
||||
# Dark treeview style
|
||||
style = ttk.Style()
|
||||
style.configure("Dark.Treeview",
|
||||
@@ -460,6 +478,53 @@ class S3Tab(ctk.CTkFrame):
|
||||
# Double-click file = download
|
||||
self._download()
|
||||
|
||||
def _on_right_click(self, event):
|
||||
"""Show context menu on right-click."""
|
||||
item_id = self._tree.identify_row(event.y)
|
||||
if not item_id:
|
||||
return
|
||||
self._tree.selection_set(item_id)
|
||||
# Check if it's a file (not a folder)
|
||||
item = self._tree.item(item_id)
|
||||
name = item["values"][0] if item["values"] else ""
|
||||
if not isinstance(name, str):
|
||||
name = str(name)
|
||||
is_folder = name.endswith("/")
|
||||
# Enable/disable menu items based on type
|
||||
self._ctx_menu.entryconfigure(0, state="normal" if not is_folder else "disabled") # Copy link
|
||||
self._ctx_menu.entryconfigure(1, state="normal" if not is_folder else "disabled") # Download
|
||||
self._ctx_menu.entryconfigure(3, state="normal" if not is_folder else "disabled") # Delete
|
||||
self._ctx_menu.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
def _copy_link(self):
|
||||
"""Generate presigned URL and copy to clipboard."""
|
||||
sel = self._tree.selection()
|
||||
if not sel or not self._client or not self._current_bucket:
|
||||
return
|
||||
item = self._tree.item(sel[0])
|
||||
name = item["values"][0] if item["values"] else ""
|
||||
if not isinstance(name, str):
|
||||
name = str(name)
|
||||
if name.endswith("/"):
|
||||
return
|
||||
|
||||
key = self._current_prefix + name
|
||||
self._status_label.configure(text=t("s3_generating_link"))
|
||||
|
||||
def _do():
|
||||
url = self._client.generate_presigned_url(self._current_bucket, key)
|
||||
self.after(0, lambda: self._on_link_ready(url))
|
||||
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
|
||||
def _on_link_ready(self, url: str | None):
|
||||
if url:
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(url)
|
||||
self._status_label.configure(text=t("s3_link_copied"))
|
||||
else:
|
||||
self._status_label.configure(text=t("s3_link_failed"))
|
||||
|
||||
def _go_back(self):
|
||||
if self._nav_stack:
|
||||
self._current_prefix = self._nav_stack.pop()
|
||||
|
||||
Reference in New Issue
Block a user