Skip to content

Commit c645f85

Browse files
committed
Enh(viewer): Allow to filter by date
1 parent bef8404 commit c645f85

3 files changed

Lines changed: 244 additions & 1 deletion

File tree

codeclash/viewer/static/css/picker.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
.filter-controls {
3838
display: flex;
3939
align-items: center;
40+
gap: 0.75rem;
4041
}
4142

4243
#clear-filters {
@@ -190,6 +191,65 @@
190191
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
191192
}
192193

194+
/* Active Filters Badge */
195+
.active-filters-container {
196+
display: flex;
197+
align-items: center;
198+
gap: 0.5rem;
199+
margin-right: 1rem;
200+
flex-wrap: wrap;
201+
}
202+
203+
.filter-label {
204+
font-size: 0.9rem;
205+
color: var(--text-secondary);
206+
font-weight: 500;
207+
}
208+
209+
.filter-badge {
210+
display: inline-flex;
211+
align-items: center;
212+
gap: 0.375rem;
213+
background-color: var(--accent-color);
214+
color: #000000;
215+
padding: 0.375rem 0.625rem;
216+
border-radius: 1rem;
217+
font-size: 0.85rem;
218+
font-weight: 600;
219+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
220+
transition: all 0.2s ease;
221+
}
222+
223+
.filter-badge i {
224+
font-size: 0.9rem;
225+
}
226+
227+
.filter-badge-close {
228+
background: none;
229+
border: none;
230+
color: #000000;
231+
cursor: pointer;
232+
padding: 0;
233+
margin-left: 0.125rem;
234+
display: flex;
235+
align-items: center;
236+
justify-content: center;
237+
width: 16px;
238+
height: 16px;
239+
border-radius: 50%;
240+
transition: all 0.2s ease;
241+
}
242+
243+
.filter-badge-close:hover {
244+
background-color: rgba(0, 0, 0, 0.2);
245+
transform: scale(1.1);
246+
}
247+
248+
.filter-badge-close i {
249+
font-size: 0.75rem;
250+
font-weight: 700;
251+
}
252+
193253
.table-header {
194254
background-color: var(--bg-tertiary);
195255
padding: 1rem;
@@ -565,6 +625,22 @@
565625
background-color: var(--accent-color);
566626
}
567627

628+
.date-text.clickable-filter:hover {
629+
filter: brightness(1.2);
630+
background-color: var(--accent-hover);
631+
color: #000000;
632+
font-weight: 600;
633+
}
634+
635+
.date-text.date-selected {
636+
background-color: var(--accent-color);
637+
color: #000000;
638+
font-weight: 700;
639+
padding: 0.2rem 0.4rem;
640+
border-radius: 0.25rem;
641+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
642+
}
643+
568644
.action-cell {
569645
display: flex;
570646
justify-content: center;

codeclash/viewer/static/js/picker.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,12 @@ document.addEventListener("DOMContentLoaded", function () {
629629
// Initialize filters
630630
initializeFilters();
631631

632+
// Add listener to game filter to save changes
633+
const gameFilter = document.getElementById("game-filter");
634+
if (gameFilter) {
635+
gameFilter.addEventListener("change", saveFilters);
636+
}
637+
632638
// Close model dropdown when clicking outside
633639
document.addEventListener("click", (event) => {
634640
const dropdown = document.getElementById("model-filter-dropdown");
@@ -921,6 +927,7 @@ let allGameRows = [];
921927
let uniqueGames = new Set();
922928
let uniqueModels = new Set();
923929
let selectedModels = new Set();
930+
let selectedDate = null;
924931

925932
function shouldRowBeVisible(row) {
926933
const gameFilter =
@@ -952,6 +959,22 @@ function shouldRowBeVisible(row) {
952959
}
953960
}
954961

962+
// Check date filter - if a date is selected, row must match that date
963+
if (selectedDate) {
964+
const dateElement = row.querySelector(".date-text");
965+
if (dateElement) {
966+
const dateText = dateElement.textContent.trim();
967+
// Extract just the YYYY-MM-DD part
968+
const rowDate = dateText.split(" ")[0];
969+
if (rowDate !== selectedDate) {
970+
return false;
971+
}
972+
} else {
973+
// Row has no date, so it doesn't match
974+
return false;
975+
}
976+
}
977+
955978
return true;
956979
} else {
957980
// For intermediate folders, check if any child game folders should be visible
@@ -988,6 +1011,13 @@ function initializeFilters() {
9881011
// Populate filter dropdowns
9891012
populateGameFilter();
9901013
populateModelFilter();
1014+
1015+
// Load saved filters from localStorage
1016+
const hasFilters = loadFilters();
1017+
if (hasFilters) {
1018+
// Apply the loaded filters
1019+
applyFilters();
1020+
}
9911021
}
9921022

9931023
function populateGameFilter() {
@@ -1070,7 +1100,10 @@ function applyFilters() {
10701100
function clearFilters() {
10711101
document.getElementById("game-filter").value = "";
10721102
selectedModels.clear();
1103+
selectedDate = null;
10731104
updateModelFilterDisplay();
1105+
updateDateFilterDisplay();
1106+
saveFilters();
10741107

10751108
// Reapply the current folder states without any filters
10761109
applyFilters();
@@ -1080,6 +1113,7 @@ function setGameFilter(gameName) {
10801113
const gameFilter = document.getElementById("game-filter");
10811114
if (gameFilter) {
10821115
gameFilter.value = gameName;
1116+
saveFilters();
10831117
applyFilters();
10841118
}
10851119
}
@@ -1094,13 +1128,15 @@ function toggleModelSelection(event, modelName) {
10941128
}
10951129

10961130
updateModelFilterDisplay();
1131+
saveFilters();
10971132
applyFilters();
10981133
}
10991134

11001135
function clearModelSelection(event) {
11011136
event.stopPropagation();
11021137
selectedModels.clear();
11031138
updateModelFilterDisplay();
1139+
saveFilters();
11041140
applyFilters();
11051141
}
11061142

@@ -1154,6 +1190,120 @@ function handleModelTagClick(event, modelName) {
11541190
toggleModelSelection(event, modelName);
11551191
}
11561192

1193+
function handleDateClick(event, dateText) {
1194+
event.stopPropagation();
1195+
// Extract just the YYYY-MM-DD part
1196+
const date = dateText.trim().split(" ")[0];
1197+
1198+
// Toggle date filter - if already selected, clear it
1199+
if (selectedDate === date) {
1200+
selectedDate = null;
1201+
} else {
1202+
selectedDate = date;
1203+
}
1204+
1205+
updateDateFilterDisplay();
1206+
saveFilters();
1207+
applyFilters();
1208+
}
1209+
1210+
function updateDateFilterDisplay() {
1211+
// Find all date cells and update their styling
1212+
const dateCells = document.querySelectorAll(".date-text");
1213+
dateCells.forEach((cell) => {
1214+
const cellDate = cell.textContent.trim().split(" ")[0];
1215+
if (selectedDate && cellDate === selectedDate) {
1216+
cell.classList.add("date-selected");
1217+
} else {
1218+
cell.classList.remove("date-selected");
1219+
}
1220+
});
1221+
1222+
// Update the filter badge
1223+
const badge = document.getElementById("date-filter-badge");
1224+
const badgeText = document.getElementById("date-filter-text");
1225+
const container = document.getElementById("active-filters-container");
1226+
1227+
if (selectedDate) {
1228+
if (badgeText) badgeText.textContent = selectedDate;
1229+
if (badge) badge.style.display = "inline-flex";
1230+
if (container) container.style.display = "flex";
1231+
} else {
1232+
if (badge) badge.style.display = "none";
1233+
// Hide container if no filters are active
1234+
if (container && !hasActiveFilters()) {
1235+
container.style.display = "none";
1236+
}
1237+
}
1238+
}
1239+
1240+
function hasActiveFilters() {
1241+
return (
1242+
selectedDate !== null ||
1243+
selectedModels.size > 0 ||
1244+
(document.getElementById("game-filter")?.value || "") !== ""
1245+
);
1246+
}
1247+
1248+
function clearDateFilter(event) {
1249+
if (event) {
1250+
event.preventDefault();
1251+
event.stopPropagation();
1252+
}
1253+
selectedDate = null;
1254+
updateDateFilterDisplay();
1255+
saveFilters();
1256+
applyFilters();
1257+
}
1258+
1259+
// Filter persistence
1260+
const FILTERS_KEY = "picker_filters";
1261+
1262+
function saveFilters() {
1263+
const filters = {
1264+
game: document.getElementById("game-filter")?.value || "",
1265+
models: Array.from(selectedModels),
1266+
date: selectedDate,
1267+
};
1268+
try {
1269+
localStorage.setItem(FILTERS_KEY, JSON.stringify(filters));
1270+
} catch (e) {
1271+
console.error("Failed to save filters:", e);
1272+
}
1273+
}
1274+
1275+
function loadFilters() {
1276+
try {
1277+
const stored = localStorage.getItem(FILTERS_KEY);
1278+
if (stored) {
1279+
const filters = JSON.parse(stored);
1280+
1281+
// Restore game filter
1282+
const gameFilter = document.getElementById("game-filter");
1283+
if (gameFilter && filters.game) {
1284+
gameFilter.value = filters.game;
1285+
}
1286+
1287+
// Restore model filter
1288+
if (filters.models && Array.isArray(filters.models)) {
1289+
filters.models.forEach((model) => selectedModels.add(model));
1290+
updateModelFilterDisplay();
1291+
}
1292+
1293+
// Restore date filter
1294+
if (filters.date) {
1295+
selectedDate = filters.date;
1296+
updateDateFilterDisplay();
1297+
}
1298+
1299+
return true;
1300+
}
1301+
} catch (e) {
1302+
console.error("Failed to load filters:", e);
1303+
}
1304+
return false;
1305+
}
1306+
11571307
// Sorting functionality
11581308
let currentSort = { column: null, ascending: true };
11591309

codeclash/viewer/templates/picker.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626
</div>
2727
<div class="right-controls">
2828
<div class="filter-controls">
29+
<div id="active-filters-container" class="active-filters-container" style="display: none;">
30+
<span class="filter-label">Active filters:</span>
31+
<span id="date-filter-badge" class="filter-badge" style="display: none;">
32+
<i class="bi bi-calendar"></i>
33+
<span id="date-filter-text"></span>
34+
<button class="filter-badge-close" onclick="clearDateFilter(event)" title="Clear date filter">
35+
<i class="bi bi-x"></i>
36+
</button>
37+
</span>
38+
</div>
2939
<button id="clear-filters" onclick="clearFilters()" title="Clear all filters">
3040
<i class="bi bi-x-circle"></i> Clear Filters
3141
</button>
@@ -47,6 +57,13 @@
4757
</div>
4858
<div class="right-controls">
4959
<div class="filter-controls">
60+
<div id="active-filters-container" class="active-filters-container" style="display: none;">
61+
<span class="filter-label">Active filters:</span>
62+
<span id="date-filter-badge" class="filter-badge" style="display: none;">
63+
<i class="bi bi-calendar"></i>
64+
<span id="date-filter-text"></span>
65+
</span>
66+
</div>
5067
<button id="clear-filters" disabled title="Not available in static mode">
5168
<i class="bi bi-x-circle"></i> Clear Filters
5269
</button>
@@ -117,7 +134,7 @@
117134

118135
<div class="date-cell">
119136
{% if game.is_game and game.created_timestamp %}
120-
<span class="date-text">{{ game.created_timestamp|format_timestamp }}</span>
137+
<span class="date-text clickable-filter" onclick="handleDateClick(event, '{{ game.created_timestamp|format_timestamp }}')" title="Click to filter by this date">{{ game.created_timestamp|format_timestamp }}</span>
121138
{% else %}
122139
<span class="date-unknown">-</span>
123140
{% endif %}

0 commit comments

Comments
 (0)