feature(#315): reworked the whole date-picker (#475)

This commit is contained in:
Daniel Graf
2025-11-16 05:30:48 +01:00
committed by GitHub
parent baa9be9c24
commit b2652d7bf5
5 changed files with 2473 additions and 2178 deletions

View File

@@ -1,492 +1,274 @@
.horizontal-date-picker {
position: fixed;
bottom: 0;
left: 0;
.date-picker {
width: 100%;
background-color: rgba(59, 59, 59, 0.9);
backdrop-filter: blur(10px);
padding: 10px 0;
z-index: 50;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
}
body.date-picker-hidden .horizontal-date-picker{
height: 0;
height: 80px;
overflow: hidden;
transition: all 0.3s ease;
opacity: 0;
pointer-events: none;
position: relative;
background: #fff;
font-family: var(--serif-font);
}
.date-picker-container {
display: flex;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
padding: 0 10px;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
scroll-snap-type: x mandatory;
scrollbar-width: none;
transition: opacity 0.4s ease-out, transform 0.4s ease-out, filter 0.4s ease-out;
/* Prevent scroll chaining to the page and allow horizontal panning only */
overscroll-behavior: contain;
touch-action: pan-x;
}
/* Perspective Zoom Transition */
.date-picker-container {
transition: opacity 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94),
transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
transform-style: preserve-3d;
perspective: 1000px;
}
.date-picker-container.transitioning.forward {
opacity: 0.2;
transform: scale(1.3) translateZ(-100px);
}
.date-picker-container.transitioning.backward {
opacity: 0.2;
transform: scale(0.7) translateZ(100px);
}
.date-picker-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
display: none;
}
.date-item {
flex: 0 0 auto;
padding: 8px 12px;
margin: 0 4px;
text-align: center;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
color: #f8f8f8;
.timeband-item {
min-width: 80px;
user-select: none;
scroll-snap-align: center;
position: relative;
}
.date-item.selected {
background-color: #fddca1;
color: white;
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.date-item.selected::before {
content: "\eb47";
font-family: 'Lineicons';
font-weight: 900;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
color: rgba(255, 255, 255, 0.9);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 1;
}
.date-item.selected:hover::before {
opacity: 1;
}
.date-item.selected:hover .day-name,
.date-item.selected:hover .day-number,
.date-item.selected:hover .month-year-name {
opacity: 0.2;
}
.date-item.unavailable {
opacity: 0.4;
cursor: not-allowed;
}
.date-item.unavailable:hover {
background-color: transparent;
transform: none;
}
/* Range mode styles */
.date-item.range-start,
.date-item.range-end {
background-color: rgba(117, 117, 117, 0.67);
color: white;
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
position: relative;
}
.date-item.range-start::before,
.date-item.range-end::before {
content: "\ec2a";
font-family: 'Lineicons';
font-weight: 900;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
color: rgba(255, 255, 255, 0.9);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 1;
}
.date-item.range-start:hover::before,
.date-item.range-end:hover::before {
opacity: 1;
}
.date-item.range-start:hover .day-name,
.date-item.range-start:hover .day-number,
.date-item.range-end:hover .day-name,
.date-item.range-end:hover .day-number {
opacity: 0.2;
}
.date-item.range-start::after {
content: 'Start';
position: absolute;
bottom: 3px;
left: 50%;
transform: translateX(-50%);
font-size: 0.8rem;
color: wheat;
font-weight: bold;
white-space: nowrap;
transition: opacity 0.3s ease;
}
.date-item.range-start:hover::after {
opacity: 0.2;
}
.date-item.range-end::after {
content: 'End';
position: absolute;
bottom: 3px;
left: 50%;
transform: translateX(-50%);
font-size: 0.8rem;
color: #fddca1;
font-weight: bold;
white-space: nowrap;
transition: opacity 0.3s ease;
}
.date-item.range-end:hover::after {
opacity: 0.2;
}
.date-item.range-preview,
.date-item.in-range {
background-color: rgba(117, 82, 0, 0.49);
color: white;
}
.date-item.in-range:hover {
background-color: rgba(253, 220, 161, 0.75);
}
/* Clear range button */
.clear-range-button {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(220, 74, 74, 0.8);
color: white;
border: none;
border-radius: 20px;
padding: 6px 12px;
font-size: 0.85rem;
cursor: pointer;
z-index: 52;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.3s ease;
}
.clear-range-button:hover {
background-color: rgba(220, 74, 74, 1);
transform: scale(1.05);
}
.date-item .day-name {
font-size: 0.8rem;
opacity: 0.8;
display: block;
transition: opacity 0.3s ease;
position: relative;
z-index: 0;
}
.date-item .day-number {
font-size: 1.2rem;
font-weight: bold;
display: block;
transition: opacity 0.3s ease;
position: relative;
z-index: 0;
}
.date-item .month-name {
font-size: 0.7rem;
display: block;
}
.date-item .month-year-name {
font-size: 0.7rem;
display: block;
font-weight: bold;
margin-top: 2px;
transition: opacity 0.3s ease;
position: relative;
z-index: 0;
}
.date-nav-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(74, 137, 220, 0.8);
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 51;
}
.date-nav-button:hover {
background-color: rgba(74, 137, 220, 1);
}
.date-nav-prev {
left: 10px;
}
.date-nav-next {
right: 10px;
}
/* Month row styles */
.month-row-container {
height: 100%;
display: flex;
flex-direction: column;
overflow-x: auto;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
padding: 0 10px;
margin-bottom: 8px;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.month-row-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.year-row {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
cursor: pointer;
user-select: none;
transition: background-color 0.25s ease-out, transform 0.25s ease-out, opacity 0.25s ease-out;
position: relative;
opacity: 1;
}
.today-button {
position: absolute;
left: 10px;
padding: 4px 12px;
/* Timeband-specific min-widths */
.timeband-month .timeband-item {
min-width: 100px;
}
.timeband-year .timeband-item {
min-width: 120px;
}
.timeband-item:hover {
background-color: #f5f5f5;
transform: translateY(-1px);
}
.timeband-item.selected {
background-color: #007bff;
color: white;
border-radius: 16px;
}
.timeband-item.in-range {
background-color: #e3f2fd;
color: #1976d2;
}
.timeband-item.range-start {
background-color: #007bff;
color: white;
}
.timeband-item.range-end {
background-color: #007bff;
color: white;
}
.timeband-item.today .day-number {
text-decoration: underline;
}
.timeband-item.locked.today {
border-color: #ffc107;
}
.primary-text {
font-size: 18px;
font-weight: 500;
transition: font-size 0.25s ease-out;
}
.secondary-text {
font-size: 12px;
opacity: 0.7;
margin-top: 2px;
transition: font-size 0.25s ease-out, opacity 0.25s ease-out;
}
.tertiary-text {
font-size: 10px;
opacity: 0.6;
margin-top: 1px;
transition: font-size 0.25s ease-out, opacity 0.25s ease-out;
}
/* Legacy classes for backward compatibility */
.day-number {
font-size: 18px;
font-weight: 500;
}
.day-name {
font-size: 12px;
opacity: 0.7;
margin-top: 2px;
}
.month-year {
font-size: 10px;
opacity: 0.6;
margin-top: 1px;
}
/* Timeband-specific styles */
.timeband-day .primary-text {
font-size: 18px;
}
.timeband-day .secondary-text {
font-size: 12px;
}
.timeband-day .tertiary-text {
font-size: 10px;
}
.timeband-month .primary-text {
font-size: 16px;
}
.timeband-month .secondary-text {
font-size: 14px;
}
.timeband-month .tertiary-text {
font-size: 11px;
}
.timeband-year .primary-text {
font-size: 20px;
}
.timeband-year .secondary-text {
font-size: 14px;
}
.timeband-year .tertiary-text {
font-size: 12px;
}
/* Demo styles */
.demo-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.controls {
margin-top: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.controls button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
transition: background-color 0.2s ease;
}
.controls button:hover {
background-color: #f5f5f5;
}
.selected-range {
margin-left: 20px;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 4px;
font-size: 14px;
}
/* Range indicators */
.date-picker-range-indicator {
position: absolute;
top: 10px;
transform: translateY(-50%);
width: 10px;
display: flex;
align-items: center;
gap: 5px;
z-index: 10;
}
.today-button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.year-item {
padding: 4px 16px;
margin: 0 8px;
text-align: center;
border-radius: 16px;
cursor: pointer;
transition: all 0.3s ease;
color: #f8f8f8;
font-size: 1rem;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 5;
pointer-events: none;
}
.year-item:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.year-item.selected {
background-color: #4a89dc;
color: white;
}
.year-item.unavailable {
opacity: 0.4;
color: #999;
cursor: not-allowed;
}
.year-item.unavailable:hover {
background-color: transparent;
}
.month-row {
display: flex;
overflow-x: auto;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.month-row::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.month-item {
flex: 0 0 auto;
padding: 4px 12px;
margin: 0 4px;
text-align: center;
border-radius: 16px;
cursor: pointer;
transition: all 0.3s ease;
color: #f8f8f8;
min-width: 60px;
font-size: 0.8rem;
position: relative;
}
.month-item:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.month-item.selected {
background-color: #4a89dc;
color: white;
}
.month-item.unavailable {
opacity: 0.4;
color: #999;
cursor: not-allowed;
}
.month-item.unavailable:hover {
background-color: transparent;
}
.month-item .year-label {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
background-color: rgba(74, 137, 220, 0.8);
padding: 2px 6px;
border-radius: 10px;
}
.date-item.selected {
background-color: unset;
color: var(--color-highlight);
transform: scale(1.05);
box-shadow: unset;
}
.month-item.selected {
background-color: var(--color-highlight);
color: var(--color-highlight);
font-weight: bolder;
background: unset;
}
.year-item.selected {
background-color: var(--color-highlight);
color: var(--color-highlight);
font-weight: bolder;
background: unset;
}
.today-button {
background-color: unset;
}
.date-item {
padding: unset;
margin: unset;
border-radius: 0;
}
.date-item.selected:hover {
border-bottom: 0;
}
.date-item .day-name,
.date-item .day-number {
font-size: 2rem;
font-weight: lighter;
}
.horizontal-date-picker {
position: fixed;
font-family: var(--serif-font);
bottom: 0;
.date-picker-range-indicator.left {
left: 0;
width: 100%;
background-color: rgba(59, 59, 59, 0.68);
backdrop-filter: blur(10px);
padding: 10px 0;
z-index: 50;
box-shadow: unset;
border-radius: 0 10px 10px 0;
}
.date-item:hover {
background-color: rgba(255, 255, 255, 0.2);
.date-picker-range-indicator.right {
right: 0;
border-radius: 10px 0 0 10px;
}
@media (max-width: 768px) {
.date-item {
min-width: 60px;
padding: 6px 8px;
}
.date-item .day-number {
font-size: 1rem;
}
.month-item {
min-width: 50px;
padding: 4px 8px;
font-size: 0.7rem;
}
.today-button {
justify-content: center;
position: initial;
}
.clear-range-button {
top: 5px;
right: 5px;
padding: 4px 8px;
font-size: 0.75rem;
}
.date-item.selected::before {
font-size: 1.5rem;
}
.date-item.range-start::before,
.date-item.range-end::before {
font-size: 1.5rem;
}
/* Hover info */
.date-picker-hover-info {
position: fixed;
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 8px 12px;
font-size: 12px;
text-align: center;
min-height: 20px;
opacity: 0;
transition: opacity 0.2s ease;
border-radius: 4px;
z-index: 1000;
pointer-events: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
white-space: nowrap;
}
.date-picker-hover-overlay {
position: fixed;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 6px 10px;
font-size: 11px;
text-align: center;
opacity: 0;
transition: opacity 0.2s ease;
border-radius: 4px;
z-index: 1001;
pointer-events: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
white-space: nowrap;
display: none;
}

View File

@@ -833,7 +833,7 @@ body.timeline-hidden.datepicker-hidden .timeline-toggle-btn {
.timeline-toggle-btn,
.datepicker-toggle-btn {
bottom: 188px;
bottom: 114px;
}
@@ -847,7 +847,7 @@ body.timeline-hidden.datepicker-hidden .timeline-toggle-btn {
font-size: 1.2rem;
}
body.datepicker-hidden #horizontal-date-picker-container {
body.datepicker-hidden #date-picker-container {
transform: translateY(100%);
transition: all 0.3s ease-in-out;
}
@@ -1848,40 +1848,6 @@ button:disabled {
pointer-events: none;
}
/* Create Memory FAB */
.create-memory-fab {
position: fixed;
bottom: 320px;
right: 2rem;
z-index: 999;
animation: slideInUp 0.3s ease-out;
}
.fab-button {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
background-color: #ff6c00;
color: white;
border-radius: 50px;
text-decoration: none;
box-shadow: 0 4px 12px rgba(255, 108, 0, 0.4);
transition: all 0.3s ease;
font-weight: 600;
font-size: 1rem;
}
.fab-button:hover {
background-color: #e66100;
box-shadow: 0 6px 16px rgba(255, 108, 0, 0.5);
transform: translateY(-2px);
}
.fab-button i {
font-size: 1.2rem;
}
@keyframes slideInUp {
from {
opacity: 0;
@@ -2416,3 +2382,82 @@ button:disabled {
}
}
.date-picker {
display: flex;
flex-direction: column;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(59, 59, 59, 0.68);
backdrop-filter: blur(10px);
z-index: 50;
box-shadow: unset;
color: white;
height: 110px;
}
.timeband-item:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.timeband-item .primary-text {
font-size: 2rem;
font-weight: lighter;
}
.timeband-item.selected {
background-color: unset;
color: var(--color-highlight);
transform: scale(1.05);
box-shadow: unset;
}
.timeband-item.in-range {
background-color: rgba(117, 82, 0, 0.49);
color: white;
}
.timeband-item.range-start, .timeband-item.range-end {
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
position: relative;
background-color: rgba(117, 82, 0, 0.49);
color: white;
}
.timeband-item.range-start:hover::before, .timeband-item.range-end:hover::before {
opacity: 1;
}
.timeband-item.selected::before {
content: "\eb47";
font-family: 'Lineicons';
font-weight: 900;
position: absolute;
top: 4px;
left: unset;
transform: unset;
font-size: 1.2rem;
color: rgba(255, 255, 255, 0.9);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 1;
right: 4px;
}
.timeband-item.locked::before,
.timeband-item.selected.range-start::before,
.timeband-item.selected.range-end::before{
content: "\ec2a";
}
.timeband-item.selected:hover::before {
opacity: 1;
}
.timeband-item.locked {
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@
<link rel="stylesheet" href="/css/avatar-marker.css">
<link th:if="${userSettings.customCssUrl}" rel="stylesheet" th:href="${userSettings.customCssUrl}">
<script src="/js/HumanizeDuration.js"></script>
<script src="/js/horizontal-date-picker.js"></script>
<script src="/js/date-picker-combined.js"></script>
<script src="/js/timeline-scroll-indicator.js"></script>
<script src="/js/photo-client.js"></script>
<script src="/js/raw-location-loader.js"></script>
@@ -61,7 +61,7 @@
<button type="button" class="datepicker-toggle-btn" id="datepicker-toggle-btn" onclick="toggleDatePicker()" title="Hide Date Picker">
<i class="lni lni-exit-down"></i>
</button>
<div id="horizontal-date-picker-container">
<div id="date-picker-container">
</div>
@@ -582,41 +582,66 @@
dateToUse = new Date(year, month - 1, day);
}
// Initialize horizontal date picker
window.horizontalDatePicker = new HorizontalDatePicker({
container: document.getElementById('horizontal-date-picker-container'),
selectedDate: dateToUse,
showNavButtons: false,
daysToShow: 21,
showMonthRow: true,
showYearRow: true,
yearsToShow: 5,
allowFutureDates: false,
showTodayButton: true,
onDateSelect: (date, formattedDate) => {
// Update URL
updateUrlWithDate(formattedDate);
// Clear any existing date range
currentDateRange = null;
// Trigger HTMX reload of timeline
document.body.dispatchEvent(new CustomEvent('dateChanged'));
},
onDateRangeSelect: (startDate, endDate, formattedStartDate, formattedEndDate) => {
// Store the date range
currentDateRange = {
startDate: formattedStartDate,
endDate: formattedEndDate
};
// Update URL to show range
const url = new URL(window.location);
url.searchParams.set('startDate', formattedStartDate);
url.searchParams.set('endDate', formattedEndDate);
url.searchParams.delete('date');
window.history.pushState({}, '', url);
// Trigger HTMX reload of timeline
document.body.dispatchEvent(new CustomEvent('dateChanged'));
}
window.horizontalDatePicker = new DatePicker('date-picker-container', {
daysToShow: 14,
prefetchDays: 25,
allowRangeSelection: true,
singleDateMode: true, // Enable single date mode with locking
dateFormat: 'YYYY-MM-DD',
startDate: dateToUse,
});
window.horizontalDatePicker.on('selectionChange', function (data) {
console.log(`Selection changed`, data);
updateUrlWithDate(data.startDate);
const startDate = new Date(data.startDate);
let endDate;
if (data.endDate) {
endDate = new Date(data.endDate);
} else {
switch (data.timeband) {
case 'day': endDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 1); break;
case 'month': endDate = new Date(startDate.getFullYear(), startDate.getMonth() + 1, 1); break;
case 'year': endDate = new Date(startDate.getFullYear() + 1, 0, 1); break
}
}
currentDateRange = {
startDate: window.horizontalDatePicker.formatDate(startDate),
endDate: window.horizontalDatePicker.formatDate(endDate)
};
const url = new URL(window.location);
url.searchParams.set('startDate', currentDateRange.startDate);
url.searchParams.set('endDate', currentDateRange.endDate);
url.searchParams.delete('date');
window.history.pushState({}, '', url);
// Trigger HTMX reload of timeline
document.body.dispatchEvent(new CustomEvent('dateChanged'));
});
// onDateSelect: (date, formattedDate) => {
// // Update URL
// updateUrlWithDate(formattedDate);
// // Clear any existing date range
// currentDateRange = null;
// // Trigger HTMX reload of timeline
// document.body.dispatchEvent(new CustomEvent('dateChanged'));
// },
// onDateRangeSelect: (startDate, endDate, formattedStartDate, formattedEndDate) => {
// // Store the date range
// currentDateRange = {
// startDate: formattedStartDate,
// endDate: formattedEndDate
// };
// // Update URL to show range
// const url = new URL(window.location);
// url.searchParams.set('startDate', formattedStartDate);
// url.searchParams.set('endDate', formattedEndDate);
// url.searchParams.delete('date');
// window.history.pushState({}, '', url);
// // Trigger HTMX reload of timeline
// document.body.dispatchEvent(new CustomEvent('dateChanged'));
// }
});
@@ -646,7 +671,7 @@
// Update the date picker to today
if (window.horizontalDatePicker) {
window.horizontalDatePicker.setDate(new Date());
window.horizontalDatePicker.setSelectedRange(new Date(), null);
}
// Start the timer to check for date changes every 30 seconds
@@ -716,8 +741,7 @@
icon.className = 'lni lni-play';
btn.title = 'Auto Update';
if (window.horizontalDatePicker) {
window.horizontalDatePicker.exitRangeMode();
window.horizontalDatePicker.setDate(getSelectedDate());
window.horizontalDatePicker.setSelectedRange(getSelectedDate());
}
document.body.classList.remove('auto-update-mode');
@@ -735,7 +759,7 @@
if (!isSelectedDateToday()) {
console.log('Auto-update: Switching to today\'s date');
if (window.horizontalDatePicker) {
window.horizontalDatePicker.setDate(new Date());
window.horizontalDatePicker.setSelectedRange(new Date());
}
}
}, 30000); // 30 seconds