Ezmary commited on
Commit
b372360
·
verified ·
1 Parent(s): 03c6e78

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +169 -55
templates/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI Photoshop Pro</title>
7
  <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
  <style>
9
  :root {
@@ -254,6 +254,99 @@
254
  const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); const previewImage = document.getElementById('preview-image-main'); const removeFileBtn = document.getElementById('remove-file-btn-main'); const promptInput = document.getElementById('prompt-input'); const submitBtn = document.getElementById('submit-btn'); const resultContainer = document.getElementById('result-container'); const resultGrid = document.getElementById('result-grid'); const errorMessage = document.getElementById('error-message'); const lightbox = document.getElementById('lightbox'); const lightboxImg = document.getElementById('lightbox-img'); const lightboxClose = document.getElementById('lightbox-close'); const lightboxDownload = document.getElementById('lightbox-download'); const lightboxNext = document.getElementById('lightbox-next'); const historyGrid = document.getElementById('history-grid'); const clearHistoryBtn = document.getElementById('clear-history-btn'); const confirmationModal = document.getElementById('confirmation-modal'); const modalMessageText = document.getElementById('modal-message-text'); const modalConfirmBtn = document.getElementById('modal-confirm-btn'); const modalCancelBtn = document.getElementById('modal-cancel-btn');
255
  let uploadedFile = null; let currentLightboxUrl = null; let currentLightboxGroup = []; let currentLightboxIndex = 0;
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  const convertToPNG = (file) => new Promise((resolve, reject) => { const image = new Image(); const reader = new FileReader(); image.onload = () => { const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; const ctx = canvas.getContext('2d'); ctx.drawImage(image, 0, 0); canvas.toBlob((blob) => { if (!blob) { reject(new Error('تبدیل به PNG ناموفق بود.')); return; } const originalName = file.name.substring(0, file.name.lastIndexOf('.')) || file.name; const pngFile = new File([blob], `${originalName}.png`, { type: 'image/png' }); resolve(pngFile); }, 'image/png', 1.0); }; image.onerror = () => reject(new Error('فایل تصویر قابل خواندن نیست.')); reader.onload = e => { image.src = e.target.result; }; reader.onerror = () => reject(new Error('خطا در خواندن فایل.')); reader.readAsDataURL(file); });
258
  const handleFile = async (file) => { if (!file || !file.type.startsWith('image/')) { displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.'); return; } let fileToProcess = file; if (file.type !== 'image/png' && file.type !== 'image/jpeg') { try { fileToProcess = await convertToPNG(file); } catch (error) { console.error('Image conversion failed:', error); displayError('خطا در تبدیل فرمت تصویر.'); resetUploader(); return; } } uploadedFile = fileToProcess; const reader = new FileReader(); reader.onload = (e) => { previewImage.src = e.target.result; }; reader.readAsDataURL(file); uploadArea.classList.add('has-file'); checkFormState(); clearResult(); };
259
  const resetUploader = () => { uploadedFile = null; fileInput.value = ''; previewImage.src = ''; uploadArea.classList.remove('has-file'); checkFormState(); };
@@ -264,10 +357,8 @@
264
  resultGrid.innerHTML = '';
265
  imageUrls.forEach((url) => {
266
  const img = document.createElement('img');
267
- // *** CHANGE: Use proxy for display ***
268
  img.src = `/api/proxy?url=${encodeURIComponent(url)}`;
269
  img.alt = 'تصویر ویرایش شده';
270
- // Pass the ORIGINAL url to the lightbox
271
  img.addEventListener('click', () => openLightbox(url, imageUrls));
272
  resultGrid.appendChild(img);
273
  });
@@ -281,67 +372,79 @@
281
 
282
  const updateLightboxImage = () => {
283
  const newUrl = currentLightboxGroup[currentLightboxIndex];
284
- // *** CHANGE: Use proxy for lightbox display ***
285
  lightboxImg.src = `/api/proxy?url=${encodeURIComponent(newUrl)}`;
286
- currentLightboxUrl = newUrl; // Store the ORIGINAL url for download
287
  };
288
 
289
  const handleDownload = () => { if (!currentLightboxUrl) return; parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: currentLightboxUrl }, '*'); };
290
- const getHistory = () => JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
291
- const saveHistory = (history) => { localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history)); };
292
-
293
- // *** CHANGE: Add timestamp on save ***
294
- const addToHistory = (item) => {
295
- let history = getHistory();
296
- const newItem = {
297
- ...item,
298
- timestamp: Date.now() // Add current time in milliseconds
299
- };
300
- history.unshift(newItem);
301
- if (history.length > 15) history.pop();
302
- saveHistory(history);
303
- renderHistory(); // Re-render after saving
304
  };
305
 
306
  const showConfirmationModal = (message, onConfirm) => { modalMessageText.textContent = message; confirmationModal.classList.add('visible'); modalConfirmBtn.onclick = () => { onConfirm(); hideConfirmationModal(); }; modalCancelBtn.onclick = () => { hideConfirmationModal(); }; };
307
  const hideConfirmationModal = () => { confirmationModal.classList.remove('visible'); };
308
- const handleClearHistory = () => { showConfirmationModal('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟', () => { saveHistory([]); renderHistory(); }); };
309
- const handleDeleteItem = (index) => { showConfirmationModal('آیا از حذف این مورد مطمئن هستید؟', () => { let history = getHistory(); history.splice(index, 1); saveHistory(history); renderHistory(); }); };
310
-
311
- // *** CHANGE: Filter expired items on render ***
312
- const renderHistory = () => {
313
- const now = Date.now();
314
- const expirationTime = 23 * 60 * 60 * 1000; // 23 hours in milliseconds
315
- let history = getHistory();
316
-
317
- // Filter out items older than 23 hours
318
- const validHistory = history.filter(item => {
319
- // Keep items that have a timestamp and are not expired
320
- return item.timestamp && (now - item.timestamp) < expirationTime;
321
  });
 
 
 
 
 
 
 
 
322
 
323
- // If any items were removed, update localStorage
324
- if (validHistory.length < history.length) {
325
- saveHistory(validHistory);
326
- }
327
-
328
- historyGrid.innerHTML = '';
329
- clearHistoryBtn.style.display = validHistory.length > 0 ? 'flex' : 'none';
330
- validHistory.forEach((item, index) => {
331
- const card = document.createElement('div'); card.className = 'history-item';
332
- const deleteBtn = document.createElement('button'); deleteBtn.className = 'history-delete-btn'; deleteBtn.title = 'حذف'; deleteBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
333
- deleteBtn.onclick = () => handleDeleteItem(index); card.appendChild(deleteBtn);
334
- const imageGrid = document.createElement('div'); imageGrid.className = 'history-item-grid';
335
- item.urls.forEach(url => {
336
- const img = document.createElement('img');
337
- // *** CHANGE: Use proxy for history display ***
338
- img.src = `/api/proxy?url=${encodeURIComponent(url)}`;
339
- img.alt = 'تصویر تاریخچه';
340
- img.addEventListener('click', () => openLightbox(url, item.urls));
341
- imageGrid.appendChild(img);
 
 
 
 
 
 
 
 
 
 
 
342
  });
343
- card.appendChild(imageGrid); historyGrid.appendChild(card);
344
- });
 
 
345
  };
346
 
347
  const proceedWithImageGeneration = async () => {
@@ -357,7 +460,7 @@
357
  checkFreeUserCredit();
358
  }
359
  displayResult(result.image_urls);
360
- addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls });
361
  } else { throw new Error('پاسخ معتبری از سرور دریافت نشد.'); }
362
  } catch (error) { displayError(error.message); } finally { setLoading(false); }
363
  };
@@ -376,7 +479,18 @@
376
  });
377
 
378
  // Event Listeners
379
- document.addEventListener('DOMContentLoaded', async () => { renderHistory(); userFingerprint = await getBrowserFingerprint(); parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*'); checkFormState(); });
 
 
 
 
 
 
 
 
 
 
 
380
  removeFileBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetUploader(); });
381
  fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
382
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }); });
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Photoshop Pro (IndexedDB Version)</title>
7
  <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
  <style>
9
  :root {
 
254
  const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); const previewImage = document.getElementById('preview-image-main'); const removeFileBtn = document.getElementById('remove-file-btn-main'); const promptInput = document.getElementById('prompt-input'); const submitBtn = document.getElementById('submit-btn'); const resultContainer = document.getElementById('result-container'); const resultGrid = document.getElementById('result-grid'); const errorMessage = document.getElementById('error-message'); const lightbox = document.getElementById('lightbox'); const lightboxImg = document.getElementById('lightbox-img'); const lightboxClose = document.getElementById('lightbox-close'); const lightboxDownload = document.getElementById('lightbox-download'); const lightboxNext = document.getElementById('lightbox-next'); const historyGrid = document.getElementById('history-grid'); const clearHistoryBtn = document.getElementById('clear-history-btn'); const confirmationModal = document.getElementById('confirmation-modal'); const modalMessageText = document.getElementById('modal-message-text'); const modalConfirmBtn = document.getElementById('modal-confirm-btn'); const modalCancelBtn = document.getElementById('modal-cancel-btn');
255
  let uploadedFile = null; let currentLightboxUrl = null; let currentLightboxGroup = []; let currentLightboxIndex = 0;
256
 
257
+ // --- START: IndexedDB Handler ---
258
+ const dbHandler = {
259
+ db: null,
260
+ dbName: 'aiPhotoshopDB',
261
+ storeName: 'history',
262
+
263
+ initDB() {
264
+ return new Promise((resolve, reject) => {
265
+ const request = indexedDB.open(this.dbName, 1);
266
+ request.onerror = (event) => {
267
+ console.error("IndexedDB error:", event.target.error);
268
+ reject("Error opening DB");
269
+ };
270
+ request.onsuccess = (event) => {
271
+ this.db = event.target.result;
272
+ resolve();
273
+ };
274
+ request.onupgradeneeded = (event) => {
275
+ const db = event.target.result;
276
+ const store = db.createObjectStore(this.storeName, { keyPath: 'id', autoIncrement: true });
277
+ store.createIndex('timestamp', 'timestamp', { unique: false });
278
+ };
279
+ });
280
+ },
281
+
282
+ add(item) {
283
+ return new Promise((resolve, reject) => {
284
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
285
+ const store = transaction.objectStore(this.storeName);
286
+ const request = store.add(item);
287
+ request.onsuccess = () => resolve();
288
+ request.onerror = (event) => reject(event.target.error);
289
+ });
290
+ },
291
+
292
+ getAll() {
293
+ return new Promise((resolve, reject) => {
294
+ const transaction = this.db.transaction([this.storeName], 'readonly');
295
+ const store = transaction.objectStore(this.storeName);
296
+ const request = store.getAll();
297
+ request.onsuccess = () => {
298
+ // Sort by timestamp descending (newest first)
299
+ resolve(request.result.sort((a, b) => b.timestamp - a.timestamp));
300
+ };
301
+ request.onerror = (event) => reject(event.target.error);
302
+ });
303
+ },
304
+
305
+ delete(id) {
306
+ return new Promise((resolve, reject) => {
307
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
308
+ const store = transaction.objectStore(this.storeName);
309
+ const request = store.delete(id);
310
+ request.onsuccess = () => resolve();
311
+ request.onerror = (event) => reject(event.target.error);
312
+ });
313
+ },
314
+
315
+ clear() {
316
+ return new Promise((resolve, reject) => {
317
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
318
+ const store = transaction.objectStore(this.storeName);
319
+ const request = store.clear();
320
+ request.onsuccess = () => resolve();
321
+ request.onerror = (event) => reject(event.target.error);
322
+ });
323
+ },
324
+
325
+ deleteOlderThan(timestamp) {
326
+ return new Promise((resolve, reject) => {
327
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
328
+ const store = transaction.objectStore(this.storeName);
329
+ const index = store.index('timestamp');
330
+ // Create a key range for all items with a timestamp less than the provided one
331
+ const keyRange = IDBKeyRange.upperBound(timestamp);
332
+ const request = index.openCursor(keyRange);
333
+
334
+ request.onsuccess = (event) => {
335
+ const cursor = event.target.result;
336
+ if (cursor) {
337
+ store.delete(cursor.primaryKey); // Delete the item
338
+ cursor.continue(); // Move to the next item
339
+ } else {
340
+ resolve(); // No more items to delete
341
+ }
342
+ };
343
+ request.onerror = (event) => reject(event.target.error);
344
+ });
345
+ }
346
+ };
347
+ // --- END: IndexedDB Handler ---
348
+
349
+
350
  const convertToPNG = (file) => new Promise((resolve, reject) => { const image = new Image(); const reader = new FileReader(); image.onload = () => { const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; const ctx = canvas.getContext('2d'); ctx.drawImage(image, 0, 0); canvas.toBlob((blob) => { if (!blob) { reject(new Error('تبدیل به PNG ناموفق بود.')); return; } const originalName = file.name.substring(0, file.name.lastIndexOf('.')) || file.name; const pngFile = new File([blob], `${originalName}.png`, { type: 'image/png' }); resolve(pngFile); }, 'image/png', 1.0); }; image.onerror = () => reject(new Error('فایل تصویر قابل خواندن نیست.')); reader.onload = e => { image.src = e.target.result; }; reader.onerror = () => reject(new Error('خطا در خواندن فایل.')); reader.readAsDataURL(file); });
351
  const handleFile = async (file) => { if (!file || !file.type.startsWith('image/')) { displayError('لطفا یک فایل تصویری معتبر انتخاب کنید.'); return; } let fileToProcess = file; if (file.type !== 'image/png' && file.type !== 'image/jpeg') { try { fileToProcess = await convertToPNG(file); } catch (error) { console.error('Image conversion failed:', error); displayError('خطا در تبدیل فرمت تصویر.'); resetUploader(); return; } } uploadedFile = fileToProcess; const reader = new FileReader(); reader.onload = (e) => { previewImage.src = e.target.result; }; reader.readAsDataURL(file); uploadArea.classList.add('has-file'); checkFormState(); clearResult(); };
352
  const resetUploader = () => { uploadedFile = null; fileInput.value = ''; previewImage.src = ''; uploadArea.classList.remove('has-file'); checkFormState(); };
 
357
  resultGrid.innerHTML = '';
358
  imageUrls.forEach((url) => {
359
  const img = document.createElement('img');
 
360
  img.src = `/api/proxy?url=${encodeURIComponent(url)}`;
361
  img.alt = 'تصویر ویرایش شده';
 
362
  img.addEventListener('click', () => openLightbox(url, imageUrls));
363
  resultGrid.appendChild(img);
364
  });
 
372
 
373
  const updateLightboxImage = () => {
374
  const newUrl = currentLightboxGroup[currentLightboxIndex];
 
375
  lightboxImg.src = `/api/proxy?url=${encodeURIComponent(newUrl)}`;
376
+ currentLightboxUrl = newUrl;
377
  };
378
 
379
  const handleDownload = () => { if (!currentLightboxUrl) return; parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: currentLightboxUrl }, '*'); };
380
+
381
+ const addToHistory = async (item) => {
382
+ const newItem = { ...item, timestamp: Date.now() };
383
+ try {
384
+ await dbHandler.add(newItem);
385
+ // Limit history to 15 items by deleting the oldest if count exceeds 15
386
+ const history = await dbHandler.getAll();
387
+ if (history.length > 15) {
388
+ await dbHandler.delete(history[history.length - 1].id); // Oldest is last due to sorting
389
+ }
390
+ } catch (error) {
391
+ console.error("Failed to add to history:", error);
392
+ }
393
+ await renderHistory();
394
  };
395
 
396
  const showConfirmationModal = (message, onConfirm) => { modalMessageText.textContent = message; confirmationModal.classList.add('visible'); modalConfirmBtn.onclick = () => { onConfirm(); hideConfirmationModal(); }; modalCancelBtn.onclick = () => { hideConfirmationModal(); }; };
397
  const hideConfirmationModal = () => { confirmationModal.classList.remove('visible'); };
398
+
399
+ const handleClearHistory = () => {
400
+ showConfirmationModal('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟', async () => {
401
+ await dbHandler.clear();
402
+ await renderHistory();
 
 
 
 
 
 
 
 
403
  });
404
+ };
405
+
406
+ const handleDeleteItem = (id) => {
407
+ showConfirmationModal('آیا از حذف این مورد مطمئن هستید؟', async () => {
408
+ await dbHandler.delete(id);
409
+ await renderHistory();
410
+ });
411
+ };
412
 
413
+ const renderHistory = async () => {
414
+ try {
415
+ const expirationTime = 23 * 60 * 60 * 1000; // 23 hours in milliseconds
416
+ const expirationTimestamp = Date.now() - expirationTime;
417
+
418
+ // Clean up old entries first
419
+ await dbHandler.deleteOlderThan(expirationTimestamp);
420
+
421
+ const history = await dbHandler.getAll();
422
+
423
+ historyGrid.innerHTML = '';
424
+ clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none';
425
+
426
+ history.forEach((item) => {
427
+ const card = document.createElement('div'); card.className = 'history-item';
428
+ const deleteBtn = document.createElement('button'); deleteBtn.className = 'history-delete-btn'; deleteBtn.title = 'حذف'; deleteBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
429
+ // Pass the item's unique ID from the database
430
+ deleteBtn.onclick = () => handleDeleteItem(item.id);
431
+ card.appendChild(deleteBtn);
432
+
433
+ const imageGrid = document.createElement('div'); imageGrid.className = 'history-item-grid';
434
+ item.urls.forEach(url => {
435
+ const img = document.createElement('img');
436
+ img.src = `/api/proxy?url=${encodeURIComponent(url)}`;
437
+ img.alt = 'تصویر تاریخچه';
438
+ img.addEventListener('click', () => openLightbox(url, item.urls));
439
+ imageGrid.appendChild(img);
440
+ });
441
+ card.appendChild(imageGrid);
442
+ historyGrid.appendChild(card);
443
  });
444
+ } catch (error) {
445
+ console.error("Could not render history:", error);
446
+ historyGrid.innerHTML = '<p style="color: var(--danger-color); grid-column: 1 / -1; text-align: center;">خطا در بارگذاری تاریخچه.</p>';
447
+ }
448
  };
449
 
450
  const proceedWithImageGeneration = async () => {
 
460
  checkFreeUserCredit();
461
  }
462
  displayResult(result.image_urls);
463
+ await addToHistory({ prompt: promptInput.value.trim(), urls: result.image_urls });
464
  } else { throw new Error('پاسخ معتبری از سرور دریافت نشد.'); }
465
  } catch (error) { displayError(error.message); } finally { setLoading(false); }
466
  };
 
479
  });
480
 
481
  // Event Listeners
482
+ document.addEventListener('DOMContentLoaded', async () => {
483
+ try {
484
+ await dbHandler.initDB();
485
+ await renderHistory();
486
+ } catch (error) {
487
+ console.error("Failed to initialize the application:", error);
488
+ displayError("خطا در راه اندازی اولیه برنامه. لطفا صفحه را رفرش کنید.");
489
+ }
490
+ userFingerprint = await getBrowserFingerprint();
491
+ parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
492
+ checkFormState();
493
+ });
494
  removeFileBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetUploader(); });
495
  fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
496
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }); });