fix recent uploads gallery
This commit is contained in:
298
gui.py
298
gui.py
@@ -17,7 +17,7 @@ import threading
|
|||||||
import signal
|
import signal
|
||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData, QProcess
|
from PySide6.QtCore import Qt, QThread, QObject, Signal, QUrl, QFile, QIODevice, QTimer, QMimeData, QProcess, QRunnable, QThreadPool
|
||||||
from PySide6.QtGui import QIcon, QAction, QActionGroup, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag, QClipboard
|
from PySide6.QtGui import QIcon, QAction, QActionGroup, QPixmap, QPainter, QColor, QFont, QPen, QImage, QDesktopServices, QDrag, QClipboard
|
||||||
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 (
|
||||||
@@ -215,11 +215,20 @@ def save_history(history_list):
|
|||||||
try:
|
try:
|
||||||
os.makedirs(CONFIG_DIR, exist_ok=True)
|
os.makedirs(CONFIG_DIR, exist_ok=True)
|
||||||
with open(HISTORY_PATH, "w", encoding="utf-8") as f:
|
with open(HISTORY_PATH, "w", encoding="utf-8") as f:
|
||||||
json.dump(history_list[:100], f, indent=2)
|
json.dump(history_list[:500], f, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving history: {e}")
|
print(f"Error saving history: {e}")
|
||||||
|
|
||||||
def add_history_entry(entry):
|
def add_history_entry(entry):
|
||||||
|
if isinstance(entry, dict) and "instance_name" not in entry:
|
||||||
|
config = get_env_config()
|
||||||
|
instances = config.get("instances", [])
|
||||||
|
idx = config.get("active_instance_index", 0)
|
||||||
|
if 0 <= idx < len(instances):
|
||||||
|
entry["instance_name"] = instances[idx].get("name", "Default Instance")
|
||||||
|
else:
|
||||||
|
entry["instance_name"] = "Default Instance"
|
||||||
|
|
||||||
history = load_history()
|
history = load_history()
|
||||||
history = [h for h in history if h.get("url") != entry.get("url")]
|
history = [h for h in history if h.get("url") != entry.get("url")]
|
||||||
history.insert(0, entry)
|
history.insert(0, entry)
|
||||||
@@ -916,13 +925,17 @@ class SettingsDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "Error", "Failed to save configuration file.")
|
QMessageBox.critical(self, "Error", "Failed to save configuration file.")
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Draggable File Label Widget
|
||||||
|
# ==========================================
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# Draggable File Label Widget
|
# Draggable File Label Widget
|
||||||
# ==========================================
|
# ==========================================
|
||||||
class DraggableImageLabel(QLabel):
|
class DraggableImageLabel(QLabel):
|
||||||
def __init__(self, file_path, parent=None):
|
def __init__(self, item, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.file_path = file_path
|
self.item = item if isinstance(item, dict) else {"file_path": str(item)}
|
||||||
|
self.file_path = self.item.get("file_path", "")
|
||||||
self.drag_start_pos = None
|
self.drag_start_pos = None
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
@@ -930,6 +943,19 @@ class DraggableImageLabel(QLabel):
|
|||||||
self.drag_start_pos = event.position().toPoint()
|
self.drag_start_pos = event.position().toPoint()
|
||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if event.button() == Qt.MouseButton.LeftButton and self.drag_start_pos is not None:
|
||||||
|
delta = (event.position().toPoint() - self.drag_start_pos).manhattanLength()
|
||||||
|
if delta < QApplication.startDragDistance():
|
||||||
|
file_path = self.item.get("file_path", "")
|
||||||
|
url = self.item.get("url", "")
|
||||||
|
if file_path and os.path.exists(file_path):
|
||||||
|
QDesktopServices.openUrl(QUrl.fromLocalFile(file_path))
|
||||||
|
elif url:
|
||||||
|
QDesktopServices.openUrl(QUrl(url))
|
||||||
|
self.drag_start_pos = None
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
def mouseMoveEvent(self, event):
|
||||||
if not (event.buttons() & Qt.MouseButton.LeftButton):
|
if not (event.buttons() & Qt.MouseButton.LeftButton):
|
||||||
return
|
return
|
||||||
@@ -948,15 +974,52 @@ class DraggableImageLabel(QLabel):
|
|||||||
drag.setPixmap(self.pixmap().scaled(80, 80, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
|
drag.setPixmap(self.pixmap().scaled(80, 80, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
|
||||||
|
|
||||||
drag.exec(Qt.DropAction.CopyAction)
|
drag.exec(Qt.DropAction.CopyAction)
|
||||||
|
self.drag_start_pos = None
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# Upload Gallery Window & Components
|
# Upload Gallery Window & Components
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
THUMBNAIL_CACHE = {}
|
||||||
|
|
||||||
|
class ThumbnailWorkerSignals(QObject):
|
||||||
|
finished = Signal(dict, QImage)
|
||||||
|
|
||||||
|
class ThumbnailWorker(QRunnable):
|
||||||
|
def __init__(self, item, callback):
|
||||||
|
super().__init__()
|
||||||
|
self.item = item
|
||||||
|
self.signals = ThumbnailWorkerSignals()
|
||||||
|
self.signals.finished.connect(callback)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
thumb_path = self.item.get("thumbnail_path", "")
|
||||||
|
file_path = self.item.get("file_path", "")
|
||||||
|
qimg = None
|
||||||
|
if thumb_path and os.path.exists(thumb_path):
|
||||||
|
qimg = QImage(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):
|
||||||
|
qimg = QImage(gen_thumb)
|
||||||
|
self.item["thumbnail_path"] = gen_thumb
|
||||||
|
|
||||||
|
if qimg is None or qimg.isNull():
|
||||||
|
self.signals.finished.emit(self.item, QImage())
|
||||||
|
else:
|
||||||
|
self.signals.finished.emit(self.item, qimg)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self.signals.finished.emit(self.item, QImage())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
class GalleryCard(QFrame):
|
class GalleryCard(QFrame):
|
||||||
def __init__(self, item, parent=None, on_delete=None):
|
def __init__(self, item, parent=None, on_delete=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.item = item
|
self.item = item
|
||||||
self.on_delete = on_delete
|
self.on_delete = on_delete
|
||||||
|
self._is_destroyed = False
|
||||||
self.setFrameShape(QFrame.StyledPanel)
|
self.setFrameShape(QFrame.StyledPanel)
|
||||||
self.setFixedHeight(190)
|
self.setFixedHeight(190)
|
||||||
self.setMinimumWidth(160)
|
self.setMinimumWidth(160)
|
||||||
@@ -977,35 +1040,15 @@ class GalleryCard(QFrame):
|
|||||||
layout.setContentsMargins(8, 8, 8, 8)
|
layout.setContentsMargins(8, 8, 8, 8)
|
||||||
layout.setSpacing(6)
|
layout.setSpacing(6)
|
||||||
|
|
||||||
# Thumbnail with Drag & Drop support
|
# Thumbnail with Drag & Drop & Click support
|
||||||
file_path = item.get("file_path", "")
|
self.thumb_label = DraggableImageLabel(item)
|
||||||
self.thumb_label = DraggableImageLabel(file_path)
|
|
||||||
self.thumb_label.setFixedHeight(95)
|
self.thumb_label.setFixedHeight(95)
|
||||||
self.thumb_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
self.thumb_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.thumb_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.thumb_label.setCursor(Qt.CursorShape.OpenHandCursor)
|
self.thumb_label.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.thumb_label.setToolTip("Click & Drag to drop file into other apps")
|
self.thumb_label.setToolTip("Click to open file | Drag to drop into other apps")
|
||||||
self.thumb_label.setStyleSheet("background-color: #121216; border-radius: 4px;")
|
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)
|
layout.addWidget(self.thumb_label)
|
||||||
|
|
||||||
# Filename
|
# Filename
|
||||||
@@ -1015,12 +1058,37 @@ class GalleryCard(QFrame):
|
|||||||
lbl_name.setToolTip(item.get("filename", ""))
|
lbl_name.setToolTip(item.get("filename", ""))
|
||||||
layout.addWidget(lbl_name)
|
layout.addWidget(lbl_name)
|
||||||
|
|
||||||
|
# Metadata Row: Filetype badge + Timestamp + Instance badge
|
||||||
|
meta_layout = QHBoxLayout()
|
||||||
|
meta_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
meta_layout.setSpacing(4)
|
||||||
|
|
||||||
|
# Filetype badge
|
||||||
|
ext = os.path.splitext(item.get("filename", "") or item.get("file_path", "") or item.get("url", ""))[1].replace(".", "").upper()
|
||||||
|
if not ext:
|
||||||
|
ext = "FILE"
|
||||||
|
lbl_ext = QLabel(ext)
|
||||||
|
lbl_ext.setStyleSheet("background-color: #2a2a38; color: #7f9cf5; border-radius: 3px; font-weight: bold; font-size: 9px; padding: 1px 4px;")
|
||||||
|
meta_layout.addWidget(lbl_ext)
|
||||||
|
|
||||||
# Timestamp
|
# Timestamp
|
||||||
ts = item.get("timestamp", 0)
|
ts = item.get("timestamp", 0)
|
||||||
dt_str = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts)) if ts else ""
|
dt_str = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts)) if ts else ""
|
||||||
lbl_time = QLabel(dt_str)
|
lbl_time = QLabel(dt_str)
|
||||||
lbl_time.setStyleSheet("font-size: 9px; color: #8888a0;")
|
lbl_time.setStyleSheet("font-size: 9px; color: #8888a0;")
|
||||||
layout.addWidget(lbl_time)
|
meta_layout.addWidget(lbl_time)
|
||||||
|
|
||||||
|
meta_layout.addStretch()
|
||||||
|
|
||||||
|
# Instance badge if available
|
||||||
|
inst_name = item.get("instance_name", "")
|
||||||
|
if inst_name:
|
||||||
|
lbl_inst = QLabel(inst_name)
|
||||||
|
lbl_inst.setStyleSheet("background-color: #262630; color: #a0a0b0; border-radius: 3px; font-size: 9px; padding: 1px 4px;")
|
||||||
|
lbl_inst.setToolTip(f"Instance: {inst_name}")
|
||||||
|
meta_layout.addWidget(lbl_inst)
|
||||||
|
|
||||||
|
layout.addLayout(meta_layout)
|
||||||
|
|
||||||
# Action Buttons
|
# Action Buttons
|
||||||
btn_layout = QHBoxLayout()
|
btn_layout = QHBoxLayout()
|
||||||
@@ -1047,6 +1115,49 @@ class GalleryCard(QFrame):
|
|||||||
|
|
||||||
layout.addLayout(btn_layout)
|
layout.addLayout(btn_layout)
|
||||||
|
|
||||||
|
self.load_thumbnail_async()
|
||||||
|
|
||||||
|
def load_thumbnail_async(self):
|
||||||
|
cache_key = self.item.get("url") or self.item.get("file_path") or self.item.get("thumbnail_path")
|
||||||
|
if cache_key and cache_key in THUMBNAIL_CACHE:
|
||||||
|
self.thumb_label.setPixmap(THUMBNAIL_CACHE[cache_key])
|
||||||
|
else:
|
||||||
|
self.thumb_label.setText("Loading...")
|
||||||
|
self.thumb_label.setStyleSheet("background-color: #121216; color: #666; font-size: 11px;")
|
||||||
|
worker = ThumbnailWorker(self.item, self.on_thumbnail_loaded)
|
||||||
|
QThreadPool.globalInstance().start(worker)
|
||||||
|
|
||||||
|
def on_thumbnail_loaded(self, item, qimg):
|
||||||
|
try:
|
||||||
|
if getattr(self, "_is_destroyed", False):
|
||||||
|
return
|
||||||
|
if not qimg or qimg.isNull():
|
||||||
|
self.thumb_label.setText("No Preview")
|
||||||
|
self.thumb_label.setStyleSheet("background-color: #121216; color: #666; font-size: 11px;")
|
||||||
|
return
|
||||||
|
|
||||||
|
scaled = qimg.scaled(160, 100, Qt.AspectRatioMode.KeepAspectRatioByExpanding, Qt.TransformationMode.SmoothTransformation)
|
||||||
|
x = max(0, (scaled.width() - 160) // 2)
|
||||||
|
y = max(0, (scaled.height() - 100) // 2)
|
||||||
|
cropped = scaled.copy(x, y, 160, 100)
|
||||||
|
pixmap = QPixmap.fromImage(cropped)
|
||||||
|
|
||||||
|
cache_key = item.get("url") or item.get("file_path") or item.get("thumbnail_path")
|
||||||
|
if cache_key:
|
||||||
|
THUMBNAIL_CACHE[cache_key] = pixmap
|
||||||
|
|
||||||
|
self.thumb_label.setPixmap(pixmap)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self.thumb_label.setText("No Preview")
|
||||||
|
self.thumb_label.setStyleSheet("background-color: #121216; color: #666; font-size: 11px;")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def deleteLater(self):
|
||||||
|
self._is_destroyed = True
|
||||||
|
super().deleteLater()
|
||||||
|
|
||||||
def on_copy_link(self):
|
def on_copy_link(self):
|
||||||
url = self.item.get("url", "")
|
url = self.item.get("url", "")
|
||||||
if url:
|
if url:
|
||||||
@@ -1062,10 +1173,18 @@ class GalleryCard(QFrame):
|
|||||||
self.on_delete(self.item)
|
self.on_delete(self.item)
|
||||||
|
|
||||||
class GalleryWindow(QDialog):
|
class GalleryWindow(QDialog):
|
||||||
|
BATCH_SIZE = 16
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("f0ckm Upload Gallery")
|
self.setWindowTitle("f0ckm Upload Gallery")
|
||||||
self.resize(760, 560)
|
self.resize(780, 580)
|
||||||
|
self.filtered_history = []
|
||||||
|
self.loaded_count = 0
|
||||||
|
self.cards = []
|
||||||
|
self.current_cols = 0
|
||||||
|
self.is_loading_batch = False
|
||||||
|
|
||||||
app_icon = get_app_icon()
|
app_icon = get_app_icon()
|
||||||
if not app_icon.isNull():
|
if not app_icon.isNull():
|
||||||
self.setWindowIcon(app_icon)
|
self.setWindowIcon(app_icon)
|
||||||
@@ -1086,9 +1205,17 @@ class GalleryWindow(QDialog):
|
|||||||
|
|
||||||
header_layout.addStretch()
|
header_layout.addStretch()
|
||||||
|
|
||||||
|
# Instance Filter Dropdown
|
||||||
|
self.cb_instance = QComboBox()
|
||||||
|
self.cb_instance.setToolTip("Filter uploads by target instance")
|
||||||
|
self.cb_instance.setStyleSheet("padding: 3px 6px;")
|
||||||
|
self.cb_instance.currentIndexChanged.connect(self.load_items)
|
||||||
|
header_layout.addWidget(self.cb_instance)
|
||||||
|
|
||||||
|
# Search bar
|
||||||
self.txt_search = QLineEdit()
|
self.txt_search = QLineEdit()
|
||||||
self.txt_search.setPlaceholderText("Search uploads...")
|
self.txt_search.setPlaceholderText("Search uploads...")
|
||||||
self.txt_search.setFixedWidth(200)
|
self.txt_search.setFixedWidth(180)
|
||||||
self.txt_search.textChanged.connect(self.load_items)
|
self.txt_search.textChanged.connect(self.load_items)
|
||||||
header_layout.addWidget(self.txt_search)
|
header_layout.addWidget(self.txt_search)
|
||||||
|
|
||||||
@@ -1114,9 +1241,29 @@ class GalleryWindow(QDialog):
|
|||||||
self.scroll_area.setWidget(self.scroll_content)
|
self.scroll_area.setWidget(self.scroll_content)
|
||||||
main_layout.addWidget(self.scroll_area)
|
main_layout.addWidget(self.scroll_area)
|
||||||
|
|
||||||
self.current_cols = 0
|
# Connect scroll listener for lazy loading next batch
|
||||||
|
self.scroll_area.verticalScrollBar().valueChanged.connect(self.on_scroll)
|
||||||
|
|
||||||
|
self.populate_instance_filter()
|
||||||
self.load_items()
|
self.load_items()
|
||||||
|
|
||||||
|
def populate_instance_filter(self):
|
||||||
|
prev_signal_state = self.cb_instance.blockSignals(True)
|
||||||
|
self.cb_instance.clear()
|
||||||
|
self.cb_instance.addItem("All Instances")
|
||||||
|
|
||||||
|
config = get_env_config()
|
||||||
|
configured = [inst.get("name") for inst in config.get("instances", []) if inst.get("name")]
|
||||||
|
|
||||||
|
history = load_history()
|
||||||
|
from_history = [h.get("instance_name") for h in history if h.get("instance_name")]
|
||||||
|
|
||||||
|
all_names = sorted(list(set([n for n in (configured + from_history) if n])))
|
||||||
|
for name in all_names:
|
||||||
|
self.cb_instance.addItem(name)
|
||||||
|
|
||||||
|
self.cb_instance.blockSignals(prev_signal_state)
|
||||||
|
|
||||||
def get_column_count(self):
|
def get_column_count(self):
|
||||||
w = self.scroll_area.viewport().width()
|
w = self.scroll_area.viewport().width()
|
||||||
if w <= 0:
|
if w <= 0:
|
||||||
@@ -1128,6 +1275,11 @@ class GalleryWindow(QDialog):
|
|||||||
return cols
|
return cols
|
||||||
|
|
||||||
def load_items(self):
|
def load_items(self):
|
||||||
|
# Clear existing cards
|
||||||
|
for card in self.cards:
|
||||||
|
card.deleteLater()
|
||||||
|
self.cards.clear()
|
||||||
|
|
||||||
while self.grid_layout.count():
|
while self.grid_layout.count():
|
||||||
child = self.grid_layout.takeAt(0)
|
child = self.grid_layout.takeAt(0)
|
||||||
if child.widget():
|
if child.widget():
|
||||||
@@ -1138,9 +1290,25 @@ class GalleryWindow(QDialog):
|
|||||||
self.grid_layout.setColumnStretch(c, 0)
|
self.grid_layout.setColumnStretch(c, 0)
|
||||||
|
|
||||||
history = load_history()
|
history = load_history()
|
||||||
|
|
||||||
|
# Filter by selected Instance
|
||||||
|
selected_instance = self.cb_instance.currentText()
|
||||||
|
if selected_instance and selected_instance != "All Instances":
|
||||||
|
history = [
|
||||||
|
h for h in history
|
||||||
|
if h.get("instance_name", "Default Instance") == selected_instance or selected_instance.lower() in h.get("url", "").lower()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filter by Search Query
|
||||||
query = self.txt_search.text().strip().lower()
|
query = self.txt_search.text().strip().lower()
|
||||||
if query:
|
if query:
|
||||||
history = [h for h in history if query in h.get("filename", "").lower() or query in h.get("url", "").lower()]
|
history = [
|
||||||
|
h for h in history
|
||||||
|
if query in h.get("filename", "").lower() or query in h.get("url", "").lower() or query in h.get("instance_name", "").lower()
|
||||||
|
]
|
||||||
|
|
||||||
|
self.filtered_history = history
|
||||||
|
self.loaded_count = 0
|
||||||
|
|
||||||
if not history:
|
if not history:
|
||||||
lbl_empty = QLabel("No uploads found.")
|
lbl_empty = QLabel("No uploads found.")
|
||||||
@@ -1155,8 +1323,57 @@ class GalleryWindow(QDialog):
|
|||||||
for c in range(cols):
|
for c in range(cols):
|
||||||
self.grid_layout.setColumnStretch(c, 1)
|
self.grid_layout.setColumnStretch(c, 1)
|
||||||
|
|
||||||
for idx, item in enumerate(history):
|
self.load_next_batch()
|
||||||
card = GalleryCard(item, on_delete=self.delete_item)
|
|
||||||
|
def load_next_batch(self):
|
||||||
|
if self.is_loading_batch or self.loaded_count >= len(self.filtered_history):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_loading_batch = True
|
||||||
|
try:
|
||||||
|
cols = self.get_column_count()
|
||||||
|
self.current_cols = cols
|
||||||
|
|
||||||
|
start_idx = self.loaded_count
|
||||||
|
end_idx = min(len(self.filtered_history), start_idx + self.BATCH_SIZE)
|
||||||
|
|
||||||
|
for idx in range(start_idx, end_idx):
|
||||||
|
item = self.filtered_history[idx]
|
||||||
|
card = GalleryCard(item, on_delete=self.delete_item)
|
||||||
|
self.cards.append(card)
|
||||||
|
row = idx // cols
|
||||||
|
col = idx % cols
|
||||||
|
self.grid_layout.addWidget(card, row, col)
|
||||||
|
|
||||||
|
self.loaded_count = end_idx
|
||||||
|
finally:
|
||||||
|
self.is_loading_batch = False
|
||||||
|
|
||||||
|
QTimer.singleShot(50, self.check_fill_viewport)
|
||||||
|
|
||||||
|
def check_fill_viewport(self):
|
||||||
|
vbar = self.scroll_area.verticalScrollBar()
|
||||||
|
if not vbar.isVisible() and self.loaded_count < len(self.filtered_history):
|
||||||
|
self.load_next_batch()
|
||||||
|
|
||||||
|
def on_scroll(self, value):
|
||||||
|
vbar = self.scroll_area.verticalScrollBar()
|
||||||
|
if value >= vbar.maximum() - 150:
|
||||||
|
self.load_next_batch()
|
||||||
|
|
||||||
|
def relayout_cards(self):
|
||||||
|
cols = self.get_column_count()
|
||||||
|
self.current_cols = cols
|
||||||
|
|
||||||
|
for c in range(20):
|
||||||
|
self.grid_layout.setColumnStretch(c, 1 if c < cols else 0)
|
||||||
|
|
||||||
|
# Remove cards from grid without destroying them
|
||||||
|
for card in self.cards:
|
||||||
|
self.grid_layout.removeWidget(card)
|
||||||
|
|
||||||
|
# Re-add cards at new grid positions
|
||||||
|
for idx, card in enumerate(self.cards):
|
||||||
row = idx // cols
|
row = idx // cols
|
||||||
col = idx % cols
|
col = idx % cols
|
||||||
self.grid_layout.addWidget(card, row, col)
|
self.grid_layout.addWidget(card, row, col)
|
||||||
@@ -1164,18 +1381,25 @@ class GalleryWindow(QDialog):
|
|||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
cols = self.get_column_count()
|
cols = self.get_column_count()
|
||||||
if cols != getattr(self, "current_cols", 0):
|
if cols != getattr(self, "current_cols", 0) and self.cards:
|
||||||
self.load_items()
|
self.relayout_cards()
|
||||||
|
|
||||||
def delete_item(self, item):
|
def delete_item(self, item):
|
||||||
|
cache_key = item.get("url") or item.get("file_path") or item.get("thumbnail_path")
|
||||||
|
if cache_key and cache_key in THUMBNAIL_CACHE:
|
||||||
|
del THUMBNAIL_CACHE[cache_key]
|
||||||
|
|
||||||
history = load_history()
|
history = load_history()
|
||||||
history = [h for h in history if h.get("url") != item.get("url")]
|
history = [h for h in history if h.get("url") != item.get("url")]
|
||||||
save_history(history)
|
save_history(history)
|
||||||
|
self.populate_instance_filter()
|
||||||
self.load_items()
|
self.load_items()
|
||||||
|
|
||||||
def on_clear_history(self):
|
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:
|
if QMessageBox.question(self, "Clear History", "Are you sure you want to clear your upload history?", QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
|
||||||
|
THUMBNAIL_CACHE.clear()
|
||||||
save_history([])
|
save_history([])
|
||||||
|
self.populate_instance_filter()
|
||||||
self.load_items()
|
self.load_items()
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|||||||
Reference in New Issue
Block a user