import { Control, ControlOptions,DomEvent,DomUtil,Map as LeafletMap } from "leaflet";
import { PlayBack } from "../playback/Playback";
import { Timer } from "./Timer";
import {Timeline,DataItem} from 'vis-timeline/standalone';
import {Spinner} from 'spin.js'
const PAUSE_CHAR = '';
const PLAY_FORWARD_CHAR = '';
const PLAY_BACKWARD_CHAR = '';
const STOP_CHAR = '';
const TIME_BAR_ID = 'TIME_BAR';
enum PLAYING {
BACKWARD,
NOT_PLAYING,
FORWARD
}
export class PlayControl extends Control {
options:ControlOptions = {
position:'bottomright'
}
private timer:Timer = new Timer(1,1000);
private playing:PLAYING = PLAYING.NOT_PLAYING;
private timeLabel:HTMLElement = null;
private forwardButton:HTMLElement = null;
private backwardsButton:HTMLElement = null;
private playbacks:PlayBack[] = [];
private timeline:Timeline = null;
private map:LeafletMap;
private spinner:Spinner;
constructor(...playbacks:PlayBack[]) {
super();
this.playbacks = playbacks;
this.playbacks.sort((a,b) => {
return a.MinimumTime - b.MinimumTime;
})
this.timer.addListener(this.timerHandler.bind(this));
this.spinner = new Spinner();
}
onAdd(map:LeafletMap) : HTMLElement {
this.map = map;
return this.createHtml();
}
private createHtml() : HTMLElement {
const container = DomUtil.create('div','play-control-container');
container.tabIndex = 0;
const topbar = DomUtil.create('div','player-control-top-bar',container);
const leftTopBar = DomUtil.create('div','player-control-left-top-bar',topbar);
const buttonsContainer = DomUtil.create('div','play-control-buttons',topbar);
const rightTopBar = DomUtil.create('div','player-control-right-top-bar',topbar);
const timelineContainer = DomUtil.create('div','play-control-timeline',container);
const groups = new Set();
/*const items : DataItem[] = this.playbacks.map((p) => p.Data.filter((e) => e.showOnTimeline || e.event.name.startsWith("TIMELINE")).map((e => {
groups.add(e.event.args.id)
switch (e.event.name) {
case "TIMELINE_POINT":
return {
start:e.time,
content:e.event.args.content,
group:e.event.args.id,
type:'box',
align:'left'
}
case "TIMELINE_RANGE":
return {
start:e.time,
content:e.event.args.content,
end:e.event.args.endTime,
group:e.event.args.id,
type:'range'
}
default:
return {
start:e.time,
content: e.timelineName || e.event.name,
group:e.event.args.id,
type:'box',
align:'left'
}
}
})));*/
const items = this.playbacks.map(p => p.Data.filter((e=> e.showOnTimeline || e.event.name.startsWith("TIMELINE"))).map((e) => {
groups.add(e.event.args.id)
switch (e.event.name) {
case "TIMELINE_POINT":
return {
start:e.time,
content:e.event.args.content,
group:e.event.args.id,
type:'box',
align:'left'
}
case "TIMELINE_RANGE":
return {
start:e.time,
content:e.event.args.content,
end:e.event.args.endTime,
group:e.event.args.id,
type:'range'
}
default:
return {
start:e.time,
content: e.timelineName || e.event.name,
group:e.event.args.id,
type:'box',
align:'left'
}
}
})).flat();
this.timeline = new Timeline(
timelineContainer,
items,
Array.from(groups).map(g => ({
id:g,
content:g
})),
{
min:this.minT(),
max:this.maxT(),
start:this.minT(),
end:this.maxT(),
width:'100%',
showCurrentTime:false
}
);
this.timeline.addCustomTime(this.minT(),TIME_BAR_ID);
this.timeline.on('timechanged',(e) => {
console.log('timechanged');
this.timeline.focus(TIME_BAR_ID);
this.setTime(e.time);
});
this.timeline.on('click',(e) => {
console.log('timeline click');
this.timeline.setCustomTime(e.time,TIME_BAR_ID);
console.log('bar set')
this.setTime(e.time)
})
const speedDecreaseButton = DomUtil.create('button','play-control-button',buttonsContainer);
speedDecreaseButton.innerHTML = '';
speedDecreaseButton.title = 'Decrease speed';
this.backwardsButton = DomUtil.create('button','play-control-button',buttonsContainer);
this.backwardsButton.innerHTML = PLAY_BACKWARD_CHAR;
const stopButton = DomUtil.create('button','play-control-button',buttonsContainer);
stopButton.innerHTML = STOP_CHAR;
this.forwardButton = DomUtil.create('button','play-control-button',buttonsContainer);
this.forwardButton.innerHTML = PLAY_FORWARD_CHAR;
const speedIncreaseButton = DomUtil.create('button','play-control-button',buttonsContainer);
speedIncreaseButton.innerHTML = '';
speedIncreaseButton.title = 'Increase speed';
const playBackSpeedLabel = DomUtil.create('span','play-control-speed-label',leftTopBar);
playBackSpeedLabel.innerHTML = `Speed: ${this.timer.Speed}X`;
this.timeLabel = DomUtil.create('span','play-control-time-label',rightTopBar);
this.timeLabel.innerHTML = `Time: ${(new Date(this.minT())).toLocaleString()}`;
const trackButton = (DomUtil.create('input','play-control-track',buttonsContainer) as HTMLInputElement);
const trackLabel = (DomUtil.create('label','',buttonsContainer) as HTMLLabelElement);
trackButton.type = 'checkbox';
trackButton.checked = true;
trackLabel.innerHTML = 'Track'
DomEvent.on(trackButton,'input',() => {
for(const p of this.playbacks) {
p.Track = trackButton.checked;
}
});
DomEvent.on(this.forwardButton,'click',this.playForward.bind(this));
DomEvent.on(this.backwardsButton,'click',this.playBackward.bind(this));
DomEvent.on(stopButton,'click',this.stop.bind(this));
DomEvent.on(speedIncreaseButton,'click',() => {
this.timer.Speed *= 2;
playBackSpeedLabel.innerHTML = `Speed: ${this.timer.Speed}X`;
});
DomEvent.on(speedDecreaseButton,'click',() => {
this.timer.Speed /= 2;
playBackSpeedLabel.innerHTML = `Speed: ${this.timer.Speed}X`;
});
DomEvent.on(container,'keydown',(event: KeyboardEvent) => {
switch (event.code) {
case "ArrowRight":
this.playForward()
break;
case "ArrowLeft":
this.playBackward()
break;
case "ArrowUp":
this.timer.Speed *= 2;
playBackSpeedLabel.innerHTML = `Speed: ${this.timer.Speed}X`;
break;
case "ArrowDown":
this.timer.Speed /= 2;
playBackSpeedLabel.innerHTML = `Speed: ${this.timer.Speed}X`;
break;
case "Space":
this.stop()
break;
default:
break;
}
})
DomEvent.disableClickPropagation(container);
DomEvent.disableScrollPropagation(container);
return container;
}
private timerHandler(time:number) {
if((time+this.minT()) > this.maxT() || (time+this.minT()) < this.minT()) {
this.pause();
return;
}
this.timeline.setCustomTime(time + this.minT(),TIME_BAR_ID);
this.timeline.moveTo(time + this.minT());
for(const p of this.playbacks) {
p.Time = time + this.minT();
}
this.timeLabel.innerHTML = `Time: ${(new Date(time + this.minT())).toLocaleString()}`;
}
playForward() {
switch (this.playing) {
case PLAYING.FORWARD:
this.pause();
break;
case PLAYING.BACKWARD:
this.timer.Direction = 'forward';
this.playing = PLAYING.FORWARD;
this.backwardsButton.innerHTML = PLAY_BACKWARD_CHAR;
this.forwardButton.innerHTML = PAUSE_CHAR;
if(!this.timer.isRunning)
this.timer.start();
break;
case PLAYING.NOT_PLAYING:
this.timer.Direction = 'forward';
this.timer.start();
this.forwardButton.innerHTML = PAUSE_CHAR;
this.playing = PLAYING.FORWARD;
break;
}
}
playBackward() {
switch (this.playing) {
case PLAYING.FORWARD:
this.timer.Direction = 'backward';
this.playing = PLAYING.BACKWARD;
this.backwardsButton.innerHTML = PAUSE_CHAR;
this.forwardButton.innerHTML = PLAY_FORWARD_CHAR;
if(!this.timer.isRunning)
this.timer.start();
break;
case PLAYING.BACKWARD:
this.pause();
break;
case PLAYING.NOT_PLAYING:
this.timer.Direction = 'backward';
this.timer.start();
this.backwardsButton.innerHTML = PAUSE_CHAR;
this.playing = PLAYING.BACKWARD;
break;
}
}
stop() {
this.timer.stop();
this.playing = PLAYING.NOT_PLAYING;
this.forwardButton.innerHTML = PLAY_FORWARD_CHAR;
this.backwardsButton.innerHTML = PLAY_BACKWARD_CHAR;
}
pause() {
this.timer.pause();
this.playing = PLAYING.NOT_PLAYING;
this.forwardButton.innerHTML = PLAY_FORWARD_CHAR;
this.backwardsButton.innerHTML = PLAY_BACKWARD_CHAR;
}
private setTime(date:Date) {
this.spin(true);
setTimeout(() => {
for(const p of this.playbacks) {
p.Time = date.getTime();
}
this.timer.Time = date.getTime() - this.minT();
this.spin(false);
}, 0);
}
private spin(doSpin: boolean) {
if(doSpin)
this.spinner.spin(this.map.getContainer())
else
this.spinner.stop()
}
private minT() {
return this.playbacks[0].MinimumTime;
}
private maxT() {
return this.playbacks[this.playbacks.length - 1].MaximumTime;
}
}