Ajout de l'appel a l'API pretalx
This commit is contained in:
parent
dc07b1b415
commit
87005adb44
7 changed files with 344 additions and 2 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
body, html {
|
body, html {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -381,13 +382,13 @@ camp-feature .widget > :last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
camp-feature .widget h2 {
|
camp-feature .widget > h2 {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-bottom: 1ex;
|
margin-bottom: 1ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
camp-feature .widget p {
|
camp-feature .widget > p {
|
||||||
margin-top: 1ex;
|
margin-top: 1ex;
|
||||||
margin-bottom: 1ex;
|
margin-bottom: 1ex;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
@ -460,3 +461,68 @@ camp-feature .box-widget h2:first-child,
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
margin-right: 2em;
|
margin-right: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TALKS */
|
||||||
|
|
||||||
|
camp-upcoming-talks > * {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
display: block;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk {
|
||||||
|
display: block;
|
||||||
|
--track-color: white;
|
||||||
|
border: solid 1px var(--track-color);
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-radius: 2.5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk .track-name {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--track-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk > * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk > p:not(:last-child) {
|
||||||
|
margin-top: 1ex;
|
||||||
|
margin-bottom: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk[data-track-id="36"] {
|
||||||
|
--track-color: #33d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk[data-track-id="35"] {
|
||||||
|
--track-color: #ffbf3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk[data-track-id="33"] {
|
||||||
|
--track-color: #ff4e00;
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk[data-track-id="34"] {
|
||||||
|
--track-color: #8800ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes active-talk {
|
||||||
|
from {
|
||||||
|
box-shadow: 0px 0px 0px var(--track-color), 0px 0px 0px var(--track-color);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
box-shadow: 0px 0px 10px var(--track-color), 0px 0px 0px var(--track-color);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0px 0px 15px var(--track-color), 0px 0px 7px var(--track-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
camp-talk.active {
|
||||||
|
animation: active-talk 1.5s alternate-reverse infinite linear;
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
<link rel="stylesheet" href="./css/widgets.css">
|
<link rel="stylesheet" href="./css/widgets.css">
|
||||||
<script type="module" src="./js/index.js"></script>
|
<script type="module" src="./js/index.js"></script>
|
||||||
<script type="module" src="./js/components/bidi-panel.js"></script>
|
<script type="module" src="./js/components/bidi-panel.js"></script>
|
||||||
|
<script type="module" src="./js/components/upcoming-talks.js"></script>
|
||||||
|
<script type="module" src="./js/components/talk.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header id="main-header">
|
<header id="main-header">
|
||||||
|
|
|
||||||
64
js/components/talk.js
Normal file
64
js/components/talk.js
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
const TEMPLATE = document.createElement("template")
|
||||||
|
TEMPLATE.innerHTML = `
|
||||||
|
<div class="talk-calendar">
|
||||||
|
<span class="talk-start-date"></span>
|
||||||
|
<time class="talk-start-time"></time>
|
||||||
|
→
|
||||||
|
<time class="talk-end-time"></time>
|
||||||
|
</div>
|
||||||
|
<h3 class="talk-title"></h3>
|
||||||
|
<p class="talk-abstract"></p>
|
||||||
|
<p><small class="track-name"></small></p>
|
||||||
|
`
|
||||||
|
|
||||||
|
class TalkElement extends HTMLElement {
|
||||||
|
|
||||||
|
#activeChackInterval
|
||||||
|
|
||||||
|
srcObject
|
||||||
|
|
||||||
|
connectedCallback(){
|
||||||
|
this.updateContent()
|
||||||
|
this.#activeChackInterval = setInterval(this.updateActiveState.bind(this), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback(){
|
||||||
|
clearInterval(this.#activeChackInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActiveState(){
|
||||||
|
this.classList.toggle("active", this.srcObject?.isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContent(){
|
||||||
|
if(!this.srcObject){
|
||||||
|
this.replaceChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataset.trackId = this.srcObject.track_id
|
||||||
|
|
||||||
|
this.replaceChildren(TEMPLATE.content.cloneNode(true))
|
||||||
|
this.querySelector(".talk-title").textContent = this.srcObject.title
|
||||||
|
this.querySelector(".talk-abstract").textContent = this.srcObject.abstract
|
||||||
|
this.querySelector(".track-name").textContent = this.srcObject.track.fr
|
||||||
|
|
||||||
|
let timeFormatter = new Intl.DateTimeFormat("fr-FR", {
|
||||||
|
timeStyle: "short"
|
||||||
|
})
|
||||||
|
|
||||||
|
let dateFormatter = new Intl.DateTimeFormat("fr-FR", {
|
||||||
|
dateStyle: "short"
|
||||||
|
})
|
||||||
|
|
||||||
|
this.querySelector(".talk-start-date").textContent = dateFormatter.format(this.srcObject.startTime)
|
||||||
|
this.querySelector(".talk-start-time").textContent = timeFormatter.format(this.srcObject.startTime)
|
||||||
|
this.querySelector(".talk-start-time").datetime = this.srcObject.startTime.toJSON()
|
||||||
|
this.querySelector(".talk-end-time").textContent = timeFormatter.format(this.srcObject.endTime)
|
||||||
|
this.querySelector(".talk-end-time").datetime = this.srcObject.endTime.toJSON()
|
||||||
|
|
||||||
|
this.updateActiveState()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("camp-talk", TalkElement)
|
||||||
90
js/components/upcoming-talks.js
Normal file
90
js/components/upcoming-talks.js
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { getUpcomingTalksForRoom } from "../pretalx.js"
|
||||||
|
|
||||||
|
class UpcomingTalksElement extends HTMLElement {
|
||||||
|
#roomId
|
||||||
|
#loadingPromise
|
||||||
|
|
||||||
|
srcObject = undefined
|
||||||
|
|
||||||
|
get roomId(){
|
||||||
|
return this.#roomId
|
||||||
|
}
|
||||||
|
|
||||||
|
set roomId(val){
|
||||||
|
this.#roomId = val
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback(){
|
||||||
|
this.updateContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContent(){
|
||||||
|
if(!this.roomId){
|
||||||
|
this.replaceChildren()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.srcObject){
|
||||||
|
|
||||||
|
if(!this.#loadingPromise){
|
||||||
|
this.fetchTalks()
|
||||||
|
}
|
||||||
|
|
||||||
|
let loading = document.createElement("div")
|
||||||
|
loading.classList.add("loading")
|
||||||
|
loading.textContent = "Chargement des événements..."
|
||||||
|
this.replaceChildren(loading)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let childrens = []
|
||||||
|
|
||||||
|
for(let talk of this.srcObject){
|
||||||
|
let item = document.createElement("camp-talk")
|
||||||
|
item.srcObject = talk
|
||||||
|
|
||||||
|
let a = document.createElement("a")
|
||||||
|
a.href = talk.displayUrl
|
||||||
|
a.target = "_blank"
|
||||||
|
a.append(item)
|
||||||
|
|
||||||
|
childrens.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classList.toggle("empty", childrens.length == 0)
|
||||||
|
if(childrens.length == 0){
|
||||||
|
let empty = document.createElement("p")
|
||||||
|
empty.classList.add("empty")
|
||||||
|
empty.textContent = `Aucun événement à venir dans cette salle`
|
||||||
|
childrens.push(empty)
|
||||||
|
}
|
||||||
|
this.replaceChildren(...childrens)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classList.toggle("loading", !this.srcObject && this.#loadingPromise)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchTalks(){
|
||||||
|
let prom = (async () => {
|
||||||
|
this.srcObject = await getUpcomingTalksForRoom(this.roomId)
|
||||||
|
this.#loadingPromise = null
|
||||||
|
this.updateContent()
|
||||||
|
})()
|
||||||
|
this.#loadingPromise = prom
|
||||||
|
await prom
|
||||||
|
}
|
||||||
|
|
||||||
|
static observedAttributes = ["room-id"]
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldVal, newVal){
|
||||||
|
switch(name){
|
||||||
|
case "room-id":
|
||||||
|
this.roomId = newVal
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("camp-upcoming-talks", UpcomingTalksElement);
|
||||||
|
|
@ -4,6 +4,7 @@ import { capaciteEspaceWidget } from "./capacite-espace.js"
|
||||||
import { capaciteParkingWidget } from "./capacite-parking.js"
|
import { capaciteParkingWidget } from "./capacite-parking.js"
|
||||||
import { deposeMinuteWidget } from "./depose-minute.js"
|
import { deposeMinuteWidget } from "./depose-minute.js"
|
||||||
import { mixiteChoisieWidget } from "./mixite-choisie.js"
|
import { mixiteChoisieWidget } from "./mixite-choisie.js"
|
||||||
|
import { upcomingTalksWidget } from "./upcoming-talks.js"
|
||||||
import { zoneInterditeWidget } from "./zone-interdite.js"
|
import { zoneInterditeWidget } from "./zone-interdite.js"
|
||||||
|
|
||||||
export const FEATURE_WIDGETS = [
|
export const FEATURE_WIDGETS = [
|
||||||
|
|
@ -13,6 +14,7 @@ export const FEATURE_WIDGETS = [
|
||||||
// Other
|
// Other
|
||||||
deposeMinuteWidget,
|
deposeMinuteWidget,
|
||||||
campingCarsWidget,
|
campingCarsWidget,
|
||||||
|
upcomingTalksWidget,
|
||||||
// Fields
|
// Fields
|
||||||
capaciteParkingWidget,
|
capaciteParkingWidget,
|
||||||
capaciteDortoirWidget,
|
capaciteDortoirWidget,
|
||||||
|
|
|
||||||
17
js/feature-widgets/upcoming-talks.js
Normal file
17
js/feature-widgets/upcoming-talks.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export function upcomingTalksWidget(feature){
|
||||||
|
if(feature.properties["pretalx-room-id"]){
|
||||||
|
let content = document.createElement("div")
|
||||||
|
content.classList.add("widget")
|
||||||
|
content.classList.add("upcoming-talks-widget")
|
||||||
|
|
||||||
|
let h2 = document.createElement("h2")
|
||||||
|
h2.textContent = "Événements à venir"
|
||||||
|
content.append(h2)
|
||||||
|
|
||||||
|
let list = document.createElement("camp-upcoming-talks")
|
||||||
|
list.roomId = feature.properties["pretalx-room-id"]
|
||||||
|
content.append(list)
|
||||||
|
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
}
|
||||||
101
js/pretalx.js
Normal file
101
js/pretalx.js
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
const PRETALX_URL = `https://pretalx.lebib.org/`
|
||||||
|
const EVENT_ID = `camp-interhack-2026-2025`
|
||||||
|
const TALKS_EXPIRATION = 60*60*1000;
|
||||||
|
const API_ROOT = new URL("api/", PRETALX_URL);
|
||||||
|
|
||||||
|
// Database management
|
||||||
|
|
||||||
|
let cached_talks = null;
|
||||||
|
|
||||||
|
export async function getFreshTalks(){
|
||||||
|
if(!cached_talks || (Date.now() - cached_talks.updatedAt.getTime()) > TALKS_EXPIRATION){
|
||||||
|
let talk_list = []
|
||||||
|
|
||||||
|
for await(let talk_json of requestAllTalks()){
|
||||||
|
if(talk_json.state != "confirmed"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let talk = new Talk(talk_json);
|
||||||
|
talk_list.push(talk)
|
||||||
|
}
|
||||||
|
|
||||||
|
talk_list.sort((a,b) => a.startTime.getTime() - b.startTime.getTime())
|
||||||
|
|
||||||
|
// {
|
||||||
|
// let mock_i = 2
|
||||||
|
// talk_list[mock_i].slot.start = new Date(Date.now() - 120*1000).toJSON()
|
||||||
|
// talk_list[mock_i].slot.end = (new Date(Date.now() + 3600000 + ( Math.random()*10000))).toJSON()
|
||||||
|
// console.log("mock event", talk_list[mock_i])
|
||||||
|
// }
|
||||||
|
|
||||||
|
let talks_by_room_id = {}
|
||||||
|
for(let talk of talk_list) {
|
||||||
|
if(!talks_by_room_id[talk.slot.room_id]){
|
||||||
|
talks_by_room_id[talk.slot.room_id] = []
|
||||||
|
}
|
||||||
|
talks_by_room_id[talk.slot.room_id].push(talk)
|
||||||
|
}
|
||||||
|
|
||||||
|
cached_talks = {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
talks: talk_list,
|
||||||
|
talks_by_room: talks_by_room_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cached_talks
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUpcomingTalksForRoom(room_id){
|
||||||
|
let db = await getFreshTalks()
|
||||||
|
return (db.talks_by_room[room_id] || []).filter(it => it.endTime.getTime() > Date.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
class Talk {
|
||||||
|
constructor(srcObj){
|
||||||
|
Object.assign(this, srcObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
get startTime(){
|
||||||
|
return new Date(this.slot.start)
|
||||||
|
}
|
||||||
|
|
||||||
|
get endTime(){
|
||||||
|
return new Date(this.slot.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayUrl(){
|
||||||
|
return new URL(`${encodeURIComponent(EVENT_ID)}/talk/${encodeURIComponent(this.code)}`, PRETALX_URL).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
get isActive(){
|
||||||
|
return this.startTime.getTime() <= Date.now() && this.endTime.getTime() >= Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower level tools
|
||||||
|
|
||||||
|
const requestAllRooms = makePaginatedGenerator(new URL(`events/${encodeURIComponent(EVENT_ID)}/rooms/`, API_ROOT))
|
||||||
|
const requestAllTalks = makePaginatedGenerator(new URL(`events/${encodeURIComponent(EVENT_ID)}/talks/`, API_ROOT))
|
||||||
|
|
||||||
|
function makePaginatedGenerator(baseUrl){
|
||||||
|
return async function*(){
|
||||||
|
let buffer = []
|
||||||
|
let next_url = baseUrl
|
||||||
|
|
||||||
|
while(buffer.length > 0 || next_url != null){
|
||||||
|
let item = buffer.shift()
|
||||||
|
if(item){
|
||||||
|
yield item
|
||||||
|
} else {
|
||||||
|
let res = await fetch(next_url)
|
||||||
|
if(!res.ok){
|
||||||
|
throw new Error(`Server responded with error ${res.status} ${res.statusText}`)
|
||||||
|
}
|
||||||
|
let response = await res.json()
|
||||||
|
next_url = response.next
|
||||||
|
buffer.push(...response.results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue