diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-02-05 10:43:19 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-02-05 10:43:19 -1000 |
| commit | 1eab4d59454fa5999675d51b99e77ac6580aba95 (patch) | |
| tree | 6b653e39d33fd879f29f769cdf3bd3f6bfcd3f05 /web/static/js | |
| parent | 5ddb419137b814481a208d1dd0d18ac36ed554ea (diff) | |
Improve session handling, shopping UI, and cleanup
Session improvements:
- Extend session lifetime to 7 days for mobile convenience
- Add idle timeout to extend session on activity
- Use standard cookie name for better compatibility
Shopping model:
- Add FlattenItemsForStore helper for extracting store items
- Add StoreNames helper for store list
- Improve shopping-tab.html with inline add forms
Frontend:
- Add WebSocket reconnection and agent approval UI to app.js
- Simplify timeline calendar JS (move event positioning to CSS)
- Update login page styling
Deployment:
- Remove unused git checkout step from deploy.sh
- Update apache.conf WebSocket proxy settings
Documentation:
- Add Agent Context API feature spec to issues/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'web/static/js')
| -rw-r--r-- | web/static/js/app.js | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/web/static/js/app.js b/web/static/js/app.js index 3ecd0a1..5dffacc 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -3,6 +3,108 @@ // Constants const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes in milliseconds +// Timeline Calendar Initialization +function initTimelineCalendar(calendarId, nowLineId, untimedSectionId) { + const calendar = document.getElementById(calendarId); + if (!calendar) return; + + const events = calendar.querySelectorAll('.calendar-event'); + const hourHeight = 40; + const startHour = parseInt(calendar.dataset.startHour) || 8; + const nowHour = parseInt(calendar.dataset.nowHour); + const nowMinute = parseInt(calendar.dataset.nowMinute); + + // Build event data for overlap detection + const eventData = []; + events.forEach(function(el) { + const hour = parseInt(el.dataset.hour); + const minute = parseInt(el.dataset.minute); + const endHourVal = parseInt(el.dataset.endHour); + const endMinute = parseInt(el.dataset.endMinute); + const startMin = hour * 60 + minute; + let endMin; + if (endHourVal > hour || (endHourVal === hour && endMinute > minute)) { + endMin = endHourVal * 60 + endMinute; + } else { + endMin = startMin + 55; + } + eventData.push({ el, startMin, endMin, column: 0 }); + }); + + // Assign columns for overlapping events + eventData.sort((a, b) => a.startMin - b.startMin); + for (let i = 0; i < eventData.length; i++) { + const ev = eventData[i]; + const overlaps = eventData.filter((other, j) => + j < i && other.endMin > ev.startMin && other.startMin < ev.endMin + ); + const usedCols = overlaps.map(o => o.column); + let col = 0; + while (usedCols.includes(col)) col++; + ev.column = col; + } + + // Position events with column-based indentation + eventData.forEach(function(ev) { + const el = ev.el; + const hour = parseInt(el.dataset.hour); + const minute = parseInt(el.dataset.minute); + const top = (hour - startHour) * hourHeight + (minute / 60) * hourHeight; + const durationMinutes = ev.endMin - ev.startMin; + const height = Math.max(28, (durationMinutes / 60) * hourHeight - 4); + + el.style.top = top + 'px'; + el.style.height = height + 'px'; + el.style.left = (8 + ev.column * 16) + 'px'; + el.style.display = 'block'; + + // Debounced hover effect (100ms delay) + let hoverTimeout; + el.addEventListener('mouseenter', function() { + hoverTimeout = setTimeout(() => el.classList.add('hover-active'), 100); + }); + el.addEventListener('mouseleave', function() { + clearTimeout(hoverTimeout); + el.classList.remove('hover-active'); + }); + + const url = el.dataset.url; + if (url) { + el.style.cursor = 'pointer'; + el.addEventListener('click', function(e) { + if (e.target.tagName !== 'INPUT') { + window.open(url, '_blank'); + } + }); + } + }); + + // Position the "now" line + if (nowLineId) { + const nowLine = document.getElementById(nowLineId); + if (nowLine && !isNaN(nowHour)) { + const nowTop = (nowHour - startHour) * hourHeight + (nowMinute / 60) * hourHeight; + if (nowTop >= 0) { + nowLine.style.top = nowTop + 'px'; + nowLine.style.display = 'block'; + } + } + } + + // Hide untimed section if empty + if (untimedSectionId) { + const untimedSection = document.getElementById(untimedSectionId); + if (untimedSection && untimedSection.querySelectorAll('.untimed-item').length === 0) { + untimedSection.style.display = 'none'; + } + } +} + +function initAllTimelineCalendars() { + initTimelineCalendar('today-calendar', 'now-line', 'untimed-section'); + initTimelineCalendar('tomorrow-calendar', null, 'tomorrow-untimed-section'); +} + // Get CSRF token from body hx-headers attribute function getCSRFToken() { const body = document.body; @@ -45,6 +147,12 @@ function setupHtmxListeners() { } }); + // After HTMX swap completes - reinitialize JS components + document.body.addEventListener('htmx:afterSwap', function(evt) { + // Initialize timeline calendars if they exist in the swapped content + initAllTimelineCalendars(); + }); + // After HTMX request completes document.body.addEventListener('htmx:afterRequest', function(evt) { const target = evt.detail.target; |
