recent uploads
This commit is contained in:
249
gui.py
249
gui.py
@@ -24,17 +24,41 @@ def normalize_file_path(path_str: str) -> str:
|
|||||||
path_str = urllib.parse.unquote(path_str[7:])
|
path_str = urllib.parse.unquote(path_str[7:])
|
||||||
return os.path.abspath(path_str)
|
return os.path.abspath(path_str)
|
||||||
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice
|
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.QtNetwork import QNetworkAccessManager, QNetworkRequest, QHttpMultiPart, QHttpPart, QNetworkReply, QLocalServer, QLocalSocket
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout,
|
||||||
QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox,
|
QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton, QGroupBox,
|
||||||
QFormLayout, QMessageBox, QFileDialog, QSlider
|
QFormLayout, QMessageBox, QFileDialog, QSlider, QScrollArea, QFrame, QGridLayout, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configuration paths
|
# Configuration paths
|
||||||
CONFIG_DIR = os.path.expanduser("~/.config/f0ckm-uploader")
|
CONFIG_DIR = os.path.expanduser("~/.config/f0ckm-uploader")
|
||||||
CONFIG_PATH = os.path.join(CONFIG_DIR, "config.json")
|
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 = {
|
DEFAULT_CONFIG = {
|
||||||
"api_url": "",
|
"api_url": "",
|
||||||
@@ -509,6 +533,201 @@ class SettingsDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "Error", "Failed to save configuration file.")
|
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
|
# Dynamic Tray Digit Icon Painter
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -828,6 +1047,10 @@ class SystemTrayApp(QObject):
|
|||||||
settings_action.triggered.connect(self.on_open_settings)
|
settings_action.triggered.connect(self.on_open_settings)
|
||||||
self.menu.addAction(settings_action)
|
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()
|
self.menu.addSeparator()
|
||||||
|
|
||||||
quit_action = QAction("Quit", self.menu)
|
quit_action = QAction("Quit", self.menu)
|
||||||
@@ -838,6 +1061,15 @@ class SystemTrayApp(QObject):
|
|||||||
self.tray.activated.connect(self.on_tray_activated)
|
self.tray.activated.connect(self.on_tray_activated)
|
||||||
|
|
||||||
self.settings_dialog = None
|
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):
|
def show(self):
|
||||||
self.tray.show()
|
self.tray.show()
|
||||||
@@ -975,6 +1207,19 @@ class SystemTrayApp(QObject):
|
|||||||
if res.get("success") and res.get("url"):
|
if res.get("success") and res.get("url"):
|
||||||
item_url = res.get("url")
|
item_url = res.get("url")
|
||||||
QApplication.clipboard().setText(item_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)
|
self.show_success_notification(file_path, item_url)
|
||||||
else:
|
else:
|
||||||
msg = res.get("msg", "Upload failed")
|
msg = res.get("msg", "Upload failed")
|
||||||
|
|||||||
Reference in New Issue
Block a user