Broken event support.

This commit is contained in:
Leo Vasanko 2025-08-20 13:37:18 -06:00
parent 9bb3740b61
commit 99b2d1a176
2 changed files with 247 additions and 31 deletions

View File

@ -73,10 +73,10 @@
/* Layout & typography */
:root {
--row-h: 2.2em;
--label-w: 4em;
--cell-w: 6em;
--cell-h: 6em;
--overlay-w: 3rem;
--label-w: minmax(4em, 8%);
--cell-w: 1fr;
--cell-h: clamp(4em, 8vh, 8em);
--overlay-w: minmax(3rem, 5%);
}
* { box-sizing: border-box }
@ -89,13 +89,13 @@ body {
}
.wrap {
width: fit-content;
margin: 2rem auto;
width: 100%;
margin: 0;
background: var(--panel);
height: calc(100vh - 4rem);
height: 100vh;
display: flex;
flex-direction: column;
min-width: calc(var(--label-w) + 7 * var(--cell-w) + 2.4rem);
padding: 1rem;
white-space: pre-wrap;
}
@ -127,6 +127,7 @@ header {
border-bottom: .1em solid var(--muted);
align-items: last baseline;
flex-shrink: 0;
width: 100%;
}
.calendar-container {
@ -141,15 +142,17 @@ header {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
flex: 0 0 auto;
width: calc(var(--label-w) + 7 * var(--cell-w) + var(--overlay-w));
flex: 1;
width: 100%;
scrollbar-width: none;
}
.calendar-viewport::-webkit-scrollbar { display: none }
.jogwheel-viewport {
position: absolute;
inset: 0 0 0 auto;
top: 0;
right: 0;
bottom: 0;
width: var(--overlay-w);
overflow-y: auto;
overflow-x: hidden;
@ -170,6 +173,7 @@ header {
overflow: visible;
height: var(--cell-h);
scroll-snap-align: start;
width: 100%;
}
/* Fixed heights for cells and labels */
@ -182,8 +186,8 @@ header h1 { margin: 0; font-size: 1rem }
.week-label {
display: grid;
place-items: center;
width: var(--label-w);
height: var(--row-h);
width: 100%;
height: var(--cell-h);
color: var(--muted);
cursor: ns-resize;
font-size: 1.2em;
@ -191,15 +195,85 @@ header h1 { margin: 0; font-size: 1rem }
.dow { text-transform: uppercase; }
.cell {
display: grid;
place-items: center;
width: var(--cell-w);
height: var(--row-h);
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding: 0.25em;
overflow: hidden;
width: 100%;
height: var(--cell-h);
font-weight: 700;
cursor: pointer;
transition: background-color .15s ease;
}
.cell h1 {
position: absolute;
top: 0.25em;
right: 0.25em;
padding: 0;
margin: 0;
transition: background-color .15s ease;
font-size: 1em;
z-index: 10;
}
.cell:hover h1 { text-shadow: 0 0 .2em; }
/* Event styles */
.event {
font-size: 0.75em;
padding: 0.1em 0.3em;
margin: 0.1em 0;
border-radius: 0.2em;
color: white;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
max-width: calc(100% - 0.5em);
line-height: 1.2;
cursor: pointer;
z-index: 5;
}
.event:hover {
opacity: 0.8;
}
/* Spanning event styles */
.event-span {
font-size: 0.75em;
padding: 0.1em 0.3em;
border-radius: 0.2em;
color: white;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
cursor: pointer;
transition: opacity 0.15s ease;
/* Calculate position and width using CSS variables and custom properties */
left: calc(var(--label-w) + var(--start-day) * (100% - var(--label-w) - var(--overlay-w)) / 7);
width: calc(var(--span-days) * (100% - var(--label-w) - var(--overlay-w)) / 7);
}
.event-span:hover {
opacity: 0.8;
}
.event-more {
font-size: 0.7em;
color: var(--muted);
font-style: italic;
margin-top: 0.1em;
}
/* Selection styles */
.weekend { color: var(--weekend) }
.firstday { color: var(--firstday); text-shadow: 0 0 .1em rgba(var(--ink-rgb), .5) }
@ -221,29 +295,22 @@ label:has(input[value]) {
}
.selected {
background: var(--select) !important;
border: 2px solid rgba(var(--ink-rgb), 0.3);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}
.selected h1,
:is(.range-start,.range-end,.range-middle,.range-single) h1 {
background: transparent !important;
color: var(--panel) !important;
font-weight: 700;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.cell {
position: relative;
/* Ensure events don't interfere with selection visibility */
.selected .event {
opacity: 0.7;
}
.cell h1 {
position: absolute;
top: 0;
left: 0;
padding: .25em;
margin: 0;
transition: background-color .15s ease;
font-size: 1em;
}
.cell:hover h1 { text-shadow: 0 0 .2em; }
/* Month labels in jogwheel column */
.month-name-label {
grid-column: -2 / -1;
@ -259,7 +326,7 @@ label:has(input[value]) {
position: absolute;
top: 0;
right: 0;
width: var(--overlay-w);
width: 100%;
}
.month-name-label > span {

View File

@ -37,6 +37,10 @@ class InfiniteCalendar {
this.weekend = [true, false, false, false, false, false, true]
// Event storage
this.events = new Map() // Map of date strings to arrays of events
this.eventIdCounter = 1
this.viewport = document.getElementById('calendar-viewport')
this.content = document.getElementById('calendar-content')
this.header = document.getElementById('calendar-header')
@ -279,6 +283,9 @@ class InfiniteCalendar {
weekEl.style.height = `${this.rowHeight}px`
this.content.appendChild(weekEl)
this.visibleWeeks.set(vw, weekEl)
// Add events to the newly created week
this.addEventsToWeek(weekEl, vw)
}
this.applySelectionToVisible()
@ -588,6 +595,148 @@ class InfiniteCalendar {
this.isDragging = false
this.setSelection(this.dragAnchor, dateStr)
document.body.style.cursor = 'default'
// Trigger event creation after selection with a small delay
if (this.selStart && this.selEnd) {
setTimeout(() => this.promptForEvent(), 100)
}
}
// -------- Event Management --------
promptForEvent() {
const title = prompt('Enter event title:')
if (!title || title.trim() === '') {
this.clearSelection()
return
}
this.createEvent({
title: title.trim(),
startDate: this.selStart,
endDate: this.selEnd
})
this.clearSelection()
}
createEvent(eventData) {
const event = {
id: this.eventIdCounter++,
title: eventData.title,
startDate: eventData.startDate,
endDate: eventData.endDate,
color: this.generateEventColor()
}
// Add event to all dates in the range
const startDate = new Date(fromLocalString(event.startDate))
const endDate = new Date(fromLocalString(event.endDate))
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const dateStr = toLocalString(d)
if (!this.events.has(dateStr)) {
this.events.set(dateStr, [])
}
this.events.get(dateStr).push({...event, isSpanning: startDate < endDate})
}
// Re-render visible weeks to show the new event
this.refreshEvents()
}
generateEventColor() {
const colors = [
'#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
'#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43'
]
return colors[Math.floor(Math.random() * colors.length)]
}
refreshEvents() {
// Re-render events for all visible weeks
for (const [weekDateStr, weekEl] of this.visibleWeeks) {
this.addEventsToWeek(weekEl, weekDateStr)
}
}
addEventsToWeek(weekEl, weekDateStr) {
const cells = weekEl.querySelectorAll('.cell[data-date]')
// Remove existing event elements from both week and individual cells
weekEl.querySelectorAll('.event-span').forEach(el => el.remove())
cells.forEach(cell => {
cell.querySelectorAll('.event-span').forEach(el => el.remove())
})
// Group events by their date ranges within this week
const weekEvents = new Map() // Map of event ID to event info
for (const cell of cells) {
const dateStr = cell.dataset.date
const events = this.events.get(dateStr) || []
events.forEach(event => {
if (!weekEvents.has(event.id)) {
weekEvents.set(event.id, {
...event,
startDateInWeek: dateStr,
endDateInWeek: dateStr,
startCell: cell,
endCell: cell,
daysInWeek: 1
})
} else {
const weekEvent = weekEvents.get(event.id)
weekEvent.endDateInWeek = dateStr
weekEvent.endCell = cell
weekEvent.daysInWeek++
}
})
}
// Create spanning elements for each event
weekEvents.forEach((weekEvent, eventId) => {
this.createSpanningEvent(weekEl, weekEvent)
})
}
createSpanningEvent(weekEl, weekEvent) {
const spanEl = document.createElement('div')
spanEl.className = 'event-span'
spanEl.style.backgroundColor = weekEvent.color
spanEl.textContent = weekEvent.title
spanEl.title = `${weekEvent.title} (${weekEvent.startDate === weekEvent.endDate ? weekEvent.startDate : weekEvent.startDate + ' - ' + weekEvent.endDate})`
// Get all cells in the week (excluding week label and overlay)
const cells = Array.from(weekEl.querySelectorAll('.cell[data-date]'))
const startCellIndex = cells.indexOf(weekEvent.startCell)
const endCellIndex = cells.indexOf(weekEvent.endCell)
const spanDays = endCellIndex - startCellIndex + 1
// Use CSS custom properties for positioning
spanEl.style.setProperty('--start-day', startCellIndex)
spanEl.style.setProperty('--span-days', spanDays)
// Style the spanning event
spanEl.style.position = 'absolute'
spanEl.style.top = '0.25em'
spanEl.style.height = '1.2em'
spanEl.style.zIndex = '15'
spanEl.style.fontSize = '0.75em'
spanEl.style.padding = '0.1em 0.3em'
spanEl.style.borderRadius = '0.2em'
spanEl.style.color = 'white'
spanEl.style.fontWeight = '500'
spanEl.style.whiteSpace = 'nowrap'
spanEl.style.overflow = 'hidden'
spanEl.style.textOverflow = 'ellipsis'
spanEl.style.cursor = 'pointer'
spanEl.style.lineHeight = '1.2'
spanEl.style.pointerEvents = 'auto'
// Append to the week element (not individual cells)
weekEl.appendChild(spanEl)
}
}