1'use strict'; 2 3class Workout { 4 date = new Date(); 5 id = (Date.now() + '').slice(-10); 6 isDeleted; 7 8 constructor(coords, distance, duration) { 9 this.coords = coords; //[lat,lng] 10 this.distance = distance; // in km 11 this.duration = duration; // in min 12 } 13 14 _setDescription() { 15 // prettier-ignore 16 const months = ['January', 'February', 'March', 'April', 'May', 'June', 17 'July', 'August', 'September', 'October', 'November', 'December']; 18 this.description = `${this.type[0].toUpperCase()}${this.type.slice(1)} 19 on ${months[this.date.getMonth()]} ${this.date.getDate()}`; 20 } 21 22 click() { 23 this.clicks++; 24 } 25} 26 27class Running extends Workout { 28 type = 'running'; 29 constructor(coords, distance, duration, cadence) { 30 super(coords, distance, duration); 31 this.cadence = cadence; 32 this.calcPace(); 33 this._setDescription(); 34 } 35 36 calcPace() { 37 // min/kim 38 this.pace = this.duration / this.distance; 39 return this.pace; 40 } 41} 42class Cycling extends Workout { 43 type = 'cycling'; 44 constructor(coords, distance, duration, elavationGain) { 45 super(coords, distance, duration); 46 this.elavationGain = elavationGain; 47 // this.type = 'Cycling'; 48 this.calcSpeed(); 49 this._setDescription(); 50 } 51 52 calcSpeed() { 53 // km/h 54 this.speed = this.distance / (this.duration / 60); 55 return this.speed; 56 } 57} 58 59// const run1 = new Running([39,-12], 5.2, 24, 178); 60// const cycling1 = new Cycling([39,-12], 27, 95, 523); 61// console.log(run1, cycling1); 62 63// -------------------------------------------- 64// APP Architecture 65 66const form = document.querySelector('.form'); 67const containerWorkouts = document.querySelector('.workouts'); 68const inputType = document.querySelector('.form__input--type'); 69const inputDistance = document.querySelector('.form__input--distance'); 70const inputDuration = document.querySelector('.form__input--duration'); 71const inputCadence = document.querySelector('.form__input--cadence'); 72const inputElevation = document.querySelector('.form__input--elevation'); 73const deleteWorkout = document.querySelector('delete-workout'); 74 75class App { 76 #map; 77 #mapZoomLevel = 15; 78 #mapEvent; 79 #workouts = []; 80 81 constructor() { 82 // Get user's position 83 this._getPosition(); 84 85 // Get data from local storage 86 this._getLocalStorage(); 87 88 // Attach event handlers 89 form.addEventListener('submit', this._newWorkOut.bind(this)); 90 inputType.addEventListener('change', this._toggleElevationField); 91 containerWorkouts.addEventListener('click', this._moveToPopup.bind(this)); 92 document.addEventListener('click', this._deleteWorkout.bind(this)); 93 94 // document.addEventListener('click', function (event) { 95 // const deleteButton = event.target.closest('.delete-workout'); 96 // if (deleteButton) { 97 // const workoutId = deleteButton.getAttribute('data-workout-id'); 98 // console.log(workoutId); 99 // this._setNewLocalStorage(workoutId).bind(this); 100 // } 101 // }); 102 } 103 104 _getPosition() { 105 if (navigator.geolocation) 106 navigator.geolocation.getCurrentPosition( 107 this._loadMap.bind(this), 108 109 function () { 110 alert('Could not access you location!'); 111 } 112 ); 113 } 114 _loadMap(position) { 115 const { latitude } = position.coords; 116 const { longitude } = position.coords; 117 // console.log(`https://www.google.com/maps/@${latitude},${longitude}`); 118 119 const coords = [latitude, longitude]; 120 121 this.#map = L.map('map').setView(coords, this.#mapZoomLevel); 122 123 L.tileLayer('https://tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', { 124 attribution: 125 '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', 126 }).addTo(this.#map); 127 128 // Handing clicks on map 129 this.#map.on('click', this._showForm.bind(this)); 130 131 this.#workouts.forEach(work => { 132 this._renderWorkoutMarker(work); 133 }); 134 } 135 _showForm(mapE) { 136 this.#mapEvent = mapE; 137 form.classList.remove('hidden'); 138 inputDistance.focus(); 139 } 140 141 _hideForm() { 142 // Empty the inputs 143 inputDistance.value = 144 inputDuration.value = 145 inputCadence.value = 146 inputElevation.value = 147 ''; 148 149 form.style.display = 'none'; 150 form.classList.add('hidden'); 151 setTimeout(() => (form.style.display = 'grid'), 1000); 152 } 153 _toggleElevationField() { 154 inputElevation.closest('.form__row').classList.toggle('form__row--hidden'); 155 inputCadence.closest('.form__row').classList.toggle('form__row--hidden'); 156 } 157 _newWorkOut(e) { 158 const validateInputs = (...inputs) => 159 inputs.every(inp => Number.isFinite(inp)); 160 161 const allPositive = (...inputs) => inputs.every(inp => inp > 0); 162 e.preventDefault(); 163 164 // Get data from form 165 const type = inputType.value; 166 const distance = +inputDistance.value; 167 const duration = +inputDuration.value; 168 const { lat, lng } = this.#mapEvent.latlng; 169 let workout; 170 171 // Validate the data 172 173 // If running, create running object - if cycling create cycling object 174 if (type === 'running') { 175 const cadence = +inputCadence.value; 176 // validate 177 if ( 178 !validateInputs(distance, duration, cadence) || 179 !allPositive(distance, duration, cadence) 180 ) 181 return alert('Inputs have to be positive numbers!'); 182 183 workout = new Running([lat, lng], distance, duration, cadence); 184 } 185 186 if (type === 'cycling') { 187 const elavation = +inputElevation.value; 188 189 if ( 190 !validateInputs(distance, duration, elavation) || 191 !allPositive(distance, duration) 192 ) 193 return alert('Inputs have to be positive numbers!'); 194 195 workout = new Cycling([lat, lng], distance, duration, elavation); 196 } 197 198 // Add new object to workout array 199 this.#workouts.push(workout); 200 201 // Render workout on map as marker 202 this._renderWorkoutMarker(workout); 203 204 // Render workout on the list 205 this._renderWorkout(workout); 206 207 // Hide form + Clear input fields 208 this._hideForm(); 209 210 // Set local storage to all workouts 211 this._setLocalStorage(); 212 } 213 _renderWorkoutMarker(workout) { 214 L.marker(workout.coords) 215 .addTo(this.#map) 216 .bindPopup( 217 L.popup({ 218 maxWidth: 250, 219 minWidth: 200, 220 autoClose: false, 221 closeOnClick: false, 222 className: `${workout.type}-popup`, 223 }) 224 ) 225 .setPopupContent( 226 `${workout.type === 'running' ? 'ðââï¸' : 'ð´ââï¸'} ${ 227 workout.description 228 }<span class="delete-workout" data-workout-id="${ 229 workout.id 230 }"><i class="bi bi-trash-fill"></i></span>` 231 ) 232 .openPopup(); 233 } 234 235 _renderWorkout(workout) { 236 let html = ` 237 <li class="workout workout--${workout.type}" data-id="${workout.id}"> 238 <h2 class="workout__title">${workout.description}</h2> 239 <div class="workout__details">
240 <span class="workout__icon">${ 241 workout.type === 'running' ? 'ðââï¸' : 'ð´ââï¸' 242 }</span> 243 <span class="workout__value">${workout.distance}</span> 244 <span class="workout__unit">km</span> 245 </div> 246 <div class="workout__details"> 247 <span class="workout__icon">â±</span> 248 <span class="workout__value">${workout.duration}</span> 249 <span class="workout__unit">min</span> 250 </div> 251 `; 252 253 if (workout.type === 'running') 254 html += ` 255 256 <div class="workout__details"> 257 <span class="workout__icon">â¡ï¸</span> 258 <span class="workout__value">${workout.pace.toFixed(1)}</span> 259 <span class="workout__unit">min/km</span> 260 </div> 261 262 <div class="workout__details"> 263 <span class="workout__icon">ð¦¶ð¼</span> 264 <span class="workout__value">${workout.cadence}</span> 265 <span class="workout__unit">spm</span> 266 </div> 267 </li>`; 268 if (workout.type === 'cycling') 269 html += ` 270 <div class="workout__details"> 271 <span class="workout__icon">â¡ï¸</span> 272 <span class="workout__value">${workout.speed.toFixed(1)}</span> 273 <span class="workout__unit">km/h</span> 274 </div> 275 <div class="workout__details"> 276 <span class="workout__icon">â°</span> 277 <span class="workout__value">${workout.elavationGain}</span> 278 <span class="workout__unit">m</span> 279 </div> 280 </li> 281 `; 282 283 form.insertAdjacentHTML('afterend', html); 284 } 285 _moveToPopup(e) { 286 const workOutEl = e.target.closest('.workout'); 287 if (!workOutEl) return; 288 const workout = this.#workouts.find( 289 work => work.id === workOutEl.dataset.id 290 ); 291 // console.log(workout); 292 this.#map.setView(workout.coords, this.#mapZoomLevel, { 293 animate: true, 294 pan: { 295 duration: 1, 296 }, 297 }); 298 } 299 300 _setLocalStorage() { 301 localStorage.setItem('workouts', JSON.stringify(this.#workouts)); 302 } 303 304 _deleteWorkout(e) { 305 306 const deleteButton = e.target.closest('.delete-workout'); 307 if (deleteButton) { 308 const workoutId = deleteButton.getAttribute('data-workout-id'); 309 console.log(workoutId); 310 if(!workoutId) return; 311 312 // get the element by id 313 const meow = this.#workouts.find( 314 // worko => worko.id === workOutEl.dataset.id 315 worko => worko.id === workoutId 316 ); 317 console.log(meow); 318 319 // get the index of the element 320 var removedWorkoutIndex = this.#workouts.indexOf(meow); 321 console.log(removedWorkoutIndex); 322 323 // remove the workout by index 324 this.#workouts.splice(removedWorkoutIndex, 1); 325 localStorage.setItem('workouts', JSON.stringify(this.#workouts)); 326 console.log(this.#workouts); 327 328 location.reload(); 329 } 330 331 } 332 333 _getLocalStorage() { 334 const data = JSON.parse(localStorage.getItem('workouts')); 335 336 if (!data) return; 337 338 this.#workouts = data; 339 340 this.#workouts.forEach(work => { 341 this._renderWorkout(work); 342 }); 343 } 344 345 nukeAll() { 346 localStorage.removeItem('workouts'); 347 location.reload(); 348 } 349} 350 351const app = new App();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.