feat: rework the UI

This commit is contained in:
Raito Bezarius 2022-02-20 22:29:15 +01:00
parent 139d7b40b1
commit 7674dc956a
4 changed files with 115 additions and 27 deletions

35
package-lock.json generated
View file

@ -43,7 +43,6 @@
"version": "5.10.1", "version": "5.10.1",
"resolved": "https://registry.npmjs.org/@fullcalendar/common/-/common-5.10.1.tgz", "resolved": "https://registry.npmjs.org/@fullcalendar/common/-/common-5.10.1.tgz",
"integrity": "sha512-EumKIJcQTvQdTs75/9dmeREFgjcRVWzqHJS1Xvlz5mNsmB+w9EINCHETRjChtAQg1WD/lTQyVj4sHsKO7vCMSw==", "integrity": "sha512-EumKIJcQTvQdTs75/9dmeREFgjcRVWzqHJS1Xvlz5mNsmB+w9EINCHETRjChtAQg1WD/lTQyVj4sHsKO7vCMSw==",
"dev": true,
"requires": { "requires": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
@ -80,6 +79,15 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@fullcalendar/rrule": {
"version": "5.10.1",
"resolved": "https://registry.npmjs.org/@fullcalendar/rrule/-/rrule-5.10.1.tgz",
"integrity": "sha512-K5TO8298eVkZiJ70hZrAvAAP81aTMiymgPW1nnaOkflI4bvEDJlkGdFe1MqgE07oTBZca7VU7/33ePiSTs0Pcg==",
"requires": {
"@fullcalendar/common": "~5.10.1",
"tslib": "^2.1.0"
}
},
"@fullcalendar/timegrid": { "@fullcalendar/timegrid": {
"version": "5.10.1", "version": "5.10.1",
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-5.10.1.tgz", "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-5.10.1.tgz",
@ -1339,6 +1347,12 @@
"yallist": "^4.0.0" "yallist": "^4.0.0"
} }
}, },
"luxon": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
"optional": true
},
"magic-string": { "magic-string": {
"version": "0.25.7", "version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
@ -2209,6 +2223,22 @@
} }
} }
}, },
"rrule": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.8.tgz",
"integrity": "sha512-cUaXuUPrz9d1wdyzHsBfT1hptKlGgABeCINFXFvulEPqh9Np9BnF3C3lrv9uO54IIr8VDb58tsSF3LhsW+4VRw==",
"requires": {
"luxon": "^1.21.3",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"sade": { "sade": {
"version": "1.7.4", "version": "1.7.4",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
@ -2582,8 +2612,7 @@
"tslib": { "tslib": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
"dev": true
}, },
"undici": { "undici": {
"version": "4.14.1", "version": "4.14.1",

View file

@ -27,8 +27,10 @@
"svelte-fullcalendar": "^1.1.1" "svelte-fullcalendar": "^1.1.1"
}, },
"dependencies": { "dependencies": {
"@fullcalendar/rrule": "^5.10.1",
"@nextcloud/cdav-library": "^1.0.0", "@nextcloud/cdav-library": "^1.0.0",
"ical.js": "^1.5.0", "ical.js": "^1.5.0",
"rrule": "^2.6.8",
"sirv-cli": "^1.0.0" "sirv-cli": "^1.0.0"
} }
} }

View file

@ -2,18 +2,26 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import FullCalendar from 'svelte-fullcalendar'; import FullCalendar from 'svelte-fullcalendar';
import timeGridPlugin from '@fullcalendar/timegrid'; import timeGridPlugin from '@fullcalendar/timegrid';
import rrulePlugin from '@fullcalendar/rrule';
import dayGridPlugin from '@fullcalendar/daygrid';
import frLocale from '@fullcalendar/core/locales/fr'; import frLocale from '@fullcalendar/core/locales/fr';
import { refreshEvents } from './calendar'; import { refreshEvents, calendarTree } from './calendar';
let options = { let options = {
initialView: 'timeGridWeek', initialView: 'timeGridWeek',
plugins: [timeGridPlugin], plugins: [timeGridPlugin, dayGridPlugin, rrulePlugin],
locale: frLocale, locale: frLocale,
height: "100%",
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
nowIndicator: true,
now: new Date(),
events: [] events: []
}; };
let selectedCalendars;
onMount(async () => { onMount(async () => {
const evts = await refreshEvents(); const evts = await refreshEvents(selectedCalendars);
options.events = evts.flat(); options.events = evts.flat();
options = { ...options }; options = { ...options };
}); });
@ -21,15 +29,15 @@
$: console.log(options); $: console.log(options);
</script> </script>
<div style="height:100vh"> <div class="app-container">
<h1 class="title">Calendrier de la vie étudiante à l'ENS</h1>
<div style="height: 100%;">
<!-- <FilterBar {calendarTree} {selectedCalendars}> -->
<FullCalendar {options} /> <FullCalendar {options} />
</div> </div>
</div>
<style> <style>
body {
height: 100vh;
}
main { main {
text-align: center; text-align: center;
padding: 1em; padding: 1em;
@ -37,11 +45,14 @@
margin: 0 auto; margin: 0 auto;
} }
h1 { .title {
color: #ff3e00; text-align: center;
text-transform: uppercase; }
font-size: 4em;
font-weight: 100; .app-container {
display: flex;
flex-flow: column;
height: 100%;
} }
@media (min-width: 640px) { @media (min-width: 640px) {

View file

@ -1,9 +1,24 @@
import ICAL from 'ical.js'; import ICAL from 'ical.js';
const calendarIds = [ const calendars = {
"5WrcagPPARQ3BD87", "5WrcagPPARQ3BD87": {
"TFEAKjAgNFQZpNjo" name: "Club réseau",
]; color: null
},
"TFEAKjAgNFQZpNjo": {
name: "hackENS",
color: null
}
};
export const calendarTree = {
"COF": [
"Club réseau",
"hackENS"
]
};
const calendarIds = Object.keys(calendars);
function mkCalendarUrl(id) { function mkCalendarUrl(id) {
return `/cal/${id}/?export&accept=jcal`; return `/cal/${id}/?export&accept=jcal`;
@ -14,8 +29,10 @@ function fetchCalendar(id) {
} }
class Calendar { class Calendar {
constructor (calendar) { constructor (id, calendar) {
this.calName = calendar[1][3][3]; const metadata = calendars[id];
this.name = metadata.name;
this.color = metadata.color || calendar[1][4][3];
this.events = calendar[2].filter(item => item[0] === 'vevent').map(item => this._parse_vevent(item[1])); this.events = calendar[2].filter(item => item[0] === 'vevent').map(item => this._parse_vevent(item[1]));
} }
@ -28,11 +45,39 @@ class Calendar {
} }
} }
function fcEventFromjCalEvent(cal) {
return function (evt) {
const start = new Date(evt.dtstart);
const end = new Date(evt.dtend);
const fcEvent = {
title: `${cal.name}: ${evt.summary}`,
start,
color: cal.color,
duration: end - start // in ms
};
if (evt.description) {
fcEvent.description = evt.description;
}
if (evt.rrule) {
const { freq, byday } = evt.rrule;
fcEvent.rrule = {
freq,
byweekday: byday,
dtstart: evt.dtstart,
};
}
return fcEvent;
}
}
function mkEventsFromCalendar(id) { function mkEventsFromCalendar(id) {
return fetchCalendar(id).then(calendar => { return fetchCalendar(id).then(calendar => {
if (calendar[0] !== 'vcalendar') return; if (calendar[0] !== 'vcalendar') return;
const cal = new Calendar(calendar) const cal = new Calendar(id, calendar)
return cal.events.map(evt => ({ title: evt.summary, start: new Date(evt.dtstart), end: new Date(evt.dtend) })) return cal.events.map(fcEventFromjCalEvent(cal))
}); });
} }
@ -48,6 +93,7 @@ export function mkEvent(title, start, duration, ...rest) {
} }
} }
export function refreshEvents() { // TODO: move to FullCalendar custom fetcher to control start&end.
return Promise.all(calendarIds.map(mkEventsFromCalendar)) export function refreshEvents(selectedCalendars) {
return Promise.all(calendarIds.filter(id => selectedCalendars ? selectedCalendars.includes(id) : true).map(mkEventsFromCalendar))
} }