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; } }