diff --git a/event/static/css/calendar.css b/event/static/css/calendar.css new file mode 100644 index 0000000..fe2443e --- /dev/null +++ b/event/static/css/calendar.css @@ -0,0 +1,359 @@ +/* Calendar */ + +#cal-container { + width: 100%; + height: 100%; + padding: 0; + + font-family: "sans-serif"; +} + +#cal-container, +#cal-container * { + box-sizing: border-box; +} + + + +/* Time slots */ + +#cal-container .cal-time-slot-container { + display: grid; + /* grid-template-columns: repeat(24, 1fr); */ + grid-template-rows: 30px auto; + + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 0; + z-index: 10; +} + +#cal-container .cal-time-slot { + border-right: 1px solid #EEE; + background-color: #FAFAFA; +} + +/* #cal-container .cal-time-slot:hover { + background-color: red; +} */ + +#cal-container .cal-time-slot:nth-child(even) { + background-color: #F4F4F4; +} + +/* #cal-container .cal-time-slot:nth-child(even):hover { + background-color: #D9D9D9; +} */ + +#cal-container .cal-time-slot:last-child { + border-right: 0; +} + +#cal-container .cal-time-slot-hour { + padding: 0 0 0 calc(100% - 0.9rem + 4px); + background-color: #F9F9F9;/* #3D3D3D; */ + border-bottom: 1px solid #AAA; + color: #333; + font-size: 0.9rem; + line-height: 30px; + vertical-align: middle; +} + +#cal-container .cal-time-slot-hour:nth-child(even) { + background-color: #FCFCFC;/* #464646; */ +} + +#cal-container .cal-time-slot-hour:last-child { + border-right: 0; +} + +#cal-container .cal-time-slot.cal-new-day, +#cal-container .cal-time-slot-hour.cal-new-day { + border-left: 2px solid #AAA; +} + +/* Events */ + +#cal-container .cal-event-container { + display: grid; + /* grid-template-columns: repeat(24, 1fr); */ + grid-template-rows: repeat(12, auto); + + position: absolute; + top: 40px; + left: 0; + width: 100%; + height: 100%; + padding: 0; + z-index: 100; +} + +#cal-container .cal-event { + position: relative; + margin: 2px 0; + /* padding: 5px; */ + /* background-color: #EFEFEF; */ + border-radius: 3px; + /* border: 1px solid #CCC; */ + border-width: 1px; + border-style: solid; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + /* z-index: 500; */ + transition: 50ms ease-in; +} + +#cal-container .cal-event:hover { + /* background-color: #FEDDDD; */ +} + +#cal-container .cal-event > * { + display: none; + margin: 5px; +} + +#cal-container .cal-event > .cal-event-name, +#cal-container .cal-event > .cal-event-location, +#cal-container .cal-event > .cal-event-perm-count { + display: block; +} + +#cal-container .cal-event > .cal-event-name { + font-weight: bold; +} + +#cal-container .cal-event > .cal-event-location { + font-style: italic; +} + +#cal-container .cal-event > .cal-event-perm-count { + position: absolute; + bottom: 0; + right: 0; +} + +#cal-container .cal-event:not(.cal-event-subscribed) > .cal-event-perm-count.cal-perms-missing { + width: calc(100% - 10px); + right: auto; + margin: 5px; + padding: 5px; + background-color: #FFF; + border: 2px solid #E44; + color: #E44; + font-weight: bold; + border-radius: 3px; + overflow: hidden; +} + +#cal-container .cal-event.cal-event-subscribed { + border-width: 3px; + border-color: #000; +} + +#cal-container .cal-event.cal-event-subscribed::after { + content: "✔"; + position: absolute; + left: 0; + bottom: 0; + width: 16px; + height: 16px; + padding: 1px; + color: #fff; + background-color: #000; + border-top-right-radius: 3px; +} + + +/* Event details popup */ + +#cal-container .cal-event-details { + position: absolute; + min-height: 100px; + min-width: 25%; + max-width: 60%; + padding: 20px; + background-color: #333; + color: #FFF; + border-radius: 4px; + box-shadow: 0 15px 50px rgba(0, 0, 0, 0.6); + z-index: 1000; +} + +#cal-container .cal-event-details:after { + bottom: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-bottom-color: #333; + border-width: 20px; + margin-left: -20px; +} + +#cal-container .cal-event-details.above-event:after { + top: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-top-color: #333; + border-width: 20px; + margin-left: -20px; +} + +#cal-container .cal-event-details * { + z-index: 1000; +} + +#cal-container .cal-event-details .cal-detail-close-button { + width: 35px; + height: 35px; + position: absolute; + top: 10px; + right: 10px; + margin: 0; + padding: 5px; + background: transparent; + border: none; + border-radius: 50%; + font-size: 1.2rem; + color: #BBB; + transition: 100ms ease-out; +} + +#cal-container .cal-event-details .cal-detail-close-button:hover { + background-color: #484848; + color: #EFEFEF; +} + +#cal-container .cal-event-details a, +#cal-container .cal-event-details a:hover { + color: #FFF; + text-decoration: none; +} + +#cal-container .cal-event-details .cal-detail-name > h3 { + margin: 0 0 25px 0; + padding: 10px; + border-radius: 4px; + color: #FFF; + text-transform: uppercase; + text-align: center; +} + +#cal-container .cal-event-details .cal-detail-name > h3::after { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin: 0 0 0 8px; + vertical-align: middle; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAA90lEQVQoz2WRsS6DURiGn/yaaNqBySpGicFg6iJcAYuuNBaJpJEg6gLcADFIdWR0BZZKS4SBRTe3YFf/Y/j/c3LUe4Zz3vd78+U734uUJ7Ptg7k68NDpoIdyU9V3TzzwRdXt1HCq9pwR510W616oZ8Gwoe6KWFN1ScStogui3sRJ9uxYLd/n6hTuaCy3XHfNWuR6hM+OojBQdSXyJ0cZizwSsMo3kEc+ZCEjxZgxE8j4oBFZhRww8gaff4fEL3UuGfK4uG5L4VLVfvrNCrDJHfd0gSrXQB2AJvu0+Cm8HbXnbGw9seqwWH2z65Wv/8MKcfdVHaZx/wKtOg5kifzQhwAAAABJRU5ErkJggg==); + opacity: 0.6; +} + +#cal-container .cal-event-details .cal-detail-name:hover > h3 { + margin-bottom: 5px; + background-color: #484848; + color: #FFF; +} + +#cal-container .cal-event-details .cal-detail-name:hover > h3::after { + content: "Cliquez pour afficher les détails."; + display: block; + width: auto; + height: 15px; + padding: 5px 0 0 0; + font-size: 0.6rem; + background-image: none; + color: #DDD; +} + +#cal-container .cal-event-details .cal-detail-name:hover + .cal-detail-close-button { + opacity: 0; +} + +#cal-container .cal-event-details table { + width: 100%; +} + +#cal-container .cal-event-details td.cal-detail-label { + padding: 0 10px 10px 0; + font-weight: bold; + text-align: right; +} + +#cal-container .cal-event-details td.cal-detail-value { + padding: 0 0 10px 10px; + text-align: left; +} + +#cal-container .cal-event-details .cal-detail-perm-area { + margin: 10px 0; + padding: 10px; + background-color: #DFDFDF; + color: #333; + text-align: center; + border-radius: 4px; +} + +#cal-container .cal-event-details .cal-detail-perm-title { + margin: 0 0 10px 0; +} + +#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count { + margin: 0 10px 0 0; + font-size: 1.7rem; + vertical-align: middle; +} + +#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count.cal-perms-missing { + color: #E44; +} + +#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count.cal-perms-full { + color: #393; +} + + +#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-subscription-switch { + margin: 0 0 0 10px; + padding: 10px; + font-size: 1.35rem; + vertical-align: middle; +} + +#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-nb-missing-perms { + margin: 20px 0 0 0; + padding: 5px; + background-color: #FFF; + border-radius: 4px; + text-align: center; + font-size: 1.1rem; + color: #E44; + font-weight: bold; +} + +#cal-container .cal-event-details .cal-detail-description { + color: #DDD; + font-style: italic; + text-align: justify; +} + +#cal-container .cal-event-details .cal-detail-tag { + display: inline-block; + margin: 0 5px; + padding: 5px; + border: 1px solid #DDD; +} diff --git a/event/static/js/calendar.js b/event/static/js/calendar.js new file mode 100644 index 0000000..03c15dc --- /dev/null +++ b/event/static/js/calendar.js @@ -0,0 +1,914 @@ +// Based on https://stackoverflow.com/a/15289883 +function computeDateDifferenceInHours (date1, date2) { + const msPerHour = 60 * 60 * 1000; + return Math.floor(Math.abs(date2.getTime() - date1.getTime()) / msPerHour); +} + + + +class Calendar { + + constructor (calendarParameters = {}) { + this.containerNode = calendarParameters.containerNode !== undefined + ? calendarParameters.containerNode + : $("#cal-container"); + + this.eventContainerNode = null; + this.timeSlotsContainerNode = null; + this.eventDetailsContainerNode = null; + + this.startDate = calendarParameters.startDate !== undefined + ? calendarParameters.startDate + : new Date(); + this.endDate = calendarParameters.endDate !== undefined + ? calendarParameters.endDate + : new Date(Date.now() + (24 * 60 * 60 * 1000)); + + this.nbHoursToDisplay = 0; + this.firstHourToDisplay = 0; + this.endHourToDisplay = 0; + + this.events = []; + + this.subscriptionURLFormat = calendarParameters.subscriptionURLFormat !== undefined + ? calendarParameters.subscriptionURLFormat + : ""; + this.csrfToken = calendarParameters.csrfToken !== undefined + ? calendarParameters.csrfToken + : ""; + + // Map from locations to their CSS styles + this.locationStyles = new Map(); + + this.init(); + } + + init () { + this.updateHoursToDisplay(); + + this.createTimeSlotContainer(); + this.createEventContainer(); + this.createEventDetailsContainer(); + + this.updateEventContainerGridStyle(); + + this.createTimeSlots(); + this.createEvents(); + + this.createLocationStyles(); + this.applyLocationStylesAsCSS(); + this.updateEventLocationStyleID(); + + this.sortEventNodesByEndTimeAndLocation(); + } + + + // Date change + + setStartDate (newStartDate) { + this.startDate = newStartDate; + + this.updateHoursToDisplay(); + this.updateEventContainerGridStyle(); + this.updateTimeSlots(); + this.updateEventVisibilities(); + } + + setEndDate (newEndDate) { + this.endDate = newEndDate; + + this.updateHoursToDisplay(); + this.updateEventContainerGridStyle(); + this.updateTimeSlots(); + this.updateEventVisibilities(); + } + + updateHoursToDisplay () { + this.startHourToDisplay = this.startDate.getHours(); + this.endHourToDisplay = this.endDate.getHours(); + + this.nbHoursToDisplay = computeDateDifferenceInHours(this.startDate, this.endDate); + } + + + // Time slots + + createTimeSlotContainer () { + this.timeSlotsContainerNode = $("