Skip to content

Commit

Permalink
feat(FullCalendar): add conditional styling for participation status …
Browse files Browse the repository at this point in the history
…in grid

Signed-off-by: Grigory Vodyanov <scratchx@gmx.com>
  • Loading branch information
GVodyanov committed Feb 2, 2025
1 parent 88f5e95 commit ccb02fe
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 8 deletions.
7 changes: 7 additions & 0 deletions css/fullcalendar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@

.fc-event {
box-shadow: 0px 0px 0px 1px var(--color-primary-element-light) !important;
margin-top: 0px;
}

.fc-daygrid-day-top {
Expand Down Expand Up @@ -131,6 +132,7 @@
// ### FullCalendar Event adjustments
.fc-event {
padding-left: 3px;
border-width: 2px;

&.fc-event-nc-task-completed,
&.fc-event-nc-tentative,
Expand All @@ -149,6 +151,7 @@

.fc-event-title {
text-overflow: ellipsis;
font-weight: 700;
}

// Reminder icon on events with alarms set
Expand Down Expand Up @@ -200,6 +203,10 @@
max-width: 25vw;
}

svg {
margin-right: 2px;
}

@media only screen and (max-width: 767px) {
.fc-list-event-location,
.fc-list-event-description {
Expand Down
44 changes: 38 additions & 6 deletions src/fullcalendar/eventSources/eventSourceFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '../../utils/color.js'
import logger from '../../utils/logger.js'
import { getAllObjectsInTimeRange } from '../../utils/calendarObject.js'

import usePrincipalsStore from '../../store/principals.js'
/**
* convert an array of calendar-objects to events
*
Expand All @@ -23,6 +23,8 @@ import { getAllObjectsInTimeRange } from '../../utils/calendarObject.js'
* @return {object}[]
*/
export function eventSourceFunction(calendarObjects, calendar, start, end, timezone) {
const principalsStore = usePrincipalsStore()

const fcEvents = []
for (const calendarObject of calendarObjects) {
let allObjectsInTimeRange
Expand All @@ -32,16 +34,46 @@ export function eventSourceFunction(calendarObjects, calendar, start, end, timez
logger.error(error.message)
continue
}

for (const object of allObjectsInTimeRange) {
const classNames = []
let didEveryoneDecline = false

if (object.status === 'CANCELLED') {
classNames.push('fc-event-nc-cancelled')
} else if (object.status === 'TENTATIVE') {
classNames.push('fc-event-nc-tentative')
// You are an organizer
if (object.getFirstPropertyFirstValue('ORGANIZER') === `mailto:${principalsStore.getCurrentUserPrincipalEmail}`) {
// Check if all the attendees have declined the event
if (object.hasProperty('ATTENDEE')) {
didEveryoneDecline = true
for (const attendeeProperty of object.getPropertyIterator('ATTENDEE')) {
const hasDeclined = attendeeProperty.participationStatus === 'DECLINED'
if (!hasDeclined) {
didEveryoneDecline = false
}
}
if (didEveryoneDecline) {
classNames.push('fc-event-nc-all-declined')
}
}
if (object.status === 'CANCELLED') {
classNames.push('fc-event-nc-cancelled')
} else if (object.status === 'TENTATIVE') {
classNames.push('fc-event-nc-tentative')
}
}

// You are invited
for (const attendeeProperty of object.getPropertyIterator('ATTENDEE')) {
if (attendeeProperty.email === `mailto:${principalsStore.getCurrentUserPrincipalEmail}`) {
if (attendeeProperty.participationStatus === 'DECLINED') {
classNames.push('fc-event-nc-declined')
} else if (attendeeProperty.participationStatus === 'TENTATIVE') {
classNames.push('fc-event-nc-tentative')
} else if (attendeeProperty.participationStatus === 'NEEDS-ACTION') {
classNames.push('fc-event-nc-needs-action')
}
}
}


Check failure on line 76 in src/fullcalendar/eventSources/eventSourceFunction.js

View workflow job for this annotation

GitHub Actions / NPM lint

More than 1 blank line not allowed
if (object.hasComponent('VALARM')) {
classNames.push('fc-event-nc-alarms')
}
Expand Down
80 changes: 78 additions & 2 deletions src/fullcalendar/rendering/eventDidMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function({ event, el }) {
const dotElement = el.querySelector('.fc-list-event-dot')
dotElement.classList.remove('fc-list-event-dot')
dotElement.classList.add('fc-list-event-checkbox')
dotElement.style.color = dotElement.style.borderColor
dotElement.style.color = 'var(--color-main-text)'

if (event.extendedProps.percent === 100) {
dotElement.classList.add('calendar-grid-checkbox-checked')
Expand All @@ -41,7 +41,7 @@ export default function({ event, el }) {
const dotElement = el.querySelector('.fc-daygrid-event-dot')
dotElement.classList.remove('fc-daygrid-event-dot')
dotElement.classList.add('fc-daygrid-event-checkbox')
dotElement.style.color = dotElement.style.borderColor
dotElement.style.color = 'var(--color-main-text)'

if (event.extendedProps.percent === 100) {
dotElement.classList.add('calendar-grid-checkbox-checked')
Expand Down Expand Up @@ -91,4 +91,80 @@ export default function({ event, el }) {
descriptionContainer.appendChild(description)
}
}

if (
el.classList.contains('fc-event-nc-all-declined') ||

Check failure on line 96 in src/fullcalendar/rendering/eventDidMount.js

View workflow job for this annotation

GitHub Actions / NPM lint

'||' should be placed at the beginning of the line
el.classList.contains('fc-event-nc-needs-action') ||

Check failure on line 97 in src/fullcalendar/rendering/eventDidMount.js

View workflow job for this annotation

GitHub Actions / NPM lint

'||' should be placed at the beginning of the line
el.classList.contains('fc-event-nc-declined')
) {
const titleElement = el.querySelector('.fc-event-title')
const dotElement = el.querySelector('.fc-daygrid-event-dot')

if (dotElement) {
dotElement.style.borderWidth = '2px'
dotElement.style.background = 'transparent'
dotElement.style.minWidth = '10px'
dotElement.style.minHeight = '10px'
}

titleElement.style.color = 'var(--color-main-text)'
el.style.background = 'transparent'
el.title = t('calendar', 'All participants declined')

if (el.classList.contains('fc-event-nc-needs-action')) {
el.title = t('calendar', 'Please confirm your participation')
}

if (el.classList.contains('fc-event-nc-declined')) {
el.title = t('calendar', 'You declined this event')
titleElement.style.textDecoration = 'line-through'
}
}

if (el.classList.contains('fc-event-nc-all-declined')) {
const titleElement = el.querySelector('.fc-event-title')

const svgString = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m40-120 440-760 440 760H40Zm440-120q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Z"/></svg>'
titleElement.innerHTML = svgString + titleElement.innerHTML

const svgElement = titleElement.querySelector('svg')
svgElement.style.fill = el.style.borderColor
svgElement.style.width = '1em'
svgElement.style.marginBottom = '0.2em'
svgElement.style.verticalAlign = 'middle'
}

if (el.classList.contains('fc-event-nc-tentative')) {
const dotElement = el.querySelector('.fc-daygrid-event-dot')

const bgColor = el.style.backgroundColor ? el.style.backgroundColor : dotElement.style.borderColor
const bgStripeColor = darkenColor(bgColor)

let backgroundStyling = `repeating-linear-gradient(45deg, ${bgStripeColor}, ${bgStripeColor} 1px, ${bgColor} 1px, ${bgColor} 10px)`

if (dotElement) {
dotElement.style.borderWidth = '2px'
backgroundStyling = `repeating-linear-gradient(45deg, ${bgColor}, ${bgColor} 1px, transparent 1px, transparent 3.5px)`

dotElement.style.background = backgroundStyling
dotElement.style.minWidth = '10px'
dotElement.style.minHeight = '10px'
} else {
el.style.background = backgroundStyling
}

el.title = t('calendar', 'Your participation is tentative')
}
}

/**
* Create a slightly darker color for background stripes
*
* @param {string} color The color to darken
*/
function darkenColor(color) {
const rgb = color.match(/\d+/g)
if (!rgb) return color
const [r, g, b] = rgb.map(c => Math.max(0, Math.min(255, c - (c * 0.3))))
return `rgb(${r}, ${g}, ${b})`
}

0 comments on commit ccb02fe

Please sign in to comment.