Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visually distinguish invitations in calendar grid #6624

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 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 @@ -149,6 +150,7 @@

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

// Reminder icon on events with alarms set
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 @@
} 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 @@
* @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 @@
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
81 changes: 79 additions & 2 deletions src/fullcalendar/rendering/eventDidMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
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 @@
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,81 @@
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 = '0.1px'
dotElement.style.background = 'transparent'
dotElement.style.minWidth = '10px'
dotElement.style.minHeight = '10px'
}

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

View workflow job for this annotation

GitHub Actions / NPM lint

Trailing spaces not allowed
titleElement.style.color = 'var(--color-main-text)'
el.style.background = 'transparent'
el.style.borderWidth = '2px'
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 = '1.2em'
svgElement.style.paddingBottom = '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) {
backgroundStyling = `repeating-linear-gradient(45deg, ${bgColor}, ${bgColor} 1px, transparent 1px, transparent 3.5px)`

dotElement.style.borderWidth = '0.1px'
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})`
}
Loading