1 import { controllerEvents
, controllers
} from './Controller.js';
2 import { ELEMENT_FOCUSED
, IS_MOBILE
, setURL
, utcToLocal
} from '../util.js';
3 import { SimulationSlider
} from './simulationSlider.js';
4 import { simVars
} from '../simVars.js';
8 const NORMAL_RATE
= 330;
10 /** Creates a UI component that includes a play / pause / prev / next buttons to iterate through
11 * the simulation. Also includes a slider bar with a head that indicates relative position in
12 * simulation that can be dragged to a specific location. Bar can also be clicked to seek
13 * to a specific position.
16 * 1. Initialization block
18 * 3. FrameNavigation block
20 export class SimulationController
extends HTMLElement
{
21 /** ===== Initialization block ===== */
25 <div id='sim-controller' class='slider-container hidden'>
26 <div id='slider-header'>
27 <div id='slider-play-bar'>
28 <button class='slider-button' id='slider-slow-down'>
29 <svg class='svgIcon slider-icon'>
30 <use href="#fast_rewind_black_24dp"></use>
33 <button class='slider-button' id='slider-prev'>
34 <svg class='svgIcon slider-icon'>
35 <use href="#arrow_left-24px"></use>
38 <button class='slider-button' id='slider-play-pause'>
39 <svg id='play-button' class='svgIcon slider-icon'>
40 <use href="#play_arrow-24px"></use>
42 <svg id='pause-button' class='svgIcon slider-icon hidden'>
43 <use href="#pause-24px"></use>
46 <button class='slider-button' id='slider-next'>
47 <svg class='svgIcon slider-icon'>
48 <use href="#arrow_right-24px"></use>
51 <button class='slider-button' id='slider-fast-forward'>
52 <svg class='svgIcon slider-icon'>
53 <use href="#fast_forward_black_24dp"></use>
57 <div id='slider-timestamp'>
58 <span id='timestamp'></span>
65 this.frameRate
= NORMAL_RATE
;
66 this.simulationSlider
;
70 this.initializeContainer();
72 this.createSimulationSlider();
73 this.initializeFrameNavigation();
74 this.initializeFrameRates();
76 controllers
.currentDomain
.subscribe(() => {
78 }, controllerEvents
.ALL
);
79 controllers
.currentTimestamp
.subscribe(() => {
80 this.updateDisplayedTimestamp();
84 initializeContainer() {
85 const container
= this.querySelector('.slider-container');
86 L
.DomEvent
.disableScrollPropagation(container
);
87 L
.DomEvent
.disableClickPropagation(container
);
90 createSimulationSlider() {
91 const container
= this.querySelector('.slider-container');
93 const slider
= new SimulationSlider();
94 container
.appendChild(slider
);
95 this.simulationSlider
= slider
;
98 initializeFrameNavigation() {
99 document
.addEventListener('keydown', (e
) => {
100 e
= e
|| window
.event
;
101 if (ELEMENT_FOCUSED
) {
105 if (e
.key
== 'ArrowRight') {
108 if (e
.key
== 'ArrowLeft') {
113 this.querySelector('#slider-play-pause').onpointerdown
= () => {
116 this.querySelector('#slider-prev').onpointerdown
= () => {
120 this.querySelector('#slider-next').onpointerdown
= () => {
126 initializeFrameRates() {
127 const speedUp
= this.querySelector('#slider-fast-forward');
128 const slowDown
= this.querySelector('#slider-slow-down');
130 speedUp
.onpointerdown
= () => {
131 this.toggleRate(FAST_RATE
, speedUp
, slowDown
);
133 slowDown
.onpointerdown
= () => {
134 this.toggleRate(SLOW_RATE
, slowDown
, speedUp
);
139 const sliderContainer
= this.querySelector('#sim-controller');
144 if (simVars
.sortedTimestamps
.length
< 2) {
145 sliderContainer
.classList
.add('hidden');
147 sliderContainer
.classList
.remove('hidden');
150 this.updateDisplayedTimestamp();
153 /** ===== UI block ===== */
156 const timeStamp
= this.querySelector('#slider-timestamp');
157 const playButtons
= this.querySelector('#slider-play-bar');
158 timeStamp
.parentNode
.insertBefore(timeStamp
, playButtons
);
162 updateDisplayedTimestamp() {
163 let currentTimestamp
= controllers
.currentTimestamp
.getValue();
164 this.querySelector('#timestamp').innerText
= utcToLocal(currentTimestamp
);
168 const prevButton
= this.querySelector('#slider-prev');
169 const nextButton
= this.querySelector('#slider-next');
170 const playButton
= this.querySelector('#play-button');
171 const pauseButton
= this.querySelector('#pause-button');
173 playButton
.classList
.add('hidden');
174 pauseButton
.classList
.remove('hidden');
175 prevButton
.disabled
= true;
176 nextButton
.disabled
= true;
177 prevButton
.classList
.add('disabled-button');
178 nextButton
.classList
.add('disabled-button');
182 const prevButton
= this.querySelector('#slider-prev');
183 const nextButton
= this.querySelector('#slider-next');
184 const playButton
= this.querySelector('#play-button');
185 const pauseButton
= this.querySelector('#pause-button');
187 playButton
.classList
.remove('hidden');
188 pauseButton
.classList
.add('hidden');
189 prevButton
.disabled
= false;
190 nextButton
.disabled
= false;
191 prevButton
.classList
.remove('disabled-button');
192 nextButton
.classList
.remove('disabled-button');
195 /** ===== FrameNavigation block ===== */
197 this.playing
= !this.playing
;
207 /** Iterates to next frame while still playing */
210 let endDate
= controllers
.endDate
.getValue();
211 let nextTimestamp
= this.nextFrame();
212 if (nextTimestamp
== endDate
) {
213 window
.setTimeout(() => this.play(), 2*this.frameRate
);
215 window
.setTimeout(() => this.play(), this.frameRate
);
221 let nextTimestamp
= this.simulationSlider
.nextTimestamp();
223 controllers
.currentTimestamp
.setValue(nextTimestamp
);
224 return nextTimestamp
;
228 let prevTimestamp
= this.simulationSlider
.prevTimestamp();
230 controllers
.currentTimestamp
.setValue(prevTimestamp
);
231 return prevTimestamp
;
234 toggleRate(rate
, togglePrimary
, toggleSecondary
) {
235 let unPressedColor
= '#d6d6d6';
236 togglePrimary
.style
.background
= unPressedColor
;
237 toggleSecondary
.style
.background
= unPressedColor
;
239 if (this.frameRate
== rate
) {
240 this.frameRate
= NORMAL_RATE
;
242 this.frameRate
= rate
;
243 togglePrimary
.style
.background
= '#e5e5e5';
248 window
.customElements
.define('simulation-controller', SimulationController
);