diff --git a/app/static/public/js/imagine.js b/app/static/public/js/imagine.js
index 9ef800e..63c49b7 100644
--- a/app/static/public/js/imagine.js
+++ b/app/static/public/js/imagine.js
@@ -128,7 +128,7 @@
updateModeValue();
}
- function updateModeValue() {}
+ function updateModeValue() { }
async function loadFilterDefaults() {
try {
@@ -163,7 +163,7 @@
}
}
- function updateError(value) {}
+ function updateError(value) { }
function setLightboxImageFullscreen(enabled) {
if (!lightbox) return;
@@ -182,7 +182,7 @@
setLightboxKeyboardShift(0);
return;
}
- const isMobile = window.matchMedia('(max-width: 768px)').matches;
+ const isMobile = window.matchMedia('(max-width: 1024px)').matches || window.matchMedia('(pointer: coarse)').matches;
if (!isMobile || document.activeElement !== lightboxEditInput) {
setLightboxKeyboardShift(0);
return;
@@ -634,14 +634,14 @@
if (!directoryHandle) {
return false;
}
-
+
const mime = inferMime(base64);
const ext = mime === 'image/png' ? 'png' : 'jpg';
const finalFilename = filename.endsWith(`.${ext}`) ? filename : `${filename}.${ext}`;
-
+
const fileHandle = await directoryHandle.getFileHandle(finalFilename, { create: true });
const writable = await fileHandle.createWritable();
-
+
// Convert base64 to blob
const byteString = atob(base64);
const ab = new ArrayBuffer(byteString.length);
@@ -650,7 +650,7 @@
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ab], { type: mime });
-
+
await writable.write(blob);
await writable.close();
return true;
@@ -980,7 +980,7 @@
if (isSelectionMode) {
item.classList.add('selection-mode');
}
-
+
if (reverseInsertToggle && reverseInsertToggle.checked) {
waterfall.prepend(item);
} else {
@@ -1000,7 +1000,7 @@
const seq = meta && meta.sequence ? meta.sequence : 'unknown';
const ext = mime === 'image/png' ? 'png' : 'jpg';
const filename = `imagine_${timestamp}_${seq}.${ext}`;
-
+
if (useFileSystemAPI && directoryHandle) {
saveToFileSystem(base64, filename).catch(() => {
downloadImage(base64, filename);
@@ -1339,7 +1339,7 @@
const concurrent = concurrentSelect ? parseInt(concurrentSelect.value, 10) : 1;
const ratio = ratioSelect ? ratioSelect.value : '2:3';
const nsfwEnabled = nsfwSelect ? nsfwSelect.value === 'true' : true;
-
+
if (isRunning) {
toast('已在运行中', 'warning');
return;
@@ -1617,7 +1617,7 @@
e.stopPropagation();
const cards = document.querySelectorAll('.imagine-card-collapsible');
const allCollapsed = Array.from(cards).every(card => card.classList.contains('collapsed'));
-
+
cards.forEach(card => {
if (allCollapsed) {
card.classList.remove('collapsed');
@@ -1633,31 +1633,31 @@
const selectionToolbar = document.getElementById('selectionToolbar');
const toggleSelectAllBtn = document.getElementById('toggleSelectAllBtn');
const downloadSelectedBtn = document.getElementById('downloadSelectedBtn');
-
+
function enterSelectionMode() {
isSelectionMode = true;
selectedImages.clear();
selectionToolbar.classList.remove('hidden');
-
+
const items = document.querySelectorAll('.waterfall-item');
items.forEach(item => {
item.classList.add('selection-mode');
});
-
+
updateSelectedCount();
}
-
+
function exitSelectionMode() {
isSelectionMode = false;
selectedImages.clear();
selectionToolbar.classList.add('hidden');
-
+
const items = document.querySelectorAll('.waterfall-item');
items.forEach(item => {
item.classList.remove('selection-mode', 'selected');
});
}
-
+
function toggleSelectionMode() {
if (isSelectionMode) {
exitSelectionMode();
@@ -1665,10 +1665,10 @@
enterSelectionMode();
}
}
-
+
function toggleImageSelection(item) {
if (!isSelectionMode) return;
-
+
if (item.classList.contains('selected')) {
item.classList.remove('selected');
selectedImages.delete(item);
@@ -1676,10 +1676,10 @@
item.classList.add('selected');
selectedImages.add(item);
}
-
+
updateSelectedCount();
}
-
+
function updateSelectedCount() {
const countSpan = document.getElementById('selectedCount');
if (countSpan) {
@@ -1688,7 +1688,7 @@
if (downloadSelectedBtn) {
downloadSelectedBtn.disabled = selectedImages.size === 0;
}
-
+
// Update toggle select all button text
if (toggleSelectAllBtn) {
const items = document.querySelectorAll('.waterfall-item');
@@ -1696,11 +1696,11 @@
toggleSelectAllBtn.textContent = allSelected ? '取消全选' : '全选';
}
}
-
+
function toggleSelectAll() {
const items = document.querySelectorAll('.waterfall-item');
const allSelected = items.length > 0 && selectedImages.size === items.length;
-
+
if (allSelected) {
// Deselect all
items.forEach(item => {
@@ -1714,34 +1714,34 @@
selectedImages.add(item);
});
}
-
+
updateSelectedCount();
}
-
+
async function downloadSelectedImages() {
if (selectedImages.size === 0) {
toast('请先选择要下载的图片', 'warning');
return;
}
-
+
if (typeof JSZip === 'undefined') {
toast('JSZip 库加载失败,请刷新页面重试', 'error');
return;
}
-
+
toast(`正在打包 ${selectedImages.size} 张图片...`, 'info');
downloadSelectedBtn.disabled = true;
downloadSelectedBtn.textContent = '打包中...';
-
+
const zip = new JSZip();
const imgFolder = zip.folder('images');
let processed = 0;
-
+
try {
for (const item of selectedImages) {
const url = item.dataset.imageUrl;
const prompt = item.dataset.prompt || 'image';
-
+
try {
let blob = null;
if (url && url.startsWith('data:')) {
@@ -1756,54 +1756,54 @@
const filename = `${prompt.substring(0, 30).replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_')}_${processed + 1}.png`;
imgFolder.file(filename, blob);
processed++;
-
+
// Update progress
downloadSelectedBtn.innerHTML = `打包中... (${processed}/${selectedImages.size})`;
} catch (error) {
console.error('Failed to fetch image:', error);
}
}
-
+
if (processed === 0) {
toast('没有成功获取任何图片', 'error');
return;
}
-
+
// Generate zip file
downloadSelectedBtn.textContent = '生成压缩包...';
const content = await zip.generateAsync({ type: 'blob' });
-
+
// Download zip
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
link.download = `imagine_${new Date().toISOString().slice(0, 10)}_${Date.now()}.zip`;
link.click();
URL.revokeObjectURL(link.href);
-
+
toast(`成功打包 ${processed} 张图片`, 'success');
exitSelectionMode();
} catch (error) {
console.error('Download failed:', error);
toast('打包失败,请重试', 'error');
} finally {
- downloadSelectedBtn.disabled = false;
- downloadSelectedBtn.innerHTML = `下载 ${selectedImages.size}`;
+ downloadSelectedBtn.disabled = false;
+ downloadSelectedBtn.innerHTML = `下载 ${selectedImages.size}`;
}
}
-
+
if (batchDownloadBtn) {
batchDownloadBtn.addEventListener('click', toggleSelectionMode);
}
-
+
if (toggleSelectAllBtn) {
toggleSelectAllBtn.addEventListener('click', toggleSelectAll);
}
-
+
if (downloadSelectedBtn) {
downloadSelectedBtn.addEventListener('click', downloadSelectedImages);
}
-
-
+
+
// Handle image/checkbox clicks in waterfall
if (waterfall) {
waterfall.addEventListener('click', (e) => {
@@ -1815,7 +1815,7 @@
copyParentPostIdFromItem(item);
return;
}
-
+
if (isSelectionMode) {
// In selection mode, clicking anywhere on the item toggles selection
toggleImageSelection(item);
@@ -1825,7 +1825,7 @@
const img = e.target.closest('.waterfall-item img');
const images = getAllImages();
const index = images.indexOf(img);
-
+
if (index !== -1) {
updateLightbox(index);
pauseWsForEdit();
@@ -1841,7 +1841,7 @@
const lightboxPrev = document.getElementById('lightboxPrev');
const lightboxNext = document.getElementById('lightboxNext');
let currentImageIndex = -1;
-
+
function getAllImages() {
return Array.from(document.querySelectorAll('.waterfall-item img'));
}
@@ -1851,11 +1851,11 @@
if (index < 0 || index >= images.length) return null;
return images[index].closest('.waterfall-item');
}
-
+
function updateLightbox(index) {
const images = getAllImages();
if (index < 0 || index >= images.length) return;
-
+
currentImageIndex = index;
setLightboxImageFullscreen(false);
lightboxImg.src = images[index].src;
@@ -1875,18 +1875,18 @@
}
}
}
-
+
// Update navigation buttons state
if (lightboxPrev) lightboxPrev.disabled = (index === 0);
if (lightboxNext) lightboxNext.disabled = (index === images.length - 1);
}
-
+
function showPrevImage() {
if (currentImageIndex > 0) {
updateLightbox(currentImageIndex - 1);
}
}
-
+
function showNextImage() {
const images = getAllImages();
if (currentImageIndex < images.length - 1) {
@@ -2047,7 +2047,7 @@
}
}
}
-
+
if (lightbox && closeLightbox) {
closeLightbox.addEventListener('click', (e) => {
e.stopPropagation();
@@ -2107,10 +2107,10 @@
});
}
- if (lightboxEditInput) {
- lightboxEditInput.addEventListener('click', (e) => {
- e.stopPropagation();
- });
+ if (lightboxEditInput) {
+ lightboxEditInput.addEventListener('click', (e) => {
+ e.stopPropagation();
+ });
lightboxEditInput.addEventListener('focus', () => {
setTimeout(updateLightboxKeyboardShift, 80);
});
@@ -2130,7 +2130,7 @@
e.stopPropagation();
});
}
-
+
// Navigation buttons
if (lightboxPrev) {
lightboxPrev.addEventListener('click', (e) => {
@@ -2138,7 +2138,7 @@
showPrevImage();
});
}
-
+
if (lightboxNext) {
lightboxNext.addEventListener('click', (e) => {
e.stopPropagation();
@@ -2149,7 +2149,7 @@
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (!lightbox.classList.contains('active')) return;
-
+
if (e.key === 'Escape') {
if (lightboxImageFullscreen) {
setLightboxImageFullscreen(false);
@@ -2194,43 +2194,43 @@
if (floatingActions) {
let isDragging = false;
let startX, startY, initialLeft, initialTop;
-
+
floatingActions.style.touchAction = 'none';
-
+
floatingActions.addEventListener('pointerdown', (e) => {
if (e.target.tagName.toLowerCase() === 'button' || e.target.closest('button')) return;
-
+
e.preventDefault();
isDragging = true;
floatingActions.setPointerCapture(e.pointerId);
startX = e.clientX;
startY = e.clientY;
-
+
const rect = floatingActions.getBoundingClientRect();
-
+
if (!floatingActions.style.left || floatingActions.style.left === '') {
floatingActions.style.left = rect.left + 'px';
floatingActions.style.top = rect.top + 'px';
floatingActions.style.transform = 'none';
floatingActions.style.bottom = 'auto';
}
-
+
initialLeft = parseFloat(floatingActions.style.left);
initialTop = parseFloat(floatingActions.style.top);
-
+
floatingActions.classList.add('shadow-xl');
});
-
+
document.addEventListener('pointermove', (e) => {
if (!isDragging) return;
-
+
const dx = e.clientX - startX;
const dy = e.clientY - startY;
-
+
floatingActions.style.left = `${initialLeft + dx}px`;
floatingActions.style.top = `${initialTop + dy}px`;
});
-
+
document.addEventListener('pointerup', (e) => {
if (isDragging) {
isDragging = false;