Compare commits

...

8 Commits

Author SHA1 Message Date
Kiyomichi Kosaka da56bfce7a Merge pull request #79 from ok2/codex/implement-swipe-mechanism-for-eonstrips
Enable swipe navigation in detail view
2025-06-20 14:04:19 +02:00
Kiyomichi Kosaka f38f4af4ca Add swipe navigation for detail view 2025-06-20 14:03:59 +02:00
Kiyomichi Kosaka ff5d2a7816 Merge pull request #78 from ok2/codex/improve-border-visibility-in-detail-view
Improve event border visibility
2025-06-20 13:59:21 +02:00
Kiyomichi Kosaka 3961bc3345 Improve visibility of event borders 2025-06-20 13:59:03 +02:00
Kiyomichi Kosaka 3974237f42 Merge pull request #77 from ok2/codex/ensure-visible-borders-for-event-boxes
Improve event box borders
2025-06-20 13:49:46 +02:00
Kiyomichi Kosaka 3822b0cdf7 Enhance detail event box visibility 2025-06-20 13:49:32 +02:00
Kiyomichi Kosaka 8451fa9f54 Merge pull request #76 from ok2/codex/fix-event-width-and-color-handling
Improve event overlap display
2025-06-20 13:42:02 +02:00
Kiyomichi Kosaka 26cfa8b868 Improve detail event display 2025-06-20 13:41:48 +02:00
3 changed files with 156 additions and 34 deletions
+10 -9
View File
@@ -12,15 +12,16 @@
// duration - optional length of the event in seconds. // duration - optional length of the event in seconds.
// showMega - optional boolean, show label on the megasequence view (default true). // showMega - optional boolean, show label on the megasequence view (default true).
// showDetail - optional boolean, show event in the detail view (default true). // showDetail - optional boolean, show event in the detail view (default true).
// color - optional hex color for the event (used in detail view)
window.SPECIAL_EVENTS = [ window.SPECIAL_EVENTS = [
{ cobie: '49f4.9332', label: 'Afina', unit: 'cosmocycle', interval: 1 }, { cobie: '49f4.9332', label: 'Afina', color: '#e57373', unit: 'cosmocycle', interval: 1 },
{ cobie: '11e5.f552', label: 'Oleks', unit: 'cosmocycle', interval: 1 }, { cobie: '11e5.f552', label: 'Oleks', color: '#64b5f6', unit: 'cosmocycle', interval: 1 },
{ cobie: '4d07.a2b2', label: 'Vincent', unit: 'cosmocycle', interval: 1 }, { cobie: '4d07.a2b2', label: 'Vincent', color: '#81c784', unit: 'cosmocycle', interval: 1 },
{ cobie: '3edc.d430', label: 'Hochzeitstag', unit: 'cosmocycle', interval: 1 }, { cobie: '3edc.d430', label: 'Hochzeitstag', color: '#ffb74d', unit: 'cosmocycle', interval: 1 },
{ cobie: '330d.d4ae', label: 'Zusammentag', unit: 'cosmocycle', interval: 1 }, { cobie: '330d.d4ae', label: 'Zusammentag', color: '#ba68c8', unit: 'cosmocycle', interval: 1 },
{ cobie: '11de.0c52', label: 'Anna', unit: 'cosmocycle', interval: 1 }, { cobie: '11de.0c52', label: 'Anna', color: '#4db6ac', unit: 'cosmocycle', interval: 1 },
{ cobie: '467f.ae61', label: 'Iris', unit: 'cosmocycle', interval: 1 }, { cobie: '467f.ae61', label: 'Iris', color: '#7986cb', unit: 'cosmocycle', interval: 1 },
{ cobie: '6854.7a75', label: 'Sleep', unit: 'second', interval: 86400, duration: 28800, showMega: false }, { cobie: '6854.7a75', label: 'Sleep', color: '#546e7a', unit: 'second', interval: 86400, duration: 28800, showMega: false },
{ cobie: '6854.9695', label: 'Night', unit: 'second', interval: 86400, duration: 28800, showMega: false, shiftWithTimezone: true } { cobie: '6854.9695', label: 'Night', color: '#212121', unit: 'second', interval: 86400, duration: 28800, showMega: false, shiftWithTimezone: true }
]; ];
+137 -18
View File
@@ -44,6 +44,50 @@ let updateInterval;
let lastRenderedEonstrip = null; let lastRenderedEonstrip = null;
let currentDetailCob = null; let currentDetailCob = null;
function hexToRgba(hex, alpha) {
if (!hex) return '';
let c = hex.replace('#', '');
if (c.length === 3) {
c = c.split('').map(x => x + x).join('');
}
const r = parseInt(c.substring(0,2),16);
const g = parseInt(c.substring(2,4),16);
const b = parseInt(c.substring(4,6),16);
return `rgba(${r},${g},${b},${alpha})`;
}
function getContrastColor(hex) {
if (!hex) return '#fff';
let c = hex.replace('#','');
if (c.length === 3) c = c.split('').map(x=>x+x).join('');
const r = parseInt(c.substr(0,2),16);
const g = parseInt(c.substr(2,2),16);
const b = parseInt(c.substr(4,2),16);
const yiq = (r*299 + g*587 + b*114) / 1000;
return yiq >= 128 ? '#000' : '#fff';
}
function lightenColor(hex, percent) {
if (!hex) return '#fff';
let c = hex.replace('#','');
if (c.length === 3) c = c.split('').map(x=>x+x).join('');
let r = parseInt(c.substr(0,2),16);
let g = parseInt(c.substr(2,2),16);
let b = parseInt(c.substr(4,2),16);
r = Math.min(255, Math.round(r + (255 - r) * percent));
g = Math.min(255, Math.round(g + (255 - g) * percent));
b = Math.min(255, Math.round(b + (255 - b) * percent));
return '#' + [r,g,b].map(x=>x.toString(16).padStart(2,'0')).join('');
}
function applyEventColors(elem, color, alpha) {
if (!color || !elem) return;
elem.style.setProperty('--bg-color', hexToRgba(color, alpha));
// Use a lighter shade for the border so it stands out even for dark colors
elem.style.setProperty('--border-color', lightenColor(color, 0.4));
elem.style.setProperty('--text-color', getContrastColor(color));
}
function getTimezoneOffsetSeconds(date) { function getTimezoneOffsetSeconds(date) {
if (currentTimezone === 'UTC') return 0; if (currentTimezone === 'UTC') return 0;
if (currentTimezone === 'TAI') return getTAIOffsetAt(date); if (currentTimezone === 'TAI') return getTAIOffsetAt(date);
@@ -761,6 +805,7 @@ function showEonstripDetail(index, startCob) {
const relEnd = (occ + duration - start) / COBIE_UNITS.eonstrip; const relEnd = (occ + duration - start) / COBIE_UNITS.eonstrip;
events.push({ events.push({
label: ev.label, label: ev.label,
color: ev.color,
start: relStart, start: relStart,
end: relEnd, end: relEnd,
cobStart: occ, cobStart: occ,
@@ -772,16 +817,30 @@ function showEonstripDetail(index, startCob) {
} }
}); });
events.sort((a,b)=>a.start-b.start); events.sort((a,b)=>a.start-b.start);
const columns = [];
const groups = [];
let active = [];
events.forEach(ev=>{ events.forEach(ev=>{
active = active.filter(a=>a.end>ev.start);
if (active.length===0) {
groups.push({events:[],columns:[],maxCols:0});
}
const g = groups[groups.length-1];
let col=0; let col=0;
while(columns[col] && columns[col] > ev.start) col++; while(g.columns[col] && g.columns[col] > ev.start) col++;
columns[col] = ev.end; g.columns[col] = ev.end;
ev.col = col; ev.col = col;
g.maxCols = Math.max(g.maxCols, col+1);
g.events.push(ev);
active.push(ev);
}); });
const width = 100 / (columns.length || 1); groups.forEach(g=>{
const width = 100/(g.maxCols||1);
g.events.forEach(ev=>ev.width=width);
});
events.forEach(ev=>{ events.forEach(ev=>{
const left = ev.col * width; const left = ev.col * ev.width;
const displayStart = Math.max(0, ev.start); const displayStart = Math.max(0, ev.start);
const displayEnd = Math.min(1, ev.end); const displayEnd = Math.min(1, ev.end);
const elem = document.createElement('div'); const elem = document.createElement('div');
@@ -799,7 +858,9 @@ function showEonstripDetail(index, startCob) {
} }
elem.style.top = (displayStart * 100) + '%'; elem.style.top = (displayStart * 100) + '%';
elem.style.left = `calc(var(--scale-width) + ${left}%)`; elem.style.left = `calc(var(--scale-width) + ${left}%)`;
elem.style.width = `calc(${width}% - 2px)`; elem.style.width = `calc(${ev.width}% - 2px)`;
if (ev.color) applyEventColors(elem, ev.color, 0.4);
if (elem.classList.contains('small-event') || elem.className === 'event-line') { if (elem.classList.contains('small-event') || elem.className === 'event-line') {
const label = document.createElement('span'); const label = document.createElement('span');
@@ -809,6 +870,7 @@ function showEonstripDetail(index, startCob) {
} }
label.textContent = ev.label; label.textContent = ev.label;
elem.appendChild(label); elem.appendChild(label);
if (ev.color) applyEventColors(label, ev.color, 0.5);
} else { } else {
elem.textContent = ev.label; elem.textContent = ev.label;
} }
@@ -857,12 +919,16 @@ function updateDetailCurrentTime() {
function detailPrev() { function detailPrev() {
if (currentDetailCob === null) return; if (currentDetailCob === null) return;
showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip); animateDetailSwipe(-1, () => {
showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
});
} }
function detailNext() { function detailNext() {
if (currentDetailCob === null) return; if (currentDetailCob === null) return;
showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip); animateDetailSwipe(1, () => {
showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
});
} }
function detailNow() { function detailNow() {
@@ -943,6 +1009,30 @@ function animateSwipe(direction, onDone) {
grid.addEventListener('transitionend', afterOut); grid.addEventListener('transitionend', afterOut);
} }
function animateDetailSwipe(direction, onDone) {
const tl = document.getElementById('detailTimeline');
if (!tl) { onDone(); return; }
tl.style.transition = 'none';
tl.style.transform = 'translateX(0)';
void tl.offsetWidth;
tl.style.transition = 'transform 0.3s ease';
tl.style.transform = `translateX(${direction > 0 ? '-100%' : '100%'})`;
function afterOut() {
tl.removeEventListener('transitionend', afterOut);
tl.style.transition = 'none';
tl.style.transform = `translateX(${direction > 0 ? '100%' : '-100%'})`;
onDone();
void tl.offsetWidth;
tl.style.transition = 'transform 0.3s ease';
tl.style.transform = 'translateX(0)';
}
tl.addEventListener('transitionend', afterOut);
}
function goToNow() { function goToNow() {
manualMode = false; manualMode = false;
manualCobiets = 0; manualCobiets = 0;
@@ -1080,11 +1170,12 @@ document.getElementById('toggleExtended').addEventListener('click', () => {
}); });
// ── Swipe & Wheel Navigation ──────────────────────────────────────────────── // ── Swipe & Wheel Navigation ────────────────────────────────────────────────
let swipeStartX = null; let swipeStartX = null;
let swipeStartY = null; let swipeStartY = null;
let swipeMods = { altKey: false, shiftKey: false, ctrlKey: false }; let swipeMods = { altKey: false, shiftKey: false, ctrlKey: false };
let isSwiping = false; let isSwiping = false;
let swipeGrid = null; let swipeGrid = null;
let swipeContext = 'calendar';
function swipeStart(e) { function swipeStart(e) {
const touch = e.touches ? e.touches[0] : e; const touch = e.touches ? e.touches[0] : e;
@@ -1095,7 +1186,17 @@ function swipeStart(e) {
shiftKey: e.shiftKey || false, shiftKey: e.shiftKey || false,
ctrlKey: e.ctrlKey || false ctrlKey: e.ctrlKey || false
}; };
swipeGrid = document.getElementById('eonstripGrid');
const detailView = document.getElementById('eonstripDetailView');
const detailOpen = detailView && detailView.style.display === 'block';
if (detailOpen) {
swipeGrid = document.getElementById('detailTimeline');
swipeContext = 'detail';
} else {
swipeGrid = document.getElementById('eonstripGrid');
swipeContext = 'calendar';
}
if (swipeGrid) { if (swipeGrid) {
swipeGrid.style.transition = 'none'; swipeGrid.style.transition = 'none';
} }
@@ -1132,9 +1233,19 @@ function swipeEnd(e) {
// prepare opposite side // prepare opposite side
swipeGrid.style.transition = 'none'; swipeGrid.style.transition = 'none';
swipeGrid.style.transform = `translateX(${dx < 0 ? width : -width}px)`; swipeGrid.style.transform = `translateX(${dx < 0 ? width : -width}px)`;
const step = getStep(swipeMods);
currentOffset += direction * step; if (swipeContext === 'calendar') {
updateCalendar(); const step = getStep(swipeMods);
currentOffset += direction * step;
updateCalendar();
} else if (swipeContext === 'detail') {
if (direction === 1) {
currentDetailCob !== null && showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
} else {
currentDetailCob !== null && showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
}
}
void swipeGrid.offsetWidth; void swipeGrid.offsetWidth;
swipeGrid.style.transition = 'transform 0.3s ease'; swipeGrid.style.transition = 'transform 0.3s ease';
swipeGrid.style.transform = 'translateX(0)'; swipeGrid.style.transform = 'translateX(0)';
@@ -1159,7 +1270,15 @@ document.addEventListener('mouseup', swipeEnd);
function wheelNavigate(e) { function wheelNavigate(e) {
if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 10) { if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 10) {
const direction = e.deltaX > 0 ? 1 : -1; const direction = e.deltaX > 0 ? 1 : -1;
navigatePeriod(e, direction); if (currentDetailCob !== null) {
if (direction === 1) {
detailNext();
} else {
detailPrev();
}
} else {
navigatePeriod(e, direction);
}
} }
} }
+9 -7
View File
@@ -441,15 +441,16 @@
.event-box, .event-line { .event-box, .event-line {
position: absolute; position: absolute;
left: var(--scale-width); left: var(--scale-width);
background: rgba(255,0,255,0.4); background: var(--bg-color, rgba(255,0,255,0.4));
border: 1px solid rgba(0,255,255,0.7); border: 1px solid var(--border-color, rgba(0,255,255,0.7));
border-radius: 4px; border-radius: 4px;
padding: 2px 4px; padding: 2px 4px;
color: #fff; color: var(--text-color, #fff);
font-size: 0.75em; font-size: 0.75em;
overflow: visible; overflow: visible;
white-space: nowrap; white-space: nowrap;
z-index: 3; z-index: 3;
box-shadow: 0 0 4px var(--border-color, rgba(0,255,255,0.9));
} }
.event-box { .event-box {
display: flex; display: flex;
@@ -476,16 +477,17 @@
left: 50%; left: 50%;
transform: translate(-50%, -2px); transform: translate(-50%, -2px);
margin-bottom: 2px; margin-bottom: 2px;
background: rgba(255,0,255,0.5); background: var(--bg-color, rgba(255,0,255,0.5));
border: 1px solid rgba(0,255,255,0.8); border: 1px solid var(--border-color, rgba(0,255,255,0.8));
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
white-space: nowrap; white-space: nowrap;
color: #fff; color: var(--text-color, #fff);
font-size: 0.75em; font-size: 0.75em;
font-weight: 600; font-weight: 600;
text-shadow: 0 0 4px #ff00ff; text-shadow: 0 0 4px var(--border-color, #ff00ff);
z-index: 4; z-index: 4;
box-shadow: 0 0 4px var(--border-color, rgba(0,255,255,0.9));
} }
.event-line .event-label.below, .event-line .event-label.below,