From 90bbcd1072380f200e083dc7c22a1d46862343ab Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Tue, 11 Aug 2026 22:36:40 +0200 Subject: [PATCH] recent uploads --- gui.py | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 2 deletions(-) diff --git a/gui.py b/gui.py index d6eca54..2cbca3d 100644 --- a/gui.py +++ b/gui.py @@ -24,17 +24,41 @@ def normalize_file_path(path_str: str) -> str: path_str = urllib.parse.unquote(path_str[7:]) return os.path.abspath(path_str) from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice -from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage +from PySide6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket from PySide6.QtWidgets import ( QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox, - QFormLayout, QMessageBox, QFileDialog, QSlider + QFormLayout, QMessageBox, QFileDialog, QSlider, QScrollArea, QFrame, QGridLayout, QWidget ) # Configuration paths CONFIG_DIR = os.path.expanduser("~/.config/f0ckm-uploader") CONFIG_PATH = os.path.join(CONFIG_DIR, "config.json") +HISTORY_PATH = os.path.join(CONFIG_DIR, "history.json") + +def load_history(): + if not os.path.exists(HISTORY_PATH): + return [] + try: + with open(HISTORY_PATH, "r", encoding="utf-8") as f: + return json.load(f) + except Exception: + return [] + +def save_history(history_list): + try: + os.makedirs(CONFIG_DIR, exist_ok=True) + with open(HISTORY_PATH, "w", encoding="utf-8") as f: + json.dump(history_list[:100], f, indent=2) + except Exception as e: + print(f"Error saving history: {e}") + +def add_history_entry(entry): + history = load_history() + history = [h for h in history if h.get("url") != entry.get("url")] + history.insert(0, entry) + save_history(history) DEFAULT_CONFIG = { "api_url": "", @@ -509,6 +533,201 @@ class SettingsDialog(QDialog): else: QMessageBox.critical(self, "Error", "Failed to save configuration file.") +# ========================================== +# Upload Gallery Window & Components +# ========================================== +class GalleryCard(QFrame): + def __init__(self, item, parent=None, on_delete=None): + super().__init__(parent) + self.item = item + self.on_delete = on_delete + self.setFrameShape(QFrame.StyledPanel) + self.setFixedHeight(195) + self.setStyleSheet(""" + GalleryCard { + background-color: #1e1e24; + border: 1px solid #333340; + border-radius: 8px; + } + GalleryCard:hover { + border: 1px solid #555575; + background-color: #242430; + } + """) + + layout = QVBoxLayout(self) + layout.setContentsMargins(8, 8, 8, 8) + layout.setSpacing(6) + + # Thumbnail + self.thumb_label = QLabel() + self.thumb_label.setFixedSize(160, 100) + self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.thumb_label.setStyleSheet("background-color: #121216; border-radius: 4px;") + + thumb_path = item.get("thumbnail_path", "") + file_path = item.get("file_path", "") + pixmap = None + if thumb_path and os.path.exists(thumb_path): + pixmap = QPixmap(thumb_path) + elif file_path and os.path.exists(file_path): + gen_thumb = generate_thumbnail(file_path) + if gen_thumb and os.path.exists(gen_thumb): + pixmap = QPixmap(gen_thumb) + + if pixmap and not pixmap.isNull(): + scaled = pixmap.scaled(160, 100, Qt.AspectRatioMode.KeepAspectRatioByExpanding, Qt.TransformationMode.SmoothTransformation) + x = max(0, (scaled.width() - 160) // 2) + y = max(0, (scaled.height() - 100) // 2) + self.thumb_label.setPixmap(scaled.copy(x, y, 160, 100)) + else: + self.thumb_label.setText("No Preview") + self.thumb_label.setStyleSheet("background-color: #121216; color: #666; font-size: 11px;") + + layout.addWidget(self.thumb_label) + + # Filename + fname = item.get("filename", "File") + lbl_name = QLabel(fname) + lbl_name.setStyleSheet("font-weight: bold; font-size: 11px; color: #e0e0e0;") + lbl_name.setToolTip(item.get("filename", "")) + layout.addWidget(lbl_name) + + # Timestamp + ts = item.get("timestamp", 0) + dt_str = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts)) if ts else "" + lbl_time = QLabel(dt_str) + lbl_time.setStyleSheet("font-size: 9px; color: #8888a0;") + layout.addWidget(lbl_time) + + # Action Buttons + btn_layout = QHBoxLayout() + btn_layout.setSpacing(4) + + btn_copy = QPushButton("Copy") + btn_copy.setToolTip("Copy URL to clipboard") + btn_copy.setStyleSheet("font-size: 10px; padding: 4px 6px;") + btn_copy.clicked.connect(self.on_copy_link) + + btn_open = QPushButton("Open") + btn_open.setToolTip("Open link in browser") + btn_open.setStyleSheet("font-size: 10px; padding: 4px 6px;") + btn_open.clicked.connect(self.on_open_url) + + btn_del = QPushButton("✕") + btn_del.setToolTip("Remove from history") + btn_del.setStyleSheet("font-size: 10px; padding: 4px 6px; color: #ff6666;") + btn_del.clicked.connect(self.on_remove_item) + + btn_layout.addWidget(btn_copy) + btn_layout.addWidget(btn_open) + btn_layout.addWidget(btn_del) + + layout.addLayout(btn_layout) + + def on_copy_link(self): + url = self.item.get("url", "") + if url: + QApplication.clipboard().setText(url) + + def on_open_url(self): + url = self.item.get("url", "") + if url: + QDesktopServices.openUrl(QUrl(url)) + + def on_remove_item(self): + if self.on_delete: + self.on_delete(self.item) + +class GalleryWindow(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("f0ckm Upload Gallery") + self.resize(760, 560) + app_icon = get_app_icon() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(15, 15, 15, 15) + main_layout.setSpacing(12) + + # Header bar + header_layout = QHBoxLayout() + + lbl_title = QLabel("Recent Uploads") + lbl_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #ffffff;") + header_layout.addWidget(lbl_title) + + header_layout.addStretch() + + self.txt_search = QLineEdit() + self.txt_search.setPlaceholderText("Search uploads...") + self.txt_search.setFixedWidth(200) + self.txt_search.textChanged.connect(self.load_items) + header_layout.addWidget(self.txt_search) + + btn_clear = QPushButton("Clear History") + btn_clear.clicked.connect(self.on_clear_history) + header_layout.addWidget(btn_clear) + + main_layout.addLayout(header_layout) + + # Scroll Area for Gallery Cards + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setStyleSheet("QScrollArea { border: 1px solid #333340; background: #141418; border-radius: 6px; }") + + self.scroll_content = QWidget() + self.grid_layout = QGridLayout(self.scroll_content) + self.grid_layout.setContentsMargins(12, 12, 12, 12) + self.grid_layout.setSpacing(12) + self.grid_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + + self.scroll_area.setWidget(self.scroll_content) + main_layout.addWidget(self.scroll_area) + + self.load_items() + + def load_items(self): + while self.grid_layout.count(): + child = self.grid_layout.takeAt(0) + if child.widget(): + child.widget().deleteLater() + + history = load_history() + query = self.txt_search.text().strip().lower() + if query: + history = [h for h in history if query in h.get("filename", "").lower() or query in h.get("url", "").lower()] + + if not history: + lbl_empty = QLabel("No uploads found.") + lbl_empty.setAlignment(Qt.AlignmentFlag.AlignCenter) + lbl_empty.setStyleSheet("color: #8888a0; font-size: 14px; margin: 40px;") + self.grid_layout.addWidget(lbl_empty, 0, 0) + return + + cols = 4 + for idx, item in enumerate(history): + card = GalleryCard(item, on_delete=self.delete_item) + row = idx // cols + col = idx % cols + self.grid_layout.addWidget(card, row, col) + + def delete_item(self, item): + history = load_history() + history = [h for h in history if h.get("url") != item.get("url")] + save_history(history) + self.load_items() + + def on_clear_history(self): + if QMessageBox.question(self, "Clear History", "Are you sure you want to clear your upload history?", QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: + save_history([]) + self.load_items() + # ========================================== # Dynamic Tray Digit Icon Painter # ========================================== @@ -827,6 +1046,10 @@ class SystemTrayApp(QObject): settings_action = QAction("Settings...", self.menu) settings_action.triggered.connect(self.on_open_settings) self.menu.addAction(settings_action) + + gallery_action = QAction("Recent Uploads Gallery", self.menu) + gallery_action.triggered.connect(self.on_open_gallery) + self.menu.addAction(gallery_action) self.menu.addSeparator() @@ -838,6 +1061,15 @@ class SystemTrayApp(QObject): self.tray.activated.connect(self.on_tray_activated) self.settings_dialog = None + self.gallery_window = None + + def on_open_gallery(self): + if self.gallery_window is None: + self.gallery_window = GalleryWindow() + self.gallery_window.load_items() + self.gallery_window.show() + self.gallery_window.raise_() + self.gallery_window.activateWindow() def show(self): self.tray.show() @@ -975,6 +1207,19 @@ class SystemTrayApp(QObject): if res.get("success") and res.get("url"): item_url = res.get("url") QApplication.clipboard().setText(item_url) + + # Add to history gallery + thumb = generate_thumbnail(file_path) + add_history_entry({ + "timestamp": time.time(), + "filename": os.path.basename(file_path), + "file_path": file_path, + "url": item_url, + "thumbnail_path": thumb if thumb and os.path.exists(thumb) else "" + }) + if hasattr(self, "gallery_window") and self.gallery_window and self.gallery_window.isVisible(): + self.gallery_window.load_items() + self.show_success_notification(file_path, item_url) else: msg = res.get("msg", "Upload failed")