Broken event support.
This commit is contained in:
parent
9bb3740b61
commit
99b2d1a176
129
calendar.css
129
calendar.css
@ -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 {
|
||||
|
149
calendar.js
149
calendar.js
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user