New big and small scope sunset and sunrise simulation
New calculations for the day tabs
Added missing icons to simple view
New record popups
Week ahead description popups
Camping profile
Tweak to some of the indoor tems
This commit is contained in:
fraxle
2026-10-08 09:53:41 +01:00
parent 71860b9dd9
commit 862f631beb
22 changed files with 2169 additions and 429 deletions
+68 -9
View File
@@ -23,7 +23,10 @@ export function useColumnPopup() {
const closeTimerRef = useRef(null);
// --- Event tag popup ------------------------------------------------
// Holds { events[], slideIndex, x, y, arrowLeft, below }
// Holds { events[], date, slideIndex, x, y, arrowLeft, below }
// `date` is the YYYY-MM-DD of the row the tag was on - the popup's Quick
// Record button dates its journal entry to the day being looked at, which
// is not necessarily today (the table can be showing any of the 14 days).
// When multiple events are in the popup they auto-cycle with a crossfade.
const [eventTagPopup, setEventTagPopup] = useState(null);
const [evSlideIndex, setEvSlideIndex] = useState(0);
@@ -168,12 +171,12 @@ export function useColumnPopup() {
}, [eventTagPopup, evSlideTo]);
// --- Event tag popup handlers ----------------------------------------
const openEventTagPopup = (events, spanEl, startIndex = 0) => {
const openEventTagPopup = (events, spanEl, startIndex = 0, date = null) => {
clearInterval(evSlideTimerRef.current);
setEvSlideIndex(startIndex);
prevSlideIndexRef.current = startIndex;
setEvTransition(null);
setEventTagPopup({ events, ...calcEventPopupPos(spanEl) });
setEventTagPopup({ events, date, ...calcEventPopupPos(spanEl) });
};
const closeEventTagPopup = () => {
@@ -185,8 +188,9 @@ export function useColumnPopup() {
clearInterval(evSlideTimerRef.current);
};
// rowEvents = all events for that row (passed in from app.js)
const handleEventTagClick = (rowEvents, startIndex, e) => {
// rowEvents = all events for that row, dateISO = that row's YYYY-MM-DD
// (both passed in from app.js)
const handleEventTagClick = (rowEvents, startIndex, dateISO, e) => {
e.stopPropagation();
clearTimeout(evHoverTimerRef.current);
clearTimeout(evCloseTimerRef.current);
@@ -194,19 +198,27 @@ export function useColumnPopup() {
if (eventTagPopup && JSON.stringify(eventTagPopup.events.map(ev => ev.id)) === JSON.stringify(rowEvents.map(ev => ev.id))) {
closeEventTagPopup(); return;
}
openEventTagPopup(rowEvents, e.currentTarget, startIndex);
openEventTagPopup(rowEvents, e.currentTarget, startIndex, dateISO);
};
const handleEventTagEnter = (rowEvents, startIndex, e) => {
const handleEventTagEnter = (rowEvents, startIndex, dateISO, e) => {
clearTimeout(evHoverTimerRef.current);
clearTimeout(evCloseTimerRef.current);
const spanEl = e.currentTarget;
evHoverTimerRef.current = setTimeout(() => openEventTagPopup(rowEvents, spanEl, startIndex), 700);
evHoverTimerRef.current = setTimeout(() => openEventTagPopup(rowEvents, spanEl, startIndex, dateISO), 700);
};
const handleEventTagLeave = () => clearTimeout(evHoverTimerRef.current);
const handleEventTagPopupEnter = () => clearTimeout(evCloseTimerRef.current);
// Moving into the popup stops the carousel for good (leaving it closes the
// popup anyway, so there is nothing to restart). It has a Quick Record
// button in it now: an event that slides out from under the cursor mid-reach
// would file the wrong one.
const handleEventTagPopupEnter = () => {
clearTimeout(evCloseTimerRef.current);
clearInterval(evSlideTimerRef.current);
};
const handleEventTagPopupLeave = () => { evCloseTimerRef.current = setTimeout(closeEventTagPopup, 200); };
useEffect(() => {
@@ -239,12 +251,59 @@ export function useColumnPopup() {
}
}, [eventTagPopup]);
// --- Week Ahead row popup ------------------------------------------
// Click-driven only: the Week Ahead rows used to carry their
// explanation in `title=` tooltips, which never surface on touch.
// Holds { key, title, sub, rows:[{icon,text}], ...pos }.
const [weekPopup, setWeekPopup] = useState(null);
const weekPopupRef = useRef(null);
const closeWeekPopup = () => setWeekPopup(null);
const handleWeekRowClick = (payload, e) => {
e.stopPropagation();
if (weekPopup?.key === payload.key) { closeWeekPopup(); return; }
setWeekPopup({ ...payload, ...calcPopupPos(e.currentTarget) });
};
useEffect(() => {
if (!weekPopup) return;
const onClickOutside = (e) => {
if (weekPopupRef.current && !weekPopupRef.current.contains(e.target)) closeWeekPopup();
};
const onScrollOrResize = () => closeWeekPopup();
document.addEventListener('mousedown', onClickOutside);
window.addEventListener('scroll', onScrollOrResize, { passive: true, capture: true });
window.addEventListener('resize', onScrollOrResize, { passive: true });
return () => {
document.removeEventListener('mousedown', onClickOutside);
window.removeEventListener('scroll', onScrollOrResize, { capture: true });
window.removeEventListener('resize', onScrollOrResize);
};
}, [weekPopup]);
// Post-render clamp (same logic as the col popup above).
useEffect(() => {
if (!weekPopup || !weekPopupRef.current) return;
const rect = weekPopupRef.current.getBoundingClientRect();
const MARGIN = 8;
if (!weekPopup.below && rect.top < MARGIN) {
const delta = MARGIN - rect.top;
if (delta > 1) setWeekPopup(prev => prev ? { ...prev, y: prev.y + delta } : prev);
} else if (weekPopup.below && rect.bottom > window.innerHeight - MARGIN) {
const delta = rect.bottom - (window.innerHeight - MARGIN);
if (delta > 1) setWeekPopup(prev => prev ? { ...prev, y: prev.y - delta } : prev);
}
}, [weekPopup]);
return {
// column-header popup
colPopup, colPopupRef,
handleThClick, handleThEnter, handleThLeave,
handlePopupEnter, handlePopupLeave,
closePopup,
// week-ahead row popup
weekPopup, weekPopupRef, handleWeekRowClick, closeWeekPopup,
// event-tag popup
eventTagPopup, eventTagPopupRef,
evSlideIndex, evTransition, evSlideTo,