update filter history

This commit is contained in:
phamhungd 2025-11-25 22:23:15 +07:00
parent e47805dd16
commit 8df429677c
4 changed files with 333 additions and 68 deletions

View file

@ -1,7 +1,101 @@
import { withCacheBuster } from './utils.js'; import { withCacheBuster } from './utils.js';
import { extractMetadataFromBlob } from './metadata.js'; import { extractMetadataFromBlob } from './metadata.js';
const FILTER_STORAGE_KEY = 'gemini-app-history-filter';
const SEARCH_STORAGE_KEY = 'gemini-app-history-search';
export function createGallery({ galleryGrid, onSelect }) { export function createGallery({ galleryGrid, onSelect }) {
let currentFilter = 'all';
let searchQuery = '';
let allImages = [];
// Load saved filter and search from localStorage
try {
const savedFilter = localStorage.getItem(FILTER_STORAGE_KEY);
if (savedFilter) currentFilter = savedFilter;
const savedSearch = localStorage.getItem(SEARCH_STORAGE_KEY);
if (savedSearch) searchQuery = savedSearch;
} catch (e) {
console.warn('Failed to load history filter/search', e);
}
// Date comparison utilities
function getFileTimestamp(imageUrl) {
// Extract date from filename format: model_yyyymmdd_id.png
// Example: gemini-3-pro-image-preview_20251125_1.png
const filename = imageUrl.split('/').pop().split('?')[0];
const match = filename.match(/_(\d{8})_/); // Match yyyymmdd between underscores
if (match) {
const dateStr = match[1]; // e.g., "20251125"
const year = parseInt(dateStr.substring(0, 4), 10);
const month = parseInt(dateStr.substring(4, 6), 10) - 1; // Month is 0-indexed
const day = parseInt(dateStr.substring(6, 8), 10);
return new Date(year, month, day).getTime();
}
return null;
}
function isToday(timestamp) {
if (!timestamp) return false;
const date = new Date(timestamp);
const today = new Date();
return date.toDateString() === today.toDateString();
}
function isThisWeek(timestamp) {
if (!timestamp) return false;
const date = new Date(timestamp);
const today = new Date();
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
return date >= weekAgo && date <= today;
}
function isThisMonth(timestamp) {
if (!timestamp) return false;
const date = new Date(timestamp);
const today = new Date();
return date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
function isThisYear(timestamp) {
if (!timestamp) return false;
const date = new Date(timestamp);
const today = new Date();
return date.getFullYear() === today.getFullYear();
}
function matchesSearch(imageUrl) {
if (!searchQuery) return true;
const filename = imageUrl.split('/').pop().split('?')[0];
return filename.toLowerCase().includes(searchQuery.toLowerCase());
}
function shouldShowImage(imageUrl) {
// First check text search
if (!matchesSearch(imageUrl)) return false;
// Then check date filter
if (currentFilter === 'all') return true;
const timestamp = getFileTimestamp(imageUrl);
if (!timestamp) return currentFilter === 'all';
switch (currentFilter) {
case 'today':
return isToday(timestamp);
case 'week':
return isThisWeek(timestamp);
case 'month':
return isThisMonth(timestamp);
case 'year':
return isThisYear(timestamp);
default:
return true;
}
}
async function readMetadataFromImage(imageUrl) { async function readMetadataFromImage(imageUrl) {
try { try {
const response = await fetch(withCacheBuster(imageUrl)); const response = await fetch(withCacheBuster(imageUrl));
@ -14,14 +108,13 @@ export function createGallery({ galleryGrid, onSelect }) {
} }
} }
async function load() { function renderGallery() {
if (!galleryGrid) return; if (!galleryGrid) return;
try {
const response = await fetch(`/gallery?t=${new Date().getTime()}`);
const data = await response.json();
galleryGrid.innerHTML = ''; galleryGrid.innerHTML = '';
data.images.forEach(imageUrl => { const filteredImages = allImages.filter(shouldShowImage);
filteredImages.forEach(imageUrl => {
const div = document.createElement('div'); const div = document.createElement('div');
div.className = 'gallery-item'; div.className = 'gallery-item';
@ -73,6 +166,8 @@ export function createGallery({ galleryGrid, onSelect }) {
if (res.ok) { if (res.ok) {
div.remove(); div.remove();
// Also remove from allImages array
allImages = allImages.filter(url => url !== imageUrl);
} else { } else {
console.error('Failed to delete image'); console.error('Failed to delete image');
} }
@ -85,10 +180,54 @@ export function createGallery({ galleryGrid, onSelect }) {
div.appendChild(deleteBtn); div.appendChild(deleteBtn);
galleryGrid.appendChild(div); galleryGrid.appendChild(div);
}); });
}
async function load() {
if (!galleryGrid) return;
try {
const response = await fetch(`/gallery?t=${new Date().getTime()}`);
const data = await response.json();
allImages = data.images || [];
renderGallery();
} catch (error) { } catch (error) {
console.error('Failed to load gallery:', error); console.error('Failed to load gallery:', error);
} }
} }
return { load }; function setFilter(filterType) {
if (currentFilter === filterType) return;
currentFilter = filterType;
// Save to localStorage
try {
localStorage.setItem(FILTER_STORAGE_KEY, filterType);
} catch (e) {
console.warn('Failed to save history filter', e);
}
renderGallery();
}
function setSearch(query) {
searchQuery = query || '';
// Save to localStorage
try {
localStorage.setItem(SEARCH_STORAGE_KEY, searchQuery);
} catch (e) {
console.warn('Failed to save history search', e);
}
renderGallery();
}
function getCurrentFilter() {
return currentFilter;
}
function getSearchQuery() {
return searchQuery;
}
return { load, setFilter, getCurrentFilter, setSearch, getSearchQuery };
} }

View file

@ -1192,6 +1192,51 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
} }
// Setup history filter buttons
const historyFilterBtns = document.querySelectorAll('.history-filter-btn');
if (historyFilterBtns.length > 0) {
// Set initial active state based on saved filter
const currentFilter = gallery.getCurrentFilter();
historyFilterBtns.forEach(btn => {
if (btn.dataset.filter === currentFilter) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
// Add click event listeners
historyFilterBtns.forEach(btn => {
btn.addEventListener('click', () => {
const filterType = btn.dataset.filter;
// Update active state
historyFilterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Apply filter
gallery.setFilter(filterType);
});
});
}
// Setup history search input
const historySearchInput = document.getElementById('history-search-input');
if (historySearchInput) {
// Set initial value from saved search
historySearchInput.value = gallery.getSearchQuery();
// Search on input with debounce
let searchTimeout;
historySearchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
gallery.setSearch(e.target.value);
}, 300); // 300ms debounce
});
}
function setViewState(state) { function setViewState(state) {
placeholderState.classList.add('hidden'); placeholderState.classList.add('hidden');
loadingState.classList.add('hidden'); loadingState.classList.add('hidden');

View file

@ -878,8 +878,79 @@ button#generate-btn:disabled {
font-weight: 600; font-weight: 600;
letter-spacing: 0.5px; letter-spacing: 0.5px;
text-transform: uppercase; text-transform: uppercase;
margin: 0;
} }
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
gap: 0.75rem;
}
.history-filter-group {
display: flex;
gap: 0.15rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.08);
align-items: center;
}
.history-search-input {
padding: 0.35rem 0.75rem;
min-width: 120px;
background: transparent;
border: none;
border-right: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0;
color: var(--text-primary);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.3px;
transition: background 0.2s;
height:10px;
margin-right:5px;
}
.history-search-input::placeholder {
color: var(--text-secondary);
font-weight: 500;
}
.history-search-input:focus {
outline: none;
background: rgba(255, 255, 255, 0.05);
}
.history-filter-btn {
padding: 0.35rem 0.75rem;
font-size: 0.7rem;
font-weight: 600;
background: transparent;
color: var(--text-secondary);
border: none;
border-radius: 0.35rem;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
letter-spacing: 0.3px;
}
.history-filter-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--text-primary);
}
.history-filter-btn.active {
background: linear-gradient(135deg, var(--accent-color), var(--accent-hover));
color: #111;
box-shadow: 0 2px 8px rgba(251, 191, 36, 0.3);
}
.gallery-grid { .gallery-grid {
display: flex; display: flex;
gap: 0.75rem; gap: 0.75rem;

View file

@ -236,7 +236,17 @@
</div> </div>
</main> </main>
<section class="history-section"> <section class="history-section">
<div class="history-header">
<h3>History</h3> <h3>History</h3>
<div class="history-filter-group">
<input type="text" id="history-search-input" class="history-search-input" placeholder="Day...">
<button type="button" class="history-filter-btn active" data-filter="all">All</button>
<button type="button" class="history-filter-btn" data-filter="today">Today</button>
<button type="button" class="history-filter-btn" data-filter="week">This Week</button>
<button type="button" class="history-filter-btn" data-filter="month">This Month</button>
<button type="button" class="history-filter-btn" data-filter="year">This Year</button>
</div>
</div>
<div id="gallery-grid" class="gallery-grid"> <div id="gallery-grid" class="gallery-grid">
<!-- Gallery items will be injected here --> <!-- Gallery items will be injected here -->
</div> </div>