Ajout des velov et des derniers passages
This commit is contained in:
@@ -0,0 +1,205 @@
|
|||||||
|
const TEMPLATE = document.createElement("template");
|
||||||
|
TEMPLATE.innerHTML = `<style>
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
--markee-speed: 100;
|
||||||
|
--markee-play-state: running;
|
||||||
|
--markee-interaction-pause-delay: 3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.host::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::slotted(*) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
</style><slot></slot>`;
|
||||||
|
|
||||||
|
const CHECK_OPTIONS_INTERVAL = 100;
|
||||||
|
|
||||||
|
class MarkeeComponent extends HTMLElement {
|
||||||
|
#options = {
|
||||||
|
speed: 0,
|
||||||
|
scrollPauseDelay: 1000,
|
||||||
|
playing: true,
|
||||||
|
scrollPlaying: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
#lastFrame = 0;
|
||||||
|
|
||||||
|
#animationFrameRequest = -1;
|
||||||
|
|
||||||
|
#checkOptionInterval = -1;
|
||||||
|
|
||||||
|
#scrollPlayingTimeout = -1;
|
||||||
|
|
||||||
|
/** @type {{element: Element, rect: DOMRect, margin: number}?} */
|
||||||
|
#nextCollectedElement = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.requestTick = this.requestTick.bind(this);
|
||||||
|
this.updateOptions = this.updateOptions.bind(this);
|
||||||
|
|
||||||
|
this.attachShadow({ mode: "open" });
|
||||||
|
this.shadowRoot.appendChild(TEMPLATE.content.cloneNode(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
let now = Date.now();
|
||||||
|
let delta = (now - this.#lastFrame) / 1000;
|
||||||
|
this.#lastFrame = now;
|
||||||
|
|
||||||
|
if (!this.#options.playing || !this.#options.scrollPlaying || this.scrollWidth - 800 <= this.clientWidth) return;
|
||||||
|
|
||||||
|
this.scrollLeft += Math.max(this.#options.speed * delta, 1);
|
||||||
|
|
||||||
|
this.checkElementRecycling();
|
||||||
|
}
|
||||||
|
|
||||||
|
requestTick() {
|
||||||
|
this.tick();
|
||||||
|
if (this.#options.playing) {
|
||||||
|
this.#animationFrameRequest = requestAnimationFrame(this.requestTick, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOptions() {
|
||||||
|
let style = window.getComputedStyle(this);
|
||||||
|
|
||||||
|
let speed = parseFloat(style.getPropertyValue("--markee-speed"));
|
||||||
|
if (!Number.isNaN(speed)) {
|
||||||
|
this.#options.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wasPlaying = this.#options.playing;
|
||||||
|
|
||||||
|
let playState = style.getPropertyValue("--markee-play-state");
|
||||||
|
if (playState) {
|
||||||
|
this.#options.playing = playState != "paused";
|
||||||
|
} else {
|
||||||
|
this.#options.playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scrollPauseDelay = style.getPropertyValue(
|
||||||
|
"--markee-interaction-pause-delay"
|
||||||
|
);
|
||||||
|
if (scrollPauseDelay) {
|
||||||
|
let value = parseFloat(scrollPauseDelay);
|
||||||
|
if (scrollPauseDelay.endsWith("ms")) {
|
||||||
|
this.#options.scrollPauseDelay = value;
|
||||||
|
} else if (scrollPauseDelay.endsWith("s")) {
|
||||||
|
this.#options.scrollPauseDelay = value * 1000;
|
||||||
|
} else {
|
||||||
|
this.#options.scrollPauseDelay = 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasPlaying != this.#options.playing) {
|
||||||
|
this.#lastFrame = Date.now();
|
||||||
|
cancelAnimationFrame(this.#animationFrameRequest);
|
||||||
|
this.requestTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkElementRecycling() {
|
||||||
|
if (
|
||||||
|
this.children.length > 0 &&
|
||||||
|
(!this.#nextCollectedElement ||
|
||||||
|
this.#nextCollectedElement.element != this.children[0])
|
||||||
|
) {
|
||||||
|
let element = this.children[0];
|
||||||
|
{
|
||||||
|
let i = 0;
|
||||||
|
while(element.style.display == "none" && i < this.children.length){
|
||||||
|
i += 1;
|
||||||
|
element = this.children[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let style = window.getComputedStyle(element);
|
||||||
|
|
||||||
|
this.#nextCollectedElement = {
|
||||||
|
element,
|
||||||
|
rect: element.getBoundingClientRect(),
|
||||||
|
margin: parseFloat(style.marginLeft) + parseFloat(style.marginRight),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.#nextCollectedElement) return;
|
||||||
|
|
||||||
|
let lastElement = this.children[this.children.length-1];
|
||||||
|
{
|
||||||
|
let i = lastElement.children.length-1;
|
||||||
|
while(lastElement && lastElement.style.display == "none" && i >= 0){
|
||||||
|
i -= 1;
|
||||||
|
lastElement = this.children[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (lastElement) {
|
||||||
|
let rect = lastElement.getBoundingClientRect();
|
||||||
|
let thisRect = this.getBoundingClientRect();
|
||||||
|
let style = window.getComputedStyle(lastElement);
|
||||||
|
let margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight);
|
||||||
|
|
||||||
|
if (thisRect.width == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rect.right + margin < thisRect.left + this.clientWidth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.scrollLeft >=
|
||||||
|
this.scrollWidth -
|
||||||
|
this.clientWidth -
|
||||||
|
this.#nextCollectedElement.rect.width -
|
||||||
|
this.#nextCollectedElement.margin
|
||||||
|
) {
|
||||||
|
this.scrollLeft -=
|
||||||
|
this.#nextCollectedElement.rect.width +
|
||||||
|
this.#nextCollectedElement.margin;
|
||||||
|
this.appendChild(this.#nextCollectedElement.element);
|
||||||
|
this.#nextCollectedElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePointer(e) {
|
||||||
|
this.#options.scrollPlaying = false;
|
||||||
|
clearTimeout(this.#scrollPlayingTimeout);
|
||||||
|
this.#scrollPlayingTimeout = setTimeout(() => {
|
||||||
|
this.#options.scrollPlaying = true;
|
||||||
|
}, this.#options.scrollPauseDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.updateOptions();
|
||||||
|
this.#lastFrame = Date.now();
|
||||||
|
this.requestTick();
|
||||||
|
this.#checkOptionInterval = setInterval(
|
||||||
|
this.updateOptions,
|
||||||
|
CHECK_OPTIONS_INTERVAL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
clearInterval(this.#checkOptionInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("gavle-marquee", MarkeeComponent);
|
||||||
+125
-2
@@ -1,7 +1,27 @@
|
|||||||
|
import { getNextPassage } from "../tcl.js";
|
||||||
|
|
||||||
|
const LAST_PASSAGE_WARNING_THREASHOLD_MS = 30*60*1000;
|
||||||
|
|
||||||
const TEMPLATE = document.createElement("template");
|
const TEMPLATE = document.createElement("template");
|
||||||
|
TEMPLATE.innerHTML = `
|
||||||
|
<img class="line-picto" src="" />
|
||||||
|
<h1>
|
||||||
|
<span>vers <span class="line-name"></span></span>
|
||||||
|
<small class="stop-name"></small>
|
||||||
|
</h1>
|
||||||
|
<p class="next-passage">
|
||||||
|
Prochain passage <span class="stop-passage-time-relative"></span>
|
||||||
|
(<time class="stop-passage-time"></time>)
|
||||||
|
</p>
|
||||||
|
<p class="last-passage">
|
||||||
|
<span class="next-and-last" >Prochain et </span>Dernier passage <span class="last-passage-time-relative"></span>
|
||||||
|
(<time class="last-passage-time"></time>)
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
|
||||||
class NextBusElement extends HTMLElement {
|
class NextBusElement extends HTMLElement {
|
||||||
#stop
|
#stop
|
||||||
|
#autoupdateTimeout
|
||||||
|
|
||||||
get stopId(){
|
get stopId(){
|
||||||
return this.#stop
|
return this.#stop
|
||||||
@@ -9,15 +29,118 @@ class NextBusElement extends HTMLElement {
|
|||||||
|
|
||||||
set stopId(value){
|
set stopId(value){
|
||||||
this.#stop = value
|
this.#stop = value
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAutoUpdate(){
|
||||||
|
clearTimeout(this.#autoupdateTimeout)
|
||||||
this.updateContent()
|
this.updateContent()
|
||||||
|
this.#autoupdateTimeout = setTimeout(this.handleAutoUpdate.bind(this), 60000 + ((Math.random() * 10000) - 5000))
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback(){
|
connectedCallback(){
|
||||||
this.replaceChildren(TEMPLATE.content.cloneNode(true))
|
this.replaceChildren(TEMPLATE.content.cloneNode(true))
|
||||||
|
this.handleAutoUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateContent(){
|
async updateContent(){
|
||||||
|
try {
|
||||||
|
let nextPassage = await getNextPassage(this.stopId)
|
||||||
|
|
||||||
|
let time = nextPassage.time
|
||||||
|
|
||||||
|
if(!time){
|
||||||
|
time = nextPassage.line.timetable.find(it => it.time.getTime() > Date.now())?.time
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!time){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.querySelector(".line-picto").src = nextPassage.line.picto
|
||||||
|
this.querySelector(".line-name").textContent = nextPassage.line.displayName
|
||||||
|
this.querySelector(".stop-name").textContent = nextPassage.displayName
|
||||||
|
|
||||||
|
let timeFormatter = new Intl.DateTimeFormat("fr-FR", {
|
||||||
|
timeStyle: "short"
|
||||||
|
});
|
||||||
|
let timeEl = this.querySelector(".stop-passage-time")
|
||||||
|
timeEl.textContent = timeFormatter.format(time)
|
||||||
|
|
||||||
|
let relativeTimeFOrmatter = new Intl.RelativeTimeFormat("fr-FR", {
|
||||||
|
style: "short",
|
||||||
|
numeric: "auto"
|
||||||
|
});
|
||||||
|
|
||||||
|
let time_minutes = Math.floor((time.getTime() - Date.now()) / 60000)
|
||||||
|
|
||||||
|
let relative_time_str;
|
||||||
|
if(time_minutes > 60) {
|
||||||
|
let time_hours = Math.floor(time_minutes / 60)
|
||||||
|
time_minutes -= time_hours * 60
|
||||||
|
|
||||||
|
relative_time_str = relativeTimeFOrmatter.format(
|
||||||
|
time_hours,
|
||||||
|
"hours"
|
||||||
|
)
|
||||||
|
if(time_minutes > 0){
|
||||||
|
relative_time_str += ` et ${Math.abs(time_minutes)} min`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
relative_time_str = relativeTimeFOrmatter.format(
|
||||||
|
time_minutes,
|
||||||
|
"minutes"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.querySelector(".stop-passage-time-relative").textContent = relative_time_str
|
||||||
|
|
||||||
|
{
|
||||||
|
let last_passage = nextPassage.getLastPassageTime()
|
||||||
|
if(last_passage){
|
||||||
|
let time = last_passage.time
|
||||||
|
let timeEl = this.querySelector(".last-passage-time")
|
||||||
|
timeEl.textContent = timeFormatter.format(time)
|
||||||
|
|
||||||
|
let time_minutes = Math.floor((time.getTime() - Date.now()) / 60000)
|
||||||
|
|
||||||
|
let relative_time_str;
|
||||||
|
if(time_minutes > 60) {
|
||||||
|
let time_hours = Math.floor(time_minutes / 60)
|
||||||
|
time_minutes -= time_hours * 60
|
||||||
|
|
||||||
|
relative_time_str = relativeTimeFOrmatter.format(
|
||||||
|
time_hours,
|
||||||
|
"hours"
|
||||||
|
)
|
||||||
|
if(time_minutes > 0){
|
||||||
|
relative_time_str += ` et ${Math.abs(time_minutes)} min`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
relative_time_str = relativeTimeFOrmatter.format(
|
||||||
|
time_minutes,
|
||||||
|
"minutes"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.querySelector(".last-passage-time-relative").textContent = relative_time_str
|
||||||
|
this.querySelector(".last-passage").style.display = ""
|
||||||
|
this.querySelector(".last-passage").classList.toggle("warning", (time.getTime() - Date.now()) <= LAST_PASSAGE_WARNING_THREASHOLD_MS)
|
||||||
|
} else {
|
||||||
|
this.querySelector(".last-passage").style.display = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(last_passage && last_passage.time.getTime() == time.getTime()){
|
||||||
|
this.querySelector(".next-and-last").style.display = ""
|
||||||
|
this.querySelector(".next-passage").style.display = "none"
|
||||||
|
} else {
|
||||||
|
this.querySelector(".next-and-last").style.display = "none"
|
||||||
|
this.querySelector(".next-passage").style.display = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.style.display = ""
|
||||||
|
} catch(e){
|
||||||
|
this.style.display = "none"
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static observedAttributes = ["stop-id"]
|
static observedAttributes = ["stop-id"]
|
||||||
@@ -31,4 +154,4 @@ class NextBusElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("camp-next-bus", NextBusElement);
|
customElements.define("gavle-next-bus", NextBusElement);
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { getNextPassage, getVelovBikeState } from "../tcl.js";
|
||||||
|
|
||||||
|
const TEMPLATE = document.createElement("template");
|
||||||
|
TEMPLATE.innerHTML = `
|
||||||
|
<img class="velov-picto" src="/api/valkyrie/assets/sprites/bike-stations.svg?type=image%2Fsvg%2Bxml" />
|
||||||
|
<h1 class="station-name"></h1>
|
||||||
|
<p><span class="bike-available"></span> vélo<span class="plurial">s</span> disponible<span class="plurial">s</span></p>
|
||||||
|
`
|
||||||
|
|
||||||
|
class VelovStationElement extends HTMLElement {
|
||||||
|
#station
|
||||||
|
#autoupdateTimeout
|
||||||
|
|
||||||
|
get stationId(){
|
||||||
|
return this.#station
|
||||||
|
}
|
||||||
|
|
||||||
|
set stationId(value){
|
||||||
|
this.#station = value
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAutoUpdate(){
|
||||||
|
clearTimeout(this.#autoupdateTimeout)
|
||||||
|
this.updateContent()
|
||||||
|
this.#autoupdateTimeout = setTimeout(this.handleAutoUpdate.bind(this), 60000 + ((Math.random() * 10000) - 5000))
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback(){
|
||||||
|
this.replaceChildren(TEMPLATE.content.cloneNode(true))
|
||||||
|
this.handleAutoUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateContent(){
|
||||||
|
try {
|
||||||
|
if(!this.stationId)
|
||||||
|
throw new Error("Missing station id")
|
||||||
|
|
||||||
|
let station_data = await getVelovBikeState(this.stationId)
|
||||||
|
|
||||||
|
this.querySelector(".station-name").textContent = station_data.name
|
||||||
|
this.querySelector(".bike-available").textContent = station_data.properties.available_bikes
|
||||||
|
this.querySelector(".bike-available").parentElement.classList.toggle("plurial-content", station_data.properties.available_bikes > 1);
|
||||||
|
|
||||||
|
this.style.display = ""
|
||||||
|
} catch(e){
|
||||||
|
this.style.display = "none"
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static observedAttributes = ["station-id"]
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldVal, newVal){
|
||||||
|
switch(name){
|
||||||
|
case "station-id":
|
||||||
|
this.stationId = newVal
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("gavle-velov-station", VelovStationElement);
|
||||||
+182
-112
@@ -64,83 +64,48 @@
|
|||||||
mix-blend-mode: darken;
|
mix-blend-mode: darken;
|
||||||
}
|
}
|
||||||
|
|
||||||
#next-line {
|
#next-bus-ribbon {
|
||||||
font-family: monospace;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 15px;
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
grid-template-columns: min-content 1fr;
|
|
||||||
grid-template-rows: min-content 1fr;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 50px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--tcl-red);
|
}
|
||||||
border: none;
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
transform: translateY(0);
|
gavle-marquee {
|
||||||
transition: ease-out 0.8s transform;
|
--markee-speed: 20;
|
||||||
|
--markee-interaction-pause-delay: 1s;
|
||||||
|
|
||||||
|
width: 100vw;
|
||||||
|
|
||||||
|
& > gavle-next-bus {
|
||||||
|
max-width: 75vw;
|
||||||
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
grid-column: 2;
|
padding-left: 1.5em;
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > h1 {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > img:first-child {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1;
|
|
||||||
height: 1.3em;
|
|
||||||
align-self: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#next-line[hidden] {
|
gavle-next-bus {
|
||||||
transform: translateY(-100%);
|
|
||||||
transition-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
#last-passage {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 15px;
|
|
||||||
right: 15px;
|
|
||||||
max-width: calc(100% - 30px);
|
|
||||||
max-height: calc(100% - 30px);
|
|
||||||
|
|
||||||
font-size: 20px;
|
|
||||||
font-family: monospace;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
background: var(--tcl-red);
|
|
||||||
padding: 15px;
|
|
||||||
margin: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .last-passage {
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 1.5em;
|
||||||
|
|
||||||
|
grid-template-columns: min-content 1fr;
|
||||||
|
grid-template-rows: 1fr min-content;
|
||||||
|
column-gap: 0.75em;
|
||||||
|
row-gap: 5px;
|
||||||
|
|
||||||
|
& > :not(img) {
|
||||||
|
mix-blend-mode: exclusion;
|
||||||
|
}
|
||||||
|
|
||||||
&[hidden] {
|
&[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
grid-template-columns: min-content 1fr;
|
|
||||||
grid-template-rows: 1fr min-content;
|
|
||||||
column-gap: 10px;
|
|
||||||
row-gap: 5px;
|
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -159,40 +124,105 @@
|
|||||||
& h1 small {
|
& h1 small {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
display: inline-block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
& img {
|
& img {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
max-width: 3em;
|
max-width: 3em;
|
||||||
|
margin-top: -5px;
|
||||||
|
|
||||||
|
--outline-size: 1px;
|
||||||
|
--outline-color: white;
|
||||||
|
filter:
|
||||||
|
drop-shadow(0px var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) 0px 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(0px calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) 0px 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-passage.warning {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
--outline-size: 1px;
|
||||||
|
--outline-color:#e2001a;
|
||||||
|
filter:
|
||||||
|
drop-shadow(0px var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) 0px 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(0px calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) 0px 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
;
|
||||||
|
|
||||||
|
text-transform: uppercase;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ul, ol {
|
gavle-velov-station {
|
||||||
padding-left: calc(15px + 2em);
|
display: grid;
|
||||||
margin: 0.75em 0;
|
color: white;
|
||||||
padding-left: 1em;
|
font-family: sans-serif;
|
||||||
|
font-size: 1.5em;
|
||||||
|
|
||||||
& > li:not(:last-child) {
|
grid-template-columns: min-content 1fr;
|
||||||
margin-bottom: 0.75em;
|
grid-template-rows: 1fr min-content;
|
||||||
}
|
column-gap: 0.75em;
|
||||||
}
|
row-gap: 5px;
|
||||||
|
|
||||||
|
& > img {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1 / 3;
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
#next-line:not([hidden]) ~ #last-passage {
|
& h1 {
|
||||||
color: rgba(255, 255, 255, 0.5);
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding-right: 3em;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
& img {
|
& img {
|
||||||
opacity: 0.5
|
height: 1.3em;
|
||||||
}
|
max-width: 3em;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
--outline-size: 1px;
|
||||||
|
--outline-color: white;
|
||||||
|
filter:
|
||||||
|
drop-shadow(0px var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) 0px 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(0px calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) 0px 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
drop-shadow(calc(var(--outline-size) * -1) var(--outline-size) 0px var(--outline-color))
|
||||||
|
drop-shadow(var(--outline-size) calc(var(--outline-size) * -1) 0px var(--outline-color))
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
#next-line ~ #last-passage {
|
& p {
|
||||||
transition: color 0.5s steps(5);
|
font-size: 0.8em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
& img {
|
& > :not(img) {
|
||||||
transition: opacity 0.5s steps(5);
|
mix-blend-mode: exclusion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +238,7 @@
|
|||||||
font-family: "bianzhidai nobg pearl";
|
font-family: "bianzhidai nobg pearl";
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
left: 15px;
|
right: 15px;
|
||||||
font-size: 17vh;
|
font-size: 17vh;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -219,43 +249,83 @@
|
|||||||
animation: deux-points steps(4) 1s infinite;
|
animation: deux-points steps(4) 1s infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plurial {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plurial-content .plurial {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<iframe id="gavle-tv" src="./liquid-shape-distortions/index.html" frameborder="0"></iframe>
|
<iframe id="gavle-tv" src="./liquid-shape-distortions/index.html" frameborder="0"></iframe>
|
||||||
<script src="index.js" type="module"></script>
|
|
||||||
<div id="next-line" hidden>
|
<script type="module" src="./components/marquee.js"></script>
|
||||||
<img class="line-picto" src="" />
|
<script type="module" src="./components/next-bus.js"></script>
|
||||||
<h1 class="line-name"></h1>
|
<script type="module" src="./components/velov-station.js"></script>
|
||||||
<p>
|
<script type="module">
|
||||||
<span class="stop-name"></span>:
|
let shuffledElements = Array.from(document.getElementById("next-bus-ribbon").children)
|
||||||
prochain départ <span class="stop-passage-time-relative"></span>
|
.map(value => ({ value, sort: Math.random() }))
|
||||||
(<time class="stop-passage-time"></time>)
|
.sort((a, b) => a.sort - b.sort)
|
||||||
</p>
|
.map(({ value }) => value)
|
||||||
</div>
|
|
||||||
<div id="last-passage" style="width: 100%;">
|
document.getElementById("next-bus-ribbon").replaceChildren(...shuffledElements)
|
||||||
<section style="max-width: 350px;">
|
</script>
|
||||||
Quelques petites choses a faire avant de partir :
|
<gavle-marquee id="next-bus-ribbon">
|
||||||
<ul>
|
<gavle-next-bus stop-id="line:SYTNEX:A/forward/stop_point:SYTNEX:46052">
|
||||||
<li><strong>Ranger</strong> les établis et les machines</li>
|
<!-- Metro A dir Vaulx, Hotel de ville -->
|
||||||
<li>Faire un petit coup de <strong>vaisselle</strong></li>
|
</gavle-next-bus>
|
||||||
</ul>
|
<gavle-next-bus stop-id="line:SYTNEX:A/backward/stop_point:SYTNEX:42743">
|
||||||
Bonne nuit <strong>:)</strong>
|
<!-- Metro A dir Perrache, Hotel de ville -->
|
||||||
</section>
|
</gavle-next-bus>
|
||||||
<template class="last-passage-template">
|
<gavle-next-bus stop-id="line:SYTNEX:9/forward/stop_point:SYTNEX:2494">
|
||||||
<article class="last-passage">
|
<!-- Bus 9 dir Satoney, Pont de lattre RD -->
|
||||||
<img class="line-picto" src="" />
|
</gavle-next-bus>
|
||||||
<h1>
|
<gavle-next-bus stop-id="line:SYTNEX:9/backward/stop_point:SYTNEX:2494">
|
||||||
<span class="line-name" ></span>
|
<!-- Bus 9 dir cordeliers, Pont de lattre RD -->
|
||||||
<small class="stop-name"></small>
|
</gavle-next-bus>
|
||||||
</h1>
|
<gavle-next-bus stop-id="line:SYTNEX:C/forward/stop_point:SYTNEX:10787">
|
||||||
<p>
|
<!-- Métro C dir Cuire, Croix-Paquet -->
|
||||||
Dernier passage dans <span class="stop-passage-time-relative"></span>
|
</gavle-next-bus>
|
||||||
(<time class="stop-passage-time"></time>)
|
<gavle-next-bus stop-id="line:SYTNEX:C6/forward/stop_point:SYTNEX:2496">
|
||||||
</p>
|
<!-- Bus C6 arret Pont de lattre RD campus lyon ouest -->
|
||||||
</article>
|
</gavle-next-bus>
|
||||||
</template>
|
<gavle-next-bus stop-id="line:SYTNEX:C6/backward/stop_point:SYTNEX:2495">
|
||||||
</div>
|
<!-- Bus C6 arret Pont de lattre RD Gare part dieu v. merle -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-next-bus stop-id="line:SYTNEX:C13/forward/stop_point:SYTNEX:10169">
|
||||||
|
<!-- Bus C13 arret Austerlitz montessuy gutemberg -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-next-bus stop-id="line:SYTNEX:C13/backward/stop_point:SYTNEX:10169">
|
||||||
|
<!-- Bus C13 arret Austerlitz vers grange blanche -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-next-bus stop-id="line:SYTNEX:PL2/backward/stop_point:SYTNEX:10889">
|
||||||
|
<!-- Bus PL2 arret Pont de lattre RD vers Musée des confluences -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-next-bus stop-id="line:SYTNEX:PL2/forward/stop_point:SYTNEX:10169">
|
||||||
|
<!-- BUS PL2 arret Austerlitz vers Cuire -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-next-bus stop-id="line:SYTNEX:C23/backward/stop_point:SYTNEX:10889">
|
||||||
|
<!-- Bus C13 arret Pont de lattre RD vers Flachet - Alain Gilles -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-next-bus stop-id="line:SYTNEX:C23/forward/stop_point:SYTNEX:10889">
|
||||||
|
<!-- Bus C13 arret Pont de lattre RD vers Cite internationale -->
|
||||||
|
</gavle-next-bus>
|
||||||
|
<gavle-velov-station station-id="poi:osm:node:7020557391">
|
||||||
|
<!-- Chazette / Lassagne -->
|
||||||
|
</gavle-velov-station>
|
||||||
|
<gavle-velov-station station-id="poi:osm:node:1332627068">
|
||||||
|
<!-- Quai Lassagne -->
|
||||||
|
</gavle-velov-station>
|
||||||
|
<gavle-velov-station station-id="poi:osm:node:1056768413">
|
||||||
|
<!-- Place Tolozan -->
|
||||||
|
</gavle-velov-station>
|
||||||
|
<gavle-velov-station station-id="poi:osm:node:10621667633">
|
||||||
|
<!-- Place d'Helvétie -->
|
||||||
|
</gavle-velov-station>
|
||||||
|
</gavle-marquee>
|
||||||
|
|
||||||
<script type="module" src="./components/clock.js"></script>
|
<script type="module" src="./components/clock.js"></script>
|
||||||
<gavle-clock id="clock"></gavle-clock>
|
<gavle-clock id="clock"></gavle-clock>
|
||||||
|
|||||||
+43
-6
@@ -1,4 +1,5 @@
|
|||||||
const DAY_IN_MS = 8.64e+7;
|
const DAY_IN_MS = 8.64e+7;
|
||||||
|
const MIN_TIME_BETWEEN_DAYS = 2 * 60 * 60000 // 2 hours
|
||||||
|
|
||||||
class StopId {
|
class StopId {
|
||||||
constructor(stopIdString){
|
constructor(stopIdString){
|
||||||
@@ -139,26 +140,62 @@ export async function getNextPassage(stop_description, options = {
|
|||||||
|
|
||||||
let [line, next_trip, line_stops, ...all_timetables] = await Promise.all(proms);
|
let [line, next_trip, line_stops, ...all_timetables] = await Promise.all(proms);
|
||||||
|
|
||||||
console.log(stopId)
|
|
||||||
console.log("line", line)
|
|
||||||
console.log("next trip", next_trip)
|
|
||||||
console.log("stop", line_stops)
|
|
||||||
console.log("timetable", all_timetables)
|
|
||||||
|
|
||||||
let line_timetables = []
|
let line_timetables = []
|
||||||
for(let timetable of all_timetables){
|
for(let timetable of all_timetables){
|
||||||
line_timetables.push(...timetable)
|
line_timetables.push(...timetable)
|
||||||
}
|
}
|
||||||
line.timetable = line_timetables
|
line.timetable = line_timetables
|
||||||
|
line.getLastPassageTime = getLastPassageTime.bind(line)
|
||||||
|
|
||||||
let stop = line_stops.find(it => it.id == stopId.stopId)
|
let stop = line_stops.find(it => it.id == stopId.stopId)
|
||||||
|
|
||||||
|
let time;
|
||||||
|
if(next_trip.schedules){
|
||||||
|
let next_schedule = next_trip.schedules.find(it => (new Date(it.dateTime).getTime() - Date.now()) > 120000 )
|
||||||
|
if(!next_schedule) {
|
||||||
|
next_schedule = next_trip.schedules[next_trip.schedules.length - 1]
|
||||||
|
}
|
||||||
|
if(next_schedule){
|
||||||
|
time = new Date(next_schedule.dateTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let nextPassage = {
|
let nextPassage = {
|
||||||
line,
|
line,
|
||||||
displayName: stop?.name,
|
displayName: stop?.name,
|
||||||
displayTime: "<unsupported>",
|
displayTime: "<unsupported>",
|
||||||
time: next_trip.schedules?.[0] ? new Date(next_trip.schedules?.[0].dateTime) : null
|
time: next_trip.schedules?.[0] ? new Date(next_trip.schedules?.[0].dateTime) : null
|
||||||
}
|
}
|
||||||
|
nextPassage.getLastPassageTime = getLastPassageTime.bind(nextPassage)
|
||||||
|
|
||||||
return nextPassage
|
return nextPassage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getVelovBikeState(station_id){
|
||||||
|
let res = await fetch(`/api/interface/tcl/realtime/navitia/${decodeURIComponent(station_id)}`)
|
||||||
|
if(!res.ok){
|
||||||
|
throw new Error(`Server responded with ${res.status} ${res.statusText}`)
|
||||||
|
}
|
||||||
|
return await res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastPassageTime(){
|
||||||
|
let timetable = this.timetable || this.line.timetable
|
||||||
|
|
||||||
|
if(!timetable){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = new Date()
|
||||||
|
|
||||||
|
let last_passage = timetable
|
||||||
|
.find((it, i) => {
|
||||||
|
if((timetable[i+1]) && it.time.getTime() > now && (it.time.getTime() + MIN_TIME_BETWEEN_DAYS) < timetable[i+1].time.getTime()){
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return last_passage
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user