├── 1-12-skeleton ├── favicon.ico ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── scripts │ └── app.js └── styles │ └── ud811.css ├── 1-13 ├── favicon.ico ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── scripts │ └── app.js └── styles │ └── ud811.css ├── 1-22 ├── favicon.ico ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── scripts │ └── app.js └── styles │ └── ud811.css ├── 1-30 ├── favicon.ico ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── scripts │ ├── app.js │ └── localforage-1.4.0.js └── styles │ └── ud811.css ├── 2-15 ├── favicon.ico ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── scripts │ ├── app.js │ └── localforage-1.4.0.js ├── service-worker.js └── styles │ └── ud811.css ├── 2-31 ├── favicon.ico ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── scripts │ ├── app.js │ └── localforage-1.4.0.js ├── service-worker.js └── styles │ └── ud811.css ├── 2-36 ├── favicon.ico ├── gulpfile.js ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── package.json ├── scripts │ ├── app.js │ └── localforage-1.4.0.js ├── service-worker.js └── styles │ ├── ud811.css │ ├── ud811.min.css │ └── ud811.scss ├── 3-12 ├── favicon.ico ├── firebase.json ├── gulpfile.js ├── images │ ├── clear.png │ ├── cloudy-scattered-showers.png │ ├── cloudy.png │ ├── cloudy_s_sunny-assets │ │ └── cloudy_s_sunny.png │ ├── cloudy_s_sunny.png │ ├── fog.png │ ├── ic_add_white_24px.svg │ ├── ic_refresh_white_24px.svg │ ├── icons │ │ ├── apple-120.png │ │ ├── apple-152.png │ │ ├── apple-167.png │ │ ├── apple-180.png │ │ ├── apple-60.png │ │ ├── apple-76.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-48x48.png │ │ ├── icon-512x512.png │ │ └── icon-96x96.png │ ├── partly-cloudy.png │ ├── rain.png │ ├── scattered-showers.png │ ├── sleet.png │ ├── snow.png │ ├── thunderstorm.png │ └── wind.png ├── index.html ├── manifest.json ├── package.json ├── scripts │ ├── app.js │ └── localforage-1.4.0.js ├── service-worker.js └── styles │ ├── ud811.css │ ├── ud811.min.css │ └── ud811.scss ├── CODEOWNERS ├── LICENSE ├── forecast-io_proxy.js └── readme.md /1-12-skeleton/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/favicon.ico -------------------------------------------------------------------------------- /1-12-skeleton/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/clear.png -------------------------------------------------------------------------------- /1-12-skeleton/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /1-12-skeleton/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/cloudy.png -------------------------------------------------------------------------------- /1-12-skeleton/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /1-12-skeleton/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/fog.png -------------------------------------------------------------------------------- /1-12-skeleton/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-12-skeleton/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/apple-120.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/apple-152.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/apple-167.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/apple-180.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/apple-60.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/apple-76.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /1-12-skeleton/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /1-12-skeleton/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/partly-cloudy.png -------------------------------------------------------------------------------- /1-12-skeleton/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/rain.png -------------------------------------------------------------------------------- /1-12-skeleton/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/scattered-showers.png -------------------------------------------------------------------------------- /1-12-skeleton/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/sleet.png -------------------------------------------------------------------------------- /1-12-skeleton/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/snow.png -------------------------------------------------------------------------------- /1-12-skeleton/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/thunderstorm.png -------------------------------------------------------------------------------- /1-12-skeleton/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-12-skeleton/images/wind.png -------------------------------------------------------------------------------- /1-12-skeleton/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /1-12-skeleton/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 6 | 7 | var app = { 8 | isLoading: true, 9 | visibleCards: {}, 10 | selectedCities: [], 11 | spinner: document.querySelector('.loader'), 12 | cardTemplate: document.querySelector('.cardTemplate'), 13 | container: document.querySelector('.main'), 14 | addDialog: document.querySelector('.dialog-container'), 15 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 16 | }; 17 | 18 | 19 | /***************************************************************************** 20 | * 21 | * Event listeners for UI elements 22 | * 23 | ****************************************************************************/ 24 | 25 | /* Event listener for refresh button */ 26 | document.getElementById('butRefresh').addEventListener('click', function() { 27 | app.updateForecasts(); 28 | }); 29 | 30 | /* Event listener for add new city button */ 31 | document.getElementById('butAdd').addEventListener('click', function() { 32 | // Open/show the add new city dialog 33 | app.toggleAddDialog(true); 34 | }); 35 | 36 | /* Event listener for add city button in add city dialog */ 37 | document.getElementById('butAddCity').addEventListener('click', function() { 38 | var select = document.getElementById('selectCityToAdd'); 39 | var selected = select.options[select.selectedIndex]; 40 | var key = selected.value; 41 | var label = selected.textContent; 42 | app.getForecast(key, label); 43 | app.selectedCities.push({key: key, label: label}); 44 | app.toggleAddDialog(false); 45 | }); 46 | 47 | /* Event listener for cancel button in add city dialog */ 48 | document.getElementById('butAddCancel').addEventListener('click', function() { 49 | app.toggleAddDialog(false); 50 | }); 51 | 52 | 53 | /***************************************************************************** 54 | * 55 | * Methods to update/refresh the UI 56 | * 57 | ****************************************************************************/ 58 | 59 | // Toggles the visibility of the add new city dialog. 60 | app.toggleAddDialog = function(visible) { 61 | if (visible) { 62 | app.addDialog.classList.add('dialog-container--visible'); 63 | } else { 64 | app.addDialog.classList.remove('dialog-container--visible'); 65 | } 66 | }; 67 | 68 | // Updates a weather card with the latest weather forecast. If the card 69 | // doesn't already exist, it's cloned from the template. 70 | app.updateForecastCard = function(data) { 71 | var card = app.visibleCards[data.key]; 72 | if (!card) { 73 | card = app.cardTemplate.cloneNode(true); 74 | card.classList.remove('cardTemplate'); 75 | card.querySelector('.location').textContent = data.label; 76 | card.removeAttribute('hidden'); 77 | app.container.appendChild(card); 78 | app.visibleCards[data.key] = card; 79 | } 80 | card.querySelector('.description').textContent = data.currently.summary; 81 | card.querySelector('.date').textContent = 82 | new Date(data.currently.time * 1000); 83 | card.querySelector('.current .icon').classList.add(data.currently.icon); 84 | card.querySelector('.current .temperature .value').textContent = 85 | Math.round(data.currently.temperature); 86 | card.querySelector('.current .feels-like .value').textContent = 87 | Math.round(data.currently.apparentTemperature); 88 | card.querySelector('.current .precip').textContent = 89 | Math.round(data.currently.precipProbability * 100) + '%'; 90 | card.querySelector('.current .humidity').textContent = 91 | Math.round(data.currently.humidity * 100) + '%'; 92 | card.querySelector('.current .wind .value').textContent = 93 | Math.round(data.currently.windSpeed); 94 | card.querySelector('.current .wind .direction').textContent = 95 | data.currently.windBearing; 96 | var nextDays = card.querySelectorAll('.future .oneday'); 97 | var today = new Date(); 98 | today = today.getDay(); 99 | for (var i = 0; i < 7; i++) { 100 | var nextDay = nextDays[i]; 101 | var daily = data.daily.data[i]; 102 | if (daily && nextDay) { 103 | nextDay.querySelector('.date').textContent = 104 | app.daysOfWeek[(i + today) % 7]; 105 | nextDay.querySelector('.icon').classList.add(daily.icon); 106 | nextDay.querySelector('.temp-high .value').textContent = 107 | Math.round(daily.temperatureMax); 108 | nextDay.querySelector('.temp-low .value').textContent = 109 | Math.round(daily.temperatureMin); 110 | } 111 | } 112 | if (app.isLoading) { 113 | app.spinner.setAttribute('hidden', true); 114 | app.container.removeAttribute('hidden'); 115 | app.isLoading = false; 116 | } 117 | }; 118 | 119 | 120 | /***************************************************************************** 121 | * 122 | * Methods for dealing with the model 123 | * 124 | ****************************************************************************/ 125 | 126 | // Gets a forecast for a specific city and update the card with the data 127 | app.getForecast = function(key, label) { 128 | var url = weatherAPIUrlBase + key + '.json'; 129 | // Make the XHR to get the data, then update the card 130 | var request = new XMLHttpRequest(); 131 | request.onreadystatechange = function() { 132 | if (request.readyState === XMLHttpRequest.DONE) { 133 | if (request.status === 200) { 134 | var response = JSON.parse(request.response); 135 | response.key = key; 136 | response.label = label; 137 | app.updateForecastCard(response); 138 | } 139 | } 140 | }; 141 | request.open('GET', url); 142 | request.send(); 143 | }; 144 | 145 | // Iterate all of the cards and attempt to get the latest forecast data 146 | app.updateForecasts = function() { 147 | var keys = Object.keys(app.visibleCards); 148 | keys.forEach(function(key) { 149 | app.getForecast(key); 150 | }); 151 | }; 152 | 153 | })(); 154 | -------------------------------------------------------------------------------- /1-13/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/favicon.ico -------------------------------------------------------------------------------- /1-13/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/clear.png -------------------------------------------------------------------------------- /1-13/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /1-13/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/cloudy.png -------------------------------------------------------------------------------- /1-13/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /1-13/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/fog.png -------------------------------------------------------------------------------- /1-13/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-13/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-13/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/apple-120.png -------------------------------------------------------------------------------- /1-13/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/apple-152.png -------------------------------------------------------------------------------- /1-13/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/apple-167.png -------------------------------------------------------------------------------- /1-13/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/apple-180.png -------------------------------------------------------------------------------- /1-13/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/apple-60.png -------------------------------------------------------------------------------- /1-13/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/apple-76.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /1-13/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /1-13/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/partly-cloudy.png -------------------------------------------------------------------------------- /1-13/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/rain.png -------------------------------------------------------------------------------- /1-13/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/scattered-showers.png -------------------------------------------------------------------------------- /1-13/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/sleet.png -------------------------------------------------------------------------------- /1-13/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/snow.png -------------------------------------------------------------------------------- /1-13/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/thunderstorm.png -------------------------------------------------------------------------------- /1-13/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-13/images/wind.png -------------------------------------------------------------------------------- /1-13/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /1-13/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 6 | 7 | var app = { 8 | isLoading: true, 9 | visibleCards: {}, 10 | selectedCities: [], 11 | spinner: document.querySelector('.loader'), 12 | cardTemplate: document.querySelector('.cardTemplate'), 13 | container: document.querySelector('.main'), 14 | addDialog: document.querySelector('.dialog-container'), 15 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 16 | }; 17 | 18 | 19 | /***************************************************************************** 20 | * 21 | * Event listeners for UI elements 22 | * 23 | ****************************************************************************/ 24 | 25 | /* Event listener for refresh button */ 26 | document.getElementById('butRefresh').addEventListener('click', function() { 27 | app.updateForecasts(); 28 | }); 29 | 30 | /* Event listener for add new city button */ 31 | document.getElementById('butAdd').addEventListener('click', function() { 32 | // Open/show the add new city dialog 33 | app.toggleAddDialog(true); 34 | }); 35 | 36 | /* Event listener for add city button in add city dialog */ 37 | document.getElementById('butAddCity').addEventListener('click', function() { 38 | var select = document.getElementById('selectCityToAdd'); 39 | var selected = select.options[select.selectedIndex]; 40 | var key = selected.value; 41 | var label = selected.textContent; 42 | app.getForecast(key, label); 43 | app.selectedCities.push({key: key, label: label}); 44 | app.toggleAddDialog(false); 45 | }); 46 | 47 | /* Event listener for cancel button in add city dialog */ 48 | document.getElementById('butAddCancel').addEventListener('click', function() { 49 | app.toggleAddDialog(false); 50 | }); 51 | 52 | 53 | /***************************************************************************** 54 | * 55 | * Methods to update/refresh the UI 56 | * 57 | ****************************************************************************/ 58 | 59 | // Toggles the visibility of the add new city dialog. 60 | app.toggleAddDialog = function(visible) { 61 | if (visible) { 62 | app.addDialog.classList.add('dialog-container--visible'); 63 | } else { 64 | app.addDialog.classList.remove('dialog-container--visible'); 65 | } 66 | }; 67 | 68 | // Updates a weather card with the latest weather forecast. If the card 69 | // doesn't already exist, it's cloned from the template. 70 | app.updateForecastCard = function(data) { 71 | var card = app.visibleCards[data.key]; 72 | if (!card) { 73 | card = app.cardTemplate.cloneNode(true); 74 | card.classList.remove('cardTemplate'); 75 | card.querySelector('.location').textContent = data.label; 76 | card.removeAttribute('hidden'); 77 | app.container.appendChild(card); 78 | app.visibleCards[data.key] = card; 79 | } 80 | card.querySelector('.description').textContent = data.currently.summary; 81 | card.querySelector('.date').textContent = 82 | new Date(data.currently.time * 1000); 83 | card.querySelector('.current .icon').classList.add(data.currently.icon); 84 | card.querySelector('.current .temperature .value').textContent = 85 | Math.round(data.currently.temperature); 86 | card.querySelector('.current .feels-like .value').textContent = 87 | Math.round(data.currently.apparentTemperature); 88 | card.querySelector('.current .precip').textContent = 89 | Math.round(data.currently.precipProbability * 100) + '%'; 90 | card.querySelector('.current .humidity').textContent = 91 | Math.round(data.currently.humidity * 100) + '%'; 92 | card.querySelector('.current .wind .value').textContent = 93 | Math.round(data.currently.windSpeed); 94 | card.querySelector('.current .wind .direction').textContent = 95 | data.currently.windBearing; 96 | var nextDays = card.querySelectorAll('.future .oneday'); 97 | var today = new Date(); 98 | today = today.getDay(); 99 | for (var i = 0; i < 7; i++) { 100 | var nextDay = nextDays[i]; 101 | var daily = data.daily.data[i]; 102 | if (daily && nextDay) { 103 | nextDay.querySelector('.date').textContent = 104 | app.daysOfWeek[(i + today) % 7]; 105 | nextDay.querySelector('.icon').classList.add(daily.icon); 106 | nextDay.querySelector('.temp-high .value').textContent = 107 | Math.round(daily.temperatureMax); 108 | nextDay.querySelector('.temp-low .value').textContent = 109 | Math.round(daily.temperatureMin); 110 | } 111 | } 112 | if (app.isLoading) { 113 | app.spinner.setAttribute('hidden', true); 114 | app.container.removeAttribute('hidden'); 115 | app.isLoading = false; 116 | } 117 | }; 118 | 119 | 120 | /***************************************************************************** 121 | * 122 | * Methods for dealing with the model 123 | * 124 | ****************************************************************************/ 125 | 126 | // Gets a forecast for a specific city and update the card with the data 127 | app.getForecast = function(key, label) { 128 | var url = weatherAPIUrlBase + key + '.json'; 129 | // Make the XHR to get the data, then update the card 130 | var request = new XMLHttpRequest(); 131 | request.onreadystatechange = function() { 132 | if (request.readyState === XMLHttpRequest.DONE) { 133 | if (request.status === 200) { 134 | var response = JSON.parse(request.response); 135 | response.key = key; 136 | response.label = label; 137 | app.updateForecastCard(response); 138 | } 139 | } 140 | }; 141 | request.open('GET', url); 142 | request.send(); 143 | }; 144 | 145 | // Iterate all of the cards and attempt to get the latest forecast data 146 | app.updateForecasts = function() { 147 | var keys = Object.keys(app.visibleCards); 148 | keys.forEach(function(key) { 149 | app.getForecast(key); 150 | }); 151 | }; 152 | 153 | })(); 154 | -------------------------------------------------------------------------------- /1-22/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/favicon.ico -------------------------------------------------------------------------------- /1-22/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/clear.png -------------------------------------------------------------------------------- /1-22/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /1-22/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/cloudy.png -------------------------------------------------------------------------------- /1-22/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /1-22/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/fog.png -------------------------------------------------------------------------------- /1-22/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-22/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-22/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/apple-120.png -------------------------------------------------------------------------------- /1-22/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/apple-152.png -------------------------------------------------------------------------------- /1-22/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/apple-167.png -------------------------------------------------------------------------------- /1-22/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/apple-180.png -------------------------------------------------------------------------------- /1-22/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/apple-60.png -------------------------------------------------------------------------------- /1-22/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/apple-76.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /1-22/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /1-22/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/partly-cloudy.png -------------------------------------------------------------------------------- /1-22/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/rain.png -------------------------------------------------------------------------------- /1-22/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/scattered-showers.png -------------------------------------------------------------------------------- /1-22/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/sleet.png -------------------------------------------------------------------------------- /1-22/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/snow.png -------------------------------------------------------------------------------- /1-22/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/thunderstorm.png -------------------------------------------------------------------------------- /1-22/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-22/images/wind.png -------------------------------------------------------------------------------- /1-22/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /1-22/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var injectedForecast = { 6 | key: 'newyork', 7 | label: 'New York, NY', 8 | currently: { 9 | time: 1453489481, 10 | summary: 'Clear', 11 | icon: 'partly-cloudy-day', 12 | temperature: 52.74, 13 | apparentTemperature: 74.34, 14 | precipProbability: 0.20, 15 | humidity: 0.77, 16 | windBearing: 125, 17 | windSpeed: 1.52 18 | }, 19 | daily: { 20 | data: [ 21 | {icon: 'clear-day', temperatureMax: 55, temperatureMin: 34}, 22 | {icon: 'rain', temperatureMax: 55, temperatureMin: 34}, 23 | {icon: 'snow', temperatureMax: 55, temperatureMin: 34}, 24 | {icon: 'sleet', temperatureMax: 55, temperatureMin: 34}, 25 | {icon: 'fog', temperatureMax: 55, temperatureMin: 34}, 26 | {icon: 'wind', temperatureMax: 55, temperatureMin: 34}, 27 | {icon: 'partly-cloudy-day', temperatureMax: 55, temperatureMin: 34} 28 | ] 29 | } 30 | }; 31 | 32 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 33 | 34 | var app = { 35 | isLoading: true, 36 | visibleCards: {}, 37 | selectedCities: [], 38 | spinner: document.querySelector('.loader'), 39 | cardTemplate: document.querySelector('.cardTemplate'), 40 | container: document.querySelector('.main'), 41 | addDialog: document.querySelector('.dialog-container'), 42 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 43 | }; 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * Event listeners for UI elements 49 | * 50 | ****************************************************************************/ 51 | 52 | /* Event listener for refresh button */ 53 | document.getElementById('butRefresh').addEventListener('click', function() { 54 | app.updateForecasts(); 55 | }); 56 | 57 | /* Event listener for add new city button */ 58 | document.getElementById('butAdd').addEventListener('click', function() { 59 | // Open/show the add new city dialog 60 | app.toggleAddDialog(true); 61 | }); 62 | 63 | /* Event listener for add city button in add city dialog */ 64 | document.getElementById('butAddCity').addEventListener('click', function() { 65 | var select = document.getElementById('selectCityToAdd'); 66 | var selected = select.options[select.selectedIndex]; 67 | var key = selected.value; 68 | var label = selected.textContent; 69 | app.getForecast(key, label); 70 | app.selectedCities.push({key: key, label: label}); 71 | app.toggleAddDialog(false); 72 | }); 73 | 74 | /* Event listener for cancel button in add city dialog */ 75 | document.getElementById('butAddCancel').addEventListener('click', function() { 76 | app.toggleAddDialog(false); 77 | }); 78 | 79 | 80 | /***************************************************************************** 81 | * 82 | * Methods to update/refresh the UI 83 | * 84 | ****************************************************************************/ 85 | 86 | // Toggles the visibility of the add new city dialog. 87 | app.toggleAddDialog = function(visible) { 88 | if (visible) { 89 | app.addDialog.classList.add('dialog-container--visible'); 90 | } else { 91 | app.addDialog.classList.remove('dialog-container--visible'); 92 | } 93 | }; 94 | 95 | // Updates a weather card with the latest weather forecast. If the card 96 | // doesn't already exist, it's cloned from the template. 97 | app.updateForecastCard = function(data) { 98 | var card = app.visibleCards[data.key]; 99 | if (!card) { 100 | card = app.cardTemplate.cloneNode(true); 101 | card.classList.remove('cardTemplate'); 102 | card.querySelector('.location').textContent = data.label; 103 | card.removeAttribute('hidden'); 104 | app.container.appendChild(card); 105 | app.visibleCards[data.key] = card; 106 | } 107 | card.querySelector('.description').textContent = data.currently.summary; 108 | card.querySelector('.date').textContent = 109 | new Date(data.currently.time * 1000); 110 | card.querySelector('.current .icon').classList.add(data.currently.icon); 111 | card.querySelector('.current .temperature .value').textContent = 112 | Math.round(data.currently.temperature); 113 | card.querySelector('.current .feels-like .value').textContent = 114 | Math.round(data.currently.apparentTemperature); 115 | card.querySelector('.current .precip').textContent = 116 | Math.round(data.currently.precipProbability * 100) + '%'; 117 | card.querySelector('.current .humidity').textContent = 118 | Math.round(data.currently.humidity * 100) + '%'; 119 | card.querySelector('.current .wind .value').textContent = 120 | Math.round(data.currently.windSpeed); 121 | card.querySelector('.current .wind .direction').textContent = 122 | data.currently.windBearing; 123 | var nextDays = card.querySelectorAll('.future .oneday'); 124 | var today = new Date(); 125 | today = today.getDay(); 126 | for (var i = 0; i < 7; i++) { 127 | var nextDay = nextDays[i]; 128 | var daily = data.daily.data[i]; 129 | if (daily && nextDay) { 130 | nextDay.querySelector('.date').textContent = 131 | app.daysOfWeek[(i + today) % 7]; 132 | nextDay.querySelector('.icon').classList.add(daily.icon); 133 | nextDay.querySelector('.temp-high .value').textContent = 134 | Math.round(daily.temperatureMax); 135 | nextDay.querySelector('.temp-low .value').textContent = 136 | Math.round(daily.temperatureMin); 137 | } 138 | } 139 | if (app.isLoading) { 140 | app.spinner.setAttribute('hidden', true); 141 | app.container.removeAttribute('hidden'); 142 | app.isLoading = false; 143 | } 144 | }; 145 | 146 | 147 | /***************************************************************************** 148 | * 149 | * Methods for dealing with the model 150 | * 151 | ****************************************************************************/ 152 | 153 | // Gets a forecast for a specific city and update the card with the data 154 | app.getForecast = function(key, label) { 155 | var url = weatherAPIUrlBase + key + '.json'; 156 | // Make the XHR to get the data, then update the card 157 | var request = new XMLHttpRequest(); 158 | request.onreadystatechange = function() { 159 | if (request.readyState === XMLHttpRequest.DONE) { 160 | if (request.status === 200) { 161 | var response = JSON.parse(request.response); 162 | response.key = key; 163 | response.label = label; 164 | app.updateForecastCard(response); 165 | } 166 | } 167 | }; 168 | request.open('GET', url); 169 | request.send(); 170 | }; 171 | 172 | // Iterate all of the cards and attempt to get the latest forecast data 173 | app.updateForecasts = function() { 174 | var keys = Object.keys(app.visibleCards); 175 | keys.forEach(function(key) { 176 | app.getForecast(key); 177 | }); 178 | }; 179 | 180 | app.updateForecastCard(injectedForecast); 181 | 182 | })(); 183 | -------------------------------------------------------------------------------- /1-30/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/favicon.ico -------------------------------------------------------------------------------- /1-30/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/clear.png -------------------------------------------------------------------------------- /1-30/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /1-30/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/cloudy.png -------------------------------------------------------------------------------- /1-30/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /1-30/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/fog.png -------------------------------------------------------------------------------- /1-30/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-30/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-30/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/apple-120.png -------------------------------------------------------------------------------- /1-30/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/apple-152.png -------------------------------------------------------------------------------- /1-30/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/apple-167.png -------------------------------------------------------------------------------- /1-30/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/apple-180.png -------------------------------------------------------------------------------- /1-30/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/apple-60.png -------------------------------------------------------------------------------- /1-30/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/apple-76.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /1-30/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /1-30/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/partly-cloudy.png -------------------------------------------------------------------------------- /1-30/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/rain.png -------------------------------------------------------------------------------- /1-30/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/scattered-showers.png -------------------------------------------------------------------------------- /1-30/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/sleet.png -------------------------------------------------------------------------------- /1-30/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/snow.png -------------------------------------------------------------------------------- /1-30/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/thunderstorm.png -------------------------------------------------------------------------------- /1-30/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/1-30/images/wind.png -------------------------------------------------------------------------------- /1-30/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /1-30/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var injectedForecast = { 6 | key: 'newyork', 7 | label: 'New York, NY', 8 | currently: { 9 | time: 1453489481, 10 | summary: 'Clear', 11 | icon: 'partly-cloudy-day', 12 | temperature: 52.74, 13 | apparentTemperature: 74.34, 14 | precipProbability: 0.20, 15 | humidity: 0.77, 16 | windBearing: 125, 17 | windSpeed: 1.52 18 | }, 19 | daily: { 20 | data: [ 21 | {icon: 'clear-day', temperatureMax: 55, temperatureMin: 34}, 22 | {icon: 'rain', temperatureMax: 55, temperatureMin: 34}, 23 | {icon: 'snow', temperatureMax: 55, temperatureMin: 34}, 24 | {icon: 'sleet', temperatureMax: 55, temperatureMin: 34}, 25 | {icon: 'fog', temperatureMax: 55, temperatureMin: 34}, 26 | {icon: 'wind', temperatureMax: 55, temperatureMin: 34}, 27 | {icon: 'partly-cloudy-day', temperatureMax: 55, temperatureMin: 34} 28 | ] 29 | } 30 | }; 31 | 32 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 33 | 34 | var app = { 35 | isLoading: true, 36 | visibleCards: {}, 37 | selectedCities: [], 38 | spinner: document.querySelector('.loader'), 39 | cardTemplate: document.querySelector('.cardTemplate'), 40 | container: document.querySelector('.main'), 41 | addDialog: document.querySelector('.dialog-container'), 42 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 43 | }; 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * Event listeners for UI elements 49 | * 50 | ****************************************************************************/ 51 | 52 | /* Event listener for refresh button */ 53 | document.getElementById('butRefresh').addEventListener('click', function() { 54 | app.updateForecasts(); 55 | }); 56 | 57 | /* Event listener for add new city button */ 58 | document.getElementById('butAdd').addEventListener('click', function() { 59 | // Open/show the add new city dialog 60 | app.toggleAddDialog(true); 61 | }); 62 | 63 | /* Event listener for add city button in add city dialog */ 64 | document.getElementById('butAddCity').addEventListener('click', function() { 65 | var select = document.getElementById('selectCityToAdd'); 66 | var selected = select.options[select.selectedIndex]; 67 | var key = selected.value; 68 | var label = selected.textContent; 69 | app.getForecast(key, label); 70 | app.selectedCities.push({key: key, label: label}); 71 | app.saveSelectedCities(); 72 | app.toggleAddDialog(false); 73 | }); 74 | 75 | /* Event listener for cancel button in add city dialog */ 76 | document.getElementById('butAddCancel').addEventListener('click', function() { 77 | app.toggleAddDialog(false); 78 | }); 79 | 80 | 81 | /***************************************************************************** 82 | * 83 | * Methods to update/refresh the UI 84 | * 85 | ****************************************************************************/ 86 | 87 | // Toggles the visibility of the add new city dialog. 88 | app.toggleAddDialog = function(visible) { 89 | if (visible) { 90 | app.addDialog.classList.add('dialog-container--visible'); 91 | } else { 92 | app.addDialog.classList.remove('dialog-container--visible'); 93 | } 94 | }; 95 | 96 | // Updates a weather card with the latest weather forecast. If the card 97 | // doesn't already exist, it's cloned from the template. 98 | app.updateForecastCard = function(data) { 99 | var card = app.visibleCards[data.key]; 100 | if (!card) { 101 | card = app.cardTemplate.cloneNode(true); 102 | card.classList.remove('cardTemplate'); 103 | card.querySelector('.location').textContent = data.label; 104 | card.removeAttribute('hidden'); 105 | app.container.appendChild(card); 106 | app.visibleCards[data.key] = card; 107 | } 108 | card.querySelector('.description').textContent = data.currently.summary; 109 | card.querySelector('.date').textContent = 110 | new Date(data.currently.time * 1000); 111 | card.querySelector('.current .icon').classList.add(data.currently.icon); 112 | card.querySelector('.current .temperature .value').textContent = 113 | Math.round(data.currently.temperature); 114 | card.querySelector('.current .feels-like .value').textContent = 115 | Math.round(data.currently.apparentTemperature); 116 | card.querySelector('.current .precip').textContent = 117 | Math.round(data.currently.precipProbability * 100) + '%'; 118 | card.querySelector('.current .humidity').textContent = 119 | Math.round(data.currently.humidity * 100) + '%'; 120 | card.querySelector('.current .wind .value').textContent = 121 | Math.round(data.currently.windSpeed); 122 | card.querySelector('.current .wind .direction').textContent = 123 | data.currently.windBearing; 124 | var nextDays = card.querySelectorAll('.future .oneday'); 125 | var today = new Date(); 126 | today = today.getDay(); 127 | for (var i = 0; i < 7; i++) { 128 | var nextDay = nextDays[i]; 129 | var daily = data.daily.data[i]; 130 | if (daily && nextDay) { 131 | nextDay.querySelector('.date').textContent = 132 | app.daysOfWeek[(i + today) % 7]; 133 | nextDay.querySelector('.icon').classList.add(daily.icon); 134 | nextDay.querySelector('.temp-high .value').textContent = 135 | Math.round(daily.temperatureMax); 136 | nextDay.querySelector('.temp-low .value').textContent = 137 | Math.round(daily.temperatureMin); 138 | } 139 | } 140 | if (app.isLoading) { 141 | app.spinner.setAttribute('hidden', true); 142 | app.container.removeAttribute('hidden'); 143 | app.isLoading = false; 144 | } 145 | }; 146 | 147 | 148 | /***************************************************************************** 149 | * 150 | * Methods for dealing with the model 151 | * 152 | ****************************************************************************/ 153 | 154 | // Gets a forecast for a specific city and update the card with the data 155 | app.getForecast = function(key, label) { 156 | var url = weatherAPIUrlBase + key + '.json'; 157 | // Make the XHR to get the data, then update the card 158 | var request = new XMLHttpRequest(); 159 | request.onreadystatechange = function() { 160 | if (request.readyState === XMLHttpRequest.DONE) { 161 | if (request.status === 200) { 162 | var response = JSON.parse(request.response); 163 | response.key = key; 164 | response.label = label; 165 | app.updateForecastCard(response); 166 | } 167 | } 168 | }; 169 | request.open('GET', url); 170 | request.send(); 171 | }; 172 | 173 | // Iterate all of the cards and attempt to get the latest forecast data 174 | app.updateForecasts = function() { 175 | var keys = Object.keys(app.visibleCards); 176 | keys.forEach(function(key) { 177 | app.getForecast(key); 178 | }); 179 | }; 180 | 181 | app.saveSelectedCities = function() { 182 | window.localforage.setItem('selectedCities', app.selectedCities); 183 | }; 184 | 185 | document.addEventListener('DOMContentLoaded', function() { 186 | window.localforage.getItem('selectedCities', function(err, cityList) { 187 | if (cityList) { 188 | app.selectedCities = cityList; 189 | app.selectedCities.forEach(function(city) { 190 | app.getForecast(city.key, city.label); 191 | }); 192 | } else { 193 | app.updateForecastCard(injectedForecast); 194 | app.selectedCities = [ 195 | {key: injectedForecast.key, label: injectedForecast.label} 196 | ]; 197 | app.saveSelectedCities(); 198 | } 199 | }); 200 | }); 201 | 202 | 203 | })(); 204 | -------------------------------------------------------------------------------- /2-15/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/favicon.ico -------------------------------------------------------------------------------- /2-15/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/clear.png -------------------------------------------------------------------------------- /2-15/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /2-15/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/cloudy.png -------------------------------------------------------------------------------- /2-15/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /2-15/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/fog.png -------------------------------------------------------------------------------- /2-15/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2-15/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2-15/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/apple-120.png -------------------------------------------------------------------------------- /2-15/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/apple-152.png -------------------------------------------------------------------------------- /2-15/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/apple-167.png -------------------------------------------------------------------------------- /2-15/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/apple-180.png -------------------------------------------------------------------------------- /2-15/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/apple-60.png -------------------------------------------------------------------------------- /2-15/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/apple-76.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /2-15/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /2-15/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/partly-cloudy.png -------------------------------------------------------------------------------- /2-15/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/rain.png -------------------------------------------------------------------------------- /2-15/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/scattered-showers.png -------------------------------------------------------------------------------- /2-15/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/sleet.png -------------------------------------------------------------------------------- /2-15/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/snow.png -------------------------------------------------------------------------------- /2-15/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/thunderstorm.png -------------------------------------------------------------------------------- /2-15/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-15/images/wind.png -------------------------------------------------------------------------------- /2-15/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /2-15/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var injectedForecast = { 6 | key: 'newyork', 7 | label: 'New York, NY', 8 | currently: { 9 | time: 1453489481, 10 | summary: 'Clear', 11 | icon: 'partly-cloudy-day', 12 | temperature: 52.74, 13 | apparentTemperature: 74.34, 14 | precipProbability: 0.20, 15 | humidity: 0.77, 16 | windBearing: 125, 17 | windSpeed: 1.52 18 | }, 19 | daily: { 20 | data: [ 21 | {icon: 'clear-day', temperatureMax: 55, temperatureMin: 34}, 22 | {icon: 'rain', temperatureMax: 55, temperatureMin: 34}, 23 | {icon: 'snow', temperatureMax: 55, temperatureMin: 34}, 24 | {icon: 'sleet', temperatureMax: 55, temperatureMin: 34}, 25 | {icon: 'fog', temperatureMax: 55, temperatureMin: 34}, 26 | {icon: 'wind', temperatureMax: 55, temperatureMin: 34}, 27 | {icon: 'partly-cloudy-day', temperatureMax: 55, temperatureMin: 34} 28 | ] 29 | } 30 | }; 31 | 32 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 33 | 34 | var app = { 35 | isLoading: true, 36 | visibleCards: {}, 37 | selectedCities: [], 38 | spinner: document.querySelector('.loader'), 39 | cardTemplate: document.querySelector('.cardTemplate'), 40 | container: document.querySelector('.main'), 41 | addDialog: document.querySelector('.dialog-container'), 42 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 43 | }; 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * Event listeners for UI elements 49 | * 50 | ****************************************************************************/ 51 | 52 | /* Event listener for refresh button */ 53 | document.getElementById('butRefresh').addEventListener('click', function() { 54 | app.updateForecasts(); 55 | }); 56 | 57 | /* Event listener for add new city button */ 58 | document.getElementById('butAdd').addEventListener('click', function() { 59 | // Open/show the add new city dialog 60 | app.toggleAddDialog(true); 61 | }); 62 | 63 | /* Event listener for add city button in add city dialog */ 64 | document.getElementById('butAddCity').addEventListener('click', function() { 65 | var select = document.getElementById('selectCityToAdd'); 66 | var selected = select.options[select.selectedIndex]; 67 | var key = selected.value; 68 | var label = selected.textContent; 69 | app.getForecast(key, label); 70 | app.selectedCities.push({key: key, label: label}); 71 | app.saveSelectedCities(); 72 | app.toggleAddDialog(false); 73 | }); 74 | 75 | /* Event listener for cancel button in add city dialog */ 76 | document.getElementById('butAddCancel').addEventListener('click', function() { 77 | app.toggleAddDialog(false); 78 | }); 79 | 80 | 81 | /***************************************************************************** 82 | * 83 | * Methods to update/refresh the UI 84 | * 85 | ****************************************************************************/ 86 | 87 | // Toggles the visibility of the add new city dialog. 88 | app.toggleAddDialog = function(visible) { 89 | if (visible) { 90 | app.addDialog.classList.add('dialog-container--visible'); 91 | } else { 92 | app.addDialog.classList.remove('dialog-container--visible'); 93 | } 94 | }; 95 | 96 | // Updates a weather card with the latest weather forecast. If the card 97 | // doesn't already exist, it's cloned from the template. 98 | app.updateForecastCard = function(data) { 99 | var card = app.visibleCards[data.key]; 100 | if (!card) { 101 | card = app.cardTemplate.cloneNode(true); 102 | card.classList.remove('cardTemplate'); 103 | card.querySelector('.location').textContent = data.label; 104 | card.removeAttribute('hidden'); 105 | app.container.appendChild(card); 106 | app.visibleCards[data.key] = card; 107 | } 108 | card.querySelector('.description').textContent = data.currently.summary; 109 | card.querySelector('.date').textContent = 110 | new Date(data.currently.time * 1000); 111 | card.querySelector('.current .icon').classList.add(data.currently.icon); 112 | card.querySelector('.current .temperature .value').textContent = 113 | Math.round(data.currently.temperature); 114 | card.querySelector('.current .feels-like .value').textContent = 115 | Math.round(data.currently.apparentTemperature); 116 | card.querySelector('.current .precip').textContent = 117 | Math.round(data.currently.precipProbability * 100) + '%'; 118 | card.querySelector('.current .humidity').textContent = 119 | Math.round(data.currently.humidity * 100) + '%'; 120 | card.querySelector('.current .wind .value').textContent = 121 | Math.round(data.currently.windSpeed); 122 | card.querySelector('.current .wind .direction').textContent = 123 | data.currently.windBearing; 124 | var nextDays = card.querySelectorAll('.future .oneday'); 125 | var today = new Date(); 126 | today = today.getDay(); 127 | for (var i = 0; i < 7; i++) { 128 | var nextDay = nextDays[i]; 129 | var daily = data.daily.data[i]; 130 | if (daily && nextDay) { 131 | nextDay.querySelector('.date').textContent = 132 | app.daysOfWeek[(i + today) % 7]; 133 | nextDay.querySelector('.icon').classList.add(daily.icon); 134 | nextDay.querySelector('.temp-high .value').textContent = 135 | Math.round(daily.temperatureMax); 136 | nextDay.querySelector('.temp-low .value').textContent = 137 | Math.round(daily.temperatureMin); 138 | } 139 | } 140 | if (app.isLoading) { 141 | app.spinner.setAttribute('hidden', true); 142 | app.container.removeAttribute('hidden'); 143 | app.isLoading = false; 144 | } 145 | }; 146 | 147 | 148 | /***************************************************************************** 149 | * 150 | * Methods for dealing with the model 151 | * 152 | ****************************************************************************/ 153 | 154 | // Gets a forecast for a specific city and update the card with the data 155 | app.getForecast = function(key, label) { 156 | var url = weatherAPIUrlBase + key + '.json'; 157 | // Make the XHR to get the data, then update the card 158 | var request = new XMLHttpRequest(); 159 | request.onreadystatechange = function() { 160 | if (request.readyState === XMLHttpRequest.DONE) { 161 | if (request.status === 200) { 162 | var response = JSON.parse(request.response); 163 | response.key = key; 164 | response.label = label; 165 | app.updateForecastCard(response); 166 | } 167 | } 168 | }; 169 | request.open('GET', url); 170 | request.send(); 171 | }; 172 | 173 | // Iterate all of the cards and attempt to get the latest forecast data 174 | app.updateForecasts = function() { 175 | var keys = Object.keys(app.visibleCards); 176 | keys.forEach(function(key) { 177 | app.getForecast(key); 178 | }); 179 | }; 180 | 181 | app.saveSelectedCities = function() { 182 | window.localforage.setItem('selectedCities', app.selectedCities); 183 | }; 184 | 185 | document.addEventListener('DOMContentLoaded', function() { 186 | window.localforage.getItem('selectedCities', function(err, cityList) { 187 | if (cityList) { 188 | app.selectedCities = cityList; 189 | app.selectedCities.forEach(function(city) { 190 | app.getForecast(city.key, city.label); 191 | }); 192 | } else { 193 | app.updateForecastCard(injectedForecast); 194 | app.selectedCities = [ 195 | {key: injectedForecast.key, label: injectedForecast.label} 196 | ]; 197 | app.saveSelectedCities(); 198 | } 199 | }); 200 | }); 201 | 202 | if ('serviceWorker' in navigator) { 203 | navigator.serviceWorker 204 | .register('/service-worker.js') 205 | .then(function() { 206 | console.log('Service Worker Registered'); 207 | }); 208 | } 209 | 210 | })(); 211 | -------------------------------------------------------------------------------- /2-15/service-worker.js: -------------------------------------------------------------------------------- 1 | var cacheName = 'weatherPWA-v1'; 2 | var filesToCache = [ 3 | '/', 4 | '/index.html', 5 | '/scripts/app.js', 6 | '/scripts/localforage-1.4.0.js', 7 | '/styles/ud811.css', 8 | '/images/clear.png', 9 | '/images/cloudy-scattered-showers.png', 10 | '/images/cloudy.png', 11 | '/images/fog.png', 12 | '/images/ic_add_white_24px.svg', 13 | '/images/ic_refresh_white_24px.svg', 14 | '/images/partly-cloudy.png', 15 | '/images/rain.png', 16 | '/images/scattered-showers.png', 17 | '/images/sleet.png', 18 | '/images/snow.png', 19 | '/images/thunderstorm.png', 20 | '/images/wind.png' 21 | ]; 22 | 23 | self.addEventListener('install', function(e) { 24 | console.log('[ServiceWorker] Install'); 25 | e.waitUntil( 26 | caches.open(cacheName).then(function(cache) { 27 | console.log('[ServiceWorker] Caching app shell'); 28 | return cache.addAll(filesToCache); 29 | }) 30 | ); 31 | }); 32 | 33 | self.addEventListener('activate', function(e) { 34 | console.log('[ServiceWorker] Activate'); 35 | e.waitUntil( 36 | caches.keys().then(function(keyList) { 37 | return Promise.all(keyList.map(function(key) { 38 | if (key !== cacheName) { 39 | console.log('[ServiceWorker] Removing old cache', key); 40 | return caches.delete(key); 41 | } 42 | })); 43 | }) 44 | ); 45 | }); 46 | 47 | self.addEventListener('fetch', function(e) { 48 | console.log('[ServiceWorker] Fetch', e.request.url); 49 | e.respondWith( 50 | caches.match(e.request).then(function(response) { 51 | return response || fetch(e.request); 52 | }) 53 | ); 54 | }); 55 | -------------------------------------------------------------------------------- /2-31/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/favicon.ico -------------------------------------------------------------------------------- /2-31/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/clear.png -------------------------------------------------------------------------------- /2-31/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /2-31/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/cloudy.png -------------------------------------------------------------------------------- /2-31/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /2-31/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/fog.png -------------------------------------------------------------------------------- /2-31/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2-31/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2-31/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/apple-120.png -------------------------------------------------------------------------------- /2-31/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/apple-152.png -------------------------------------------------------------------------------- /2-31/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/apple-167.png -------------------------------------------------------------------------------- /2-31/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/apple-180.png -------------------------------------------------------------------------------- /2-31/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/apple-60.png -------------------------------------------------------------------------------- /2-31/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/apple-76.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /2-31/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /2-31/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/partly-cloudy.png -------------------------------------------------------------------------------- /2-31/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/rain.png -------------------------------------------------------------------------------- /2-31/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/scattered-showers.png -------------------------------------------------------------------------------- /2-31/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/sleet.png -------------------------------------------------------------------------------- /2-31/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/snow.png -------------------------------------------------------------------------------- /2-31/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/thunderstorm.png -------------------------------------------------------------------------------- /2-31/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-31/images/wind.png -------------------------------------------------------------------------------- /2-31/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /2-31/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var injectedForecast = { 6 | key: 'newyork', 7 | label: 'New York, NY', 8 | currently: { 9 | time: 1453489481, 10 | summary: 'Clear', 11 | icon: 'partly-cloudy-day', 12 | temperature: 52.74, 13 | apparentTemperature: 74.34, 14 | precipProbability: 0.20, 15 | humidity: 0.77, 16 | windBearing: 125, 17 | windSpeed: 1.52 18 | }, 19 | daily: { 20 | data: [ 21 | {icon: 'clear-day', temperatureMax: 55, temperatureMin: 34}, 22 | {icon: 'rain', temperatureMax: 55, temperatureMin: 34}, 23 | {icon: 'snow', temperatureMax: 55, temperatureMin: 34}, 24 | {icon: 'sleet', temperatureMax: 55, temperatureMin: 34}, 25 | {icon: 'fog', temperatureMax: 55, temperatureMin: 34}, 26 | {icon: 'wind', temperatureMax: 55, temperatureMin: 34}, 27 | {icon: 'partly-cloudy-day', temperatureMax: 55, temperatureMin: 34} 28 | ] 29 | } 30 | }; 31 | 32 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 33 | 34 | var app = { 35 | isLoading: true, 36 | visibleCards: {}, 37 | selectedCities: [], 38 | spinner: document.querySelector('.loader'), 39 | cardTemplate: document.querySelector('.cardTemplate'), 40 | container: document.querySelector('.main'), 41 | addDialog: document.querySelector('.dialog-container'), 42 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 43 | }; 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * Event listeners for UI elements 49 | * 50 | ****************************************************************************/ 51 | 52 | /* Event listener for refresh button */ 53 | document.getElementById('butRefresh').addEventListener('click', function() { 54 | app.updateForecasts(); 55 | }); 56 | 57 | /* Event listener for add new city button */ 58 | document.getElementById('butAdd').addEventListener('click', function() { 59 | // Open/show the add new city dialog 60 | app.toggleAddDialog(true); 61 | }); 62 | 63 | /* Event listener for add city button in add city dialog */ 64 | document.getElementById('butAddCity').addEventListener('click', function() { 65 | var select = document.getElementById('selectCityToAdd'); 66 | var selected = select.options[select.selectedIndex]; 67 | var key = selected.value; 68 | var label = selected.textContent; 69 | app.getForecast(key, label); 70 | app.selectedCities.push({key: key, label: label}); 71 | app.saveSelectedCities(); 72 | app.toggleAddDialog(false); 73 | }); 74 | 75 | /* Event listener for cancel button in add city dialog */ 76 | document.getElementById('butAddCancel').addEventListener('click', function() { 77 | app.toggleAddDialog(false); 78 | }); 79 | 80 | 81 | /***************************************************************************** 82 | * 83 | * Methods to update/refresh the UI 84 | * 85 | ****************************************************************************/ 86 | 87 | // Toggles the visibility of the add new city dialog. 88 | app.toggleAddDialog = function(visible) { 89 | if (visible) { 90 | app.addDialog.classList.add('dialog-container--visible'); 91 | } else { 92 | app.addDialog.classList.remove('dialog-container--visible'); 93 | } 94 | }; 95 | 96 | // Updates a weather card with the latest weather forecast. If the card 97 | // doesn't already exist, it's cloned from the template. 98 | app.updateForecastCard = function(data) { 99 | var card = app.visibleCards[data.key]; 100 | if (!card) { 101 | card = app.cardTemplate.cloneNode(true); 102 | card.classList.remove('cardTemplate'); 103 | card.querySelector('.location').textContent = data.label; 104 | card.removeAttribute('hidden'); 105 | app.container.appendChild(card); 106 | app.visibleCards[data.key] = card; 107 | } 108 | 109 | // Verify data is newer than what we already have, if not, bail. 110 | var dateElem = card.querySelector('.date'); 111 | if (dateElem.getAttribute('data-dt') >= data.currently.time) { 112 | return; 113 | } 114 | 115 | dateElem.setAttribute('data-dt', data.currently.time); 116 | dateElem.textContent = new Date(data.currently.time * 1000); 117 | card.querySelector('.description').textContent = data.currently.summary; 118 | card.querySelector('.current .icon').classList.add(data.currently.icon); 119 | card.querySelector('.current .temperature .value').textContent = 120 | Math.round(data.currently.temperature); 121 | card.querySelector('.current .feels-like .value').textContent = 122 | Math.round(data.currently.apparentTemperature); 123 | card.querySelector('.current .precip').textContent = 124 | Math.round(data.currently.precipProbability * 100) + '%'; 125 | card.querySelector('.current .humidity').textContent = 126 | Math.round(data.currently.humidity * 100) + '%'; 127 | card.querySelector('.current .wind .value').textContent = 128 | Math.round(data.currently.windSpeed); 129 | card.querySelector('.current .wind .direction').textContent = 130 | data.currently.windBearing; 131 | var nextDays = card.querySelectorAll('.future .oneday'); 132 | var today = new Date(); 133 | today = today.getDay(); 134 | for (var i = 0; i < 7; i++) { 135 | var nextDay = nextDays[i]; 136 | var daily = data.daily.data[i]; 137 | if (daily && nextDay) { 138 | nextDay.querySelector('.date').textContent = 139 | app.daysOfWeek[(i + today) % 7]; 140 | nextDay.querySelector('.icon').classList.add(daily.icon); 141 | nextDay.querySelector('.temp-high .value').textContent = 142 | Math.round(daily.temperatureMax); 143 | nextDay.querySelector('.temp-low .value').textContent = 144 | Math.round(daily.temperatureMin); 145 | } 146 | } 147 | if (app.isLoading) { 148 | app.spinner.setAttribute('hidden', true); 149 | app.container.removeAttribute('hidden'); 150 | app.isLoading = false; 151 | } 152 | }; 153 | 154 | 155 | /***************************************************************************** 156 | * 157 | * Methods for dealing with the model 158 | * 159 | ****************************************************************************/ 160 | 161 | // Gets a forecast for a specific city and update the card with the data 162 | app.getForecast = function(key, label) { 163 | var url = weatherAPIUrlBase + key + '.json'; 164 | if ('caches' in window) { 165 | caches.match(url).then(function(response) { 166 | if (response) { 167 | response.json().then(function(json) { 168 | json.key = key; 169 | json.label = label; 170 | app.updateForecastCard(json); 171 | }); 172 | } 173 | }); 174 | } 175 | // Make the XHR to get the data, then update the card 176 | var request = new XMLHttpRequest(); 177 | request.onreadystatechange = function() { 178 | if (request.readyState === XMLHttpRequest.DONE) { 179 | if (request.status === 200) { 180 | var response = JSON.parse(request.response); 181 | response.key = key; 182 | response.label = label; 183 | app.updateForecastCard(response); 184 | } 185 | } 186 | }; 187 | request.open('GET', url); 188 | request.send(); 189 | }; 190 | 191 | // Iterate all of the cards and attempt to get the latest forecast data 192 | app.updateForecasts = function() { 193 | var keys = Object.keys(app.visibleCards); 194 | keys.forEach(function(key) { 195 | app.getForecast(key); 196 | }); 197 | }; 198 | 199 | app.saveSelectedCities = function() { 200 | window.localforage.setItem('selectedCities', app.selectedCities); 201 | }; 202 | 203 | document.addEventListener('DOMContentLoaded', function() { 204 | window.localforage.getItem('selectedCities', function(err, cityList) { 205 | if (cityList) { 206 | app.selectedCities = cityList; 207 | app.selectedCities.forEach(function(city) { 208 | app.getForecast(city.key, city.label); 209 | }); 210 | } else { 211 | app.updateForecastCard(injectedForecast); 212 | app.selectedCities = [ 213 | {key: injectedForecast.key, label: injectedForecast.label} 214 | ]; 215 | app.saveSelectedCities(); 216 | } 217 | }); 218 | }); 219 | 220 | if ('serviceWorker' in navigator) { 221 | navigator.serviceWorker 222 | .register('/service-worker.js') 223 | .then(function() { 224 | console.log('Service Worker Registered'); 225 | }); 226 | } 227 | 228 | })(); 229 | -------------------------------------------------------------------------------- /2-31/service-worker.js: -------------------------------------------------------------------------------- 1 | var cacheName = 'weatherPWA-v2'; 2 | var dataCacheName = 'weatherData-v2'; 3 | var filesToCache = [ 4 | '/', 5 | '/index.html', 6 | '/scripts/app.js', 7 | '/scripts/localforage-1.4.0.js', 8 | '/styles/ud811.css', 9 | '/images/clear.png', 10 | '/images/cloudy-scattered-showers.png', 11 | '/images/cloudy.png', 12 | '/images/fog.png', 13 | '/images/ic_add_white_24px.svg', 14 | '/images/ic_refresh_white_24px.svg', 15 | '/images/partly-cloudy.png', 16 | '/images/rain.png', 17 | '/images/scattered-showers.png', 18 | '/images/sleet.png', 19 | '/images/snow.png', 20 | '/images/thunderstorm.png', 21 | '/images/wind.png' 22 | ]; 23 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 24 | 25 | self.addEventListener('install', function(e) { 26 | console.log('[ServiceWorker] Install'); 27 | e.waitUntil( 28 | caches.open(cacheName).then(function(cache) { 29 | console.log('[ServiceWorker] Caching app shell'); 30 | return cache.addAll(filesToCache); 31 | }) 32 | ); 33 | }); 34 | 35 | self.addEventListener('activate', function(e) { 36 | console.log('[ServiceWorker] Activate'); 37 | e.waitUntil( 38 | caches.keys().then(function(keyList) { 39 | return Promise.all(keyList.map(function(key) { 40 | if (key !== cacheName && key !== dataCacheName) { 41 | console.log('[ServiceWorker] Removing old cache', key); 42 | return caches.delete(key); 43 | } 44 | })); 45 | }) 46 | ); 47 | }); 48 | 49 | self.addEventListener('fetch', function(e) { 50 | if (e.request.url.startsWith(weatherAPIUrlBase)) { 51 | e.respondWith( 52 | fetch(e.request) 53 | .then(function(response) { 54 | return caches.open(dataCacheName).then(function(cache) { 55 | cache.put(e.request.url, response.clone()); 56 | console.log('[ServiceWorker] Fetched & Cached', e.request.url); 57 | return response; 58 | }); 59 | }) 60 | ); 61 | } else { 62 | e.respondWith( 63 | caches.match(e.request).then(function(response) { 64 | console.log('[ServiceWorker] Fetch Only', e.request.url); 65 | return response || fetch(e.request); 66 | }) 67 | ); 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /2-36/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/favicon.ico -------------------------------------------------------------------------------- /2-36/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserSync = require('browser-sync'); 3 | var reload = browserSync.reload; 4 | var sass = require('gulp-sass'); 5 | var autoprefixer = require('gulp-autoprefixer'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var del = require('del'); 9 | var swPrecache = require('sw-precache'); 10 | 11 | gulp.task('sass', function () { 12 | return gulp 13 | .src('./styles/*.scss') 14 | .pipe(sass()) 15 | .pipe(autoprefixer()) 16 | .pipe(gulp.dest('./styles/')) 17 | .pipe(minifyCss({})) 18 | .pipe(rename({suffix: '.min'})) 19 | .pipe(gulp.dest('./styles/')); 20 | }); 21 | 22 | gulp.task('generate-sw', function() { 23 | var swOptions = { 24 | staticFileGlobs: [ 25 | './index.html', 26 | './images/*.{png,svg,gif,jpg}', 27 | './scripts/*.js', 28 | './styles/*.css' 29 | ], 30 | stripPrefix: '.', 31 | runtimeCaching: [{ 32 | urlPattern: /^https:\/\/publicdata-weather\.firebaseio\.com/, 33 | handler: 'networkFirst', 34 | options: { 35 | cache: { 36 | name: 'weatherData-v3' 37 | } 38 | } 39 | }] 40 | }; 41 | return swPrecache.write('./service-worker.js', swOptions); 42 | }); 43 | 44 | gulp.task('serve', ['generate-sw'], function() { 45 | gulp.watch('./styles/*.scss', ['sass']); 46 | browserSync({ 47 | notify: false, 48 | logPrefix: 'weatherPWA', 49 | server: ['.'], 50 | open: false 51 | }); 52 | gulp.watch([ 53 | './*.html', 54 | './scripts/*.js', 55 | './styles/*.css', 56 | '!./service-worker.js', 57 | '!./gulpfile.js' 58 | ], ['generate-sw'], browserSync.reload); 59 | }); 60 | 61 | gulp.task('default', ['serve']); 62 | -------------------------------------------------------------------------------- /2-36/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/clear.png -------------------------------------------------------------------------------- /2-36/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /2-36/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/cloudy.png -------------------------------------------------------------------------------- /2-36/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /2-36/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/fog.png -------------------------------------------------------------------------------- /2-36/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2-36/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /2-36/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/apple-120.png -------------------------------------------------------------------------------- /2-36/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/apple-152.png -------------------------------------------------------------------------------- /2-36/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/apple-167.png -------------------------------------------------------------------------------- /2-36/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/apple-180.png -------------------------------------------------------------------------------- /2-36/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/apple-60.png -------------------------------------------------------------------------------- /2-36/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/apple-76.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /2-36/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /2-36/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/partly-cloudy.png -------------------------------------------------------------------------------- /2-36/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/rain.png -------------------------------------------------------------------------------- /2-36/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/scattered-showers.png -------------------------------------------------------------------------------- /2-36/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/sleet.png -------------------------------------------------------------------------------- /2-36/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/snow.png -------------------------------------------------------------------------------- /2-36/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/thunderstorm.png -------------------------------------------------------------------------------- /2-36/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/2-36/images/wind.png -------------------------------------------------------------------------------- /2-36/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 |
14 |

Weather PWA

15 | 16 | 17 |
18 | 19 |
20 | 117 |
118 | 119 |
120 |
121 |
Add new city
122 |
123 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 | 139 |
140 | 141 | 142 | 143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /2-36/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WeatherPWA-Udacity", 3 | "description": "Your first Progressive Web App", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache2", 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "browser-sync": "^2.11.2", 10 | "del": "^2.2.0", 11 | "gulp": "^3.9.1", 12 | "gulp-autoprefixer": "^3.1.0", 13 | "gulp-minify-css": "^1.2.4", 14 | "gulp-rename": "^1.2.2", 15 | "gulp-sass": "^2.2.0", 16 | "sw-precache": "^3.1.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2-36/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var injectedForecast = { 6 | key: 'newyork', 7 | label: 'New York, NY', 8 | currently: { 9 | time: 1453489481, 10 | summary: 'Clear', 11 | icon: 'partly-cloudy-day', 12 | temperature: 52.74, 13 | apparentTemperature: 74.34, 14 | precipProbability: 0.20, 15 | humidity: 0.77, 16 | windBearing: 125, 17 | windSpeed: 1.52 18 | }, 19 | daily: { 20 | data: [ 21 | {icon: 'clear-day', temperatureMax: 55, temperatureMin: 34}, 22 | {icon: 'rain', temperatureMax: 55, temperatureMin: 34}, 23 | {icon: 'snow', temperatureMax: 55, temperatureMin: 34}, 24 | {icon: 'sleet', temperatureMax: 55, temperatureMin: 34}, 25 | {icon: 'fog', temperatureMax: 55, temperatureMin: 34}, 26 | {icon: 'wind', temperatureMax: 55, temperatureMin: 34}, 27 | {icon: 'partly-cloudy-day', temperatureMax: 55, temperatureMin: 34} 28 | ] 29 | } 30 | }; 31 | 32 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 33 | 34 | var app = { 35 | isLoading: true, 36 | visibleCards: {}, 37 | selectedCities: [], 38 | spinner: document.querySelector('.loader'), 39 | cardTemplate: document.querySelector('.cardTemplate'), 40 | container: document.querySelector('.main'), 41 | addDialog: document.querySelector('.dialog-container'), 42 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 43 | }; 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * Event listeners for UI elements 49 | * 50 | ****************************************************************************/ 51 | 52 | /* Event listener for refresh button */ 53 | document.getElementById('butRefresh').addEventListener('click', function() { 54 | app.updateForecasts(); 55 | }); 56 | 57 | /* Event listener for add new city button */ 58 | document.getElementById('butAdd').addEventListener('click', function() { 59 | // Open/show the add new city dialog 60 | app.toggleAddDialog(true); 61 | }); 62 | 63 | /* Event listener for add city button in add city dialog */ 64 | document.getElementById('butAddCity').addEventListener('click', function() { 65 | var select = document.getElementById('selectCityToAdd'); 66 | var selected = select.options[select.selectedIndex]; 67 | var key = selected.value; 68 | var label = selected.textContent; 69 | app.getForecast(key, label); 70 | app.selectedCities.push({key: key, label: label}); 71 | app.saveSelectedCities(); 72 | app.toggleAddDialog(false); 73 | }); 74 | 75 | /* Event listener for cancel button in add city dialog */ 76 | document.getElementById('butAddCancel').addEventListener('click', function() { 77 | app.toggleAddDialog(false); 78 | }); 79 | 80 | 81 | /***************************************************************************** 82 | * 83 | * Methods to update/refresh the UI 84 | * 85 | ****************************************************************************/ 86 | 87 | // Toggles the visibility of the add new city dialog. 88 | app.toggleAddDialog = function(visible) { 89 | if (visible) { 90 | app.addDialog.classList.add('dialog-container--visible'); 91 | } else { 92 | app.addDialog.classList.remove('dialog-container--visible'); 93 | } 94 | }; 95 | 96 | // Updates a weather card with the latest weather forecast. If the card 97 | // doesn't already exist, it's cloned from the template. 98 | app.updateForecastCard = function(data) { 99 | var card = app.visibleCards[data.key]; 100 | if (!card) { 101 | card = app.cardTemplate.cloneNode(true); 102 | card.classList.remove('cardTemplate'); 103 | card.querySelector('.location').textContent = data.label; 104 | card.removeAttribute('hidden'); 105 | app.container.appendChild(card); 106 | app.visibleCards[data.key] = card; 107 | } 108 | 109 | // Verify data is newer than what we already have, if not, bail. 110 | var dateElem = card.querySelector('.date'); 111 | if (dateElem.getAttribute('data-dt') >= data.currently.time) { 112 | return; 113 | } 114 | 115 | dateElem.setAttribute('data-dt', data.currently.time); 116 | dateElem.textContent = new Date(data.currently.time * 1000); 117 | card.querySelector('.description').textContent = data.currently.summary; 118 | card.querySelector('.current .icon').classList.add(data.currently.icon); 119 | card.querySelector('.current .temperature .value').textContent = 120 | Math.round(data.currently.temperature); 121 | card.querySelector('.current .feels-like .value').textContent = 122 | Math.round(data.currently.apparentTemperature); 123 | card.querySelector('.current .precip').textContent = 124 | Math.round(data.currently.precipProbability * 100) + '%'; 125 | card.querySelector('.current .humidity').textContent = 126 | Math.round(data.currently.humidity * 100) + '%'; 127 | card.querySelector('.current .wind .value').textContent = 128 | Math.round(data.currently.windSpeed); 129 | card.querySelector('.current .wind .direction').textContent = 130 | data.currently.windBearing; 131 | var nextDays = card.querySelectorAll('.future .oneday'); 132 | var today = new Date(); 133 | today = today.getDay(); 134 | for (var i = 0; i < 7; i++) { 135 | var nextDay = nextDays[i]; 136 | var daily = data.daily.data[i]; 137 | if (daily && nextDay) { 138 | nextDay.querySelector('.date').textContent = 139 | app.daysOfWeek[(i + today) % 7]; 140 | nextDay.querySelector('.icon').classList.add(daily.icon); 141 | nextDay.querySelector('.temp-high .value').textContent = 142 | Math.round(daily.temperatureMax); 143 | nextDay.querySelector('.temp-low .value').textContent = 144 | Math.round(daily.temperatureMin); 145 | } 146 | } 147 | if (app.isLoading) { 148 | app.spinner.setAttribute('hidden', true); 149 | app.container.removeAttribute('hidden'); 150 | app.isLoading = false; 151 | } 152 | }; 153 | 154 | 155 | /***************************************************************************** 156 | * 157 | * Methods for dealing with the model 158 | * 159 | ****************************************************************************/ 160 | 161 | // Gets a forecast for a specific city and update the card with the data 162 | app.getForecast = function(key, label) { 163 | var url = weatherAPIUrlBase + key + '.json'; 164 | if ('caches' in window) { 165 | caches.match(url).then(function(response) { 166 | if (response) { 167 | response.json().then(function(json) { 168 | json.key = key; 169 | json.label = label; 170 | app.updateForecastCard(json); 171 | }); 172 | } 173 | }); 174 | } 175 | // Make the XHR to get the data, then update the card 176 | var request = new XMLHttpRequest(); 177 | request.onreadystatechange = function() { 178 | if (request.readyState === XMLHttpRequest.DONE) { 179 | if (request.status === 200) { 180 | var response = JSON.parse(request.response); 181 | response.key = key; 182 | response.label = label; 183 | app.updateForecastCard(response); 184 | } 185 | } 186 | }; 187 | request.open('GET', url); 188 | request.send(); 189 | }; 190 | 191 | // Iterate all of the cards and attempt to get the latest forecast data 192 | app.updateForecasts = function() { 193 | var keys = Object.keys(app.visibleCards); 194 | keys.forEach(function(key) { 195 | app.getForecast(key); 196 | }); 197 | }; 198 | 199 | app.saveSelectedCities = function() { 200 | window.localforage.setItem('selectedCities', app.selectedCities); 201 | }; 202 | 203 | document.addEventListener('DOMContentLoaded', function() { 204 | window.localforage.getItem('selectedCities', function(err, cityList) { 205 | if (cityList) { 206 | app.selectedCities = cityList; 207 | app.selectedCities.forEach(function(city) { 208 | app.getForecast(city.key, city.label); 209 | }); 210 | } else { 211 | app.updateForecastCard(injectedForecast); 212 | app.selectedCities = [ 213 | {key: injectedForecast.key, label: injectedForecast.label} 214 | ]; 215 | app.saveSelectedCities(); 216 | } 217 | }); 218 | }); 219 | 220 | if ('serviceWorker' in navigator) { 221 | navigator.serviceWorker 222 | .register('/service-worker.js') 223 | .then(function() { 224 | console.log('Service Worker Registered'); 225 | }); 226 | } 227 | 228 | })(); 229 | -------------------------------------------------------------------------------- /2-36/styles/ud811.min.css: -------------------------------------------------------------------------------- 1 | .header,body{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;-webkit-box-direction:normal}*,.card,.loader #ud811Spinner{box-sizing:border-box}body,html{padding:0;margin:0;height:100%;width:100%;font-family:Helvetica,Verdana,sans-serif;font-weight:400;font-display:optional;color:#444;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html{overflow:hidden}body{display:flex;-webkit-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;background:#ececec}.header{width:100%;height:56px;color:#FFF;background:#3F51B5;position:fixed;font-size:20px;box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 2px 9px 1px rgba(0,0,0,.12),0 4px 2px -2px rgba(0,0,0,.2);padding:16px 16px 0;will-change:transform;display:flex;-webkit-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-transition:-webkit-transform 233ms cubic-bezier(0,0,.21,1) .1s;transition:-webkit-transform 233ms cubic-bezier(0,0,.21,1) .1s;transition:transform 233ms cubic-bezier(0,0,.21,1) .1s;transition:transform 233ms cubic-bezier(0,0,.21,1) .1s,-webkit-transform 233ms cubic-bezier(0,0,.21,1) .1s;z-index:1000}.header .headerButton{width:24px;height:24px;margin-right:16px;text-indent:-30000px;overflow:hidden;opacity:.54;-webkit-transition:opacity 333ms cubic-bezier(0,0,.21,1);transition:opacity 333ms cubic-bezier(0,0,.21,1);border:none;outline:0;cursor:pointer}.card,.dialog{border-radius:2px}.header #butRefresh{background:url(/images/ic_refresh_white_24px.svg) center center no-repeat}.header #butAdd{background:url(/images/ic_add_white_24px.svg) center center no-repeat}.header__title{font-weight:400;font-size:20px;margin:0;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.loader{left:50%;top:50%;position:fixed;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.loader #ud811Spinner{stroke:#673AB7;stroke-width:3px;-webkit-transform-origin:50%;transform-origin:50%;-webkit-animation:line 1.6s cubic-bezier(.4,0,.2,1) infinite,rotate 1.6s linear infinite;animation:line 1.6s cubic-bezier(.4,0,.2,1) infinite,rotate 1.6s linear infinite}@-webkit-keyframes rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(450deg);transform:rotate(450deg)}}@keyframes rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(450deg);transform:rotate(450deg)}}@-webkit-keyframes line{0%{stroke-dasharray:2,85.964;-webkit-transform:rotate(0);transform:rotate(0)}50%{stroke-dasharray:65.973,21.9911;stroke-dashoffset:0}100%{stroke-dasharray:2,85.964;stroke-dashoffset:-65.973;-webkit-transform:rotate(90deg);transform:rotate(90deg)}}@keyframes line{0%{stroke-dasharray:2,85.964;-webkit-transform:rotate(0);transform:rotate(0)}50%{stroke-dasharray:65.973,21.9911;stroke-dashoffset:0}100%{stroke-dasharray:2,85.964;stroke-dashoffset:-65.973;-webkit-transform:rotate(90deg);transform:rotate(90deg)}}.main{padding-top:60px;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.dialog-container{background:rgba(0,0,0,.57);position:fixed;left:0;top:0;width:100%;height:100%;opacity:0;pointer-events:none;will-change:opacity;-webkit-transition:opacity 333ms cubic-bezier(0,0,.21,1);transition:opacity 333ms cubic-bezier(0,0,.21,1)}.dialog-container--visible{opacity:1;pointer-events:auto}.dialog{background:#FFF;box-shadow:0 0 14px rgba(0,0,0,.24),0 14px 28px rgba(0,0,0,.48);min-width:280px;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) translateY(30px);transform:translate(-50%,-50%) translateY(30px);-webkit-transition:-webkit-transform 333ms cubic-bezier(0,0,.21,1) 50ms;transition:-webkit-transform 333ms cubic-bezier(0,0,.21,1) 50ms;transition:transform 333ms cubic-bezier(0,0,.21,1) 50ms;transition:transform 333ms cubic-bezier(0,0,.21,1) 50ms,-webkit-transform 333ms cubic-bezier(0,0,.21,1) 50ms;padding:24px}.card{padding:16px;position:relative;background:#fff;margin:16px;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.weather-forecast .location{font-size:1.75em}.weather-forecast .date,.weather-forecast .description{font-size:1.25em}.weather-forecast .current{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.weather-forecast .current .icon{width:128px;height:128px}.weather-forecast .current .visual{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:4em}.weather-forecast .current .visual .scale{font-size:.5em;vertical-align:super}.weather-forecast .current .description,.weather-forecast .current .visual{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.weather-forecast .current .feels-like:before{content:"Feels like: ";color:#888}.weather-forecast .current .wind:before{content:"Wind: ";color:#888}.weather-forecast .current .precip:before{content:"Precipitation: ";color:#888}.weather-forecast .current .humidity:before{content:"Humidity: ";color:#888}.weather-forecast .current .pollen:before{content:"Pollen Count: ";color:#888}.weather-forecast .current .pcount:before{content:"Pollen ";color:#888}.weather-forecast .future{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.weather-forecast .future .oneday{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.weather-forecast .future .oneday .icon{width:64px;height:64px;margin-left:auto;margin-right:auto}.weather-forecast .future .oneday .temp-high,.weather-forecast .future .oneday .temp-low{display:inline-block}.weather-forecast .future .oneday .temp-low{color:#888}.weather-forecast .icon{background-repeat:no-repeat;background-size:contain}.weather-forecast .icon.clear-day,.weather-forecast .icon.clear-night{background-image:url(/images/clear.png)}.weather-forecast .icon.rain{background-image:url(/images/rain.png)}.weather-forecast .icon.snow{background-image:url(/images/snow.png)}.weather-forecast .icon.sleet{background-image:url(/images/sleet.png)}.weather-forecast .icon.wind{background-image:url(/images/wind.png)}.weather-forecast .icon.fog{background-image:url(/images/fog.png)}.weather-forecast .icon.cloudy{background-image:url(/images/cloudy.png)}.weather-forecast .icon.partly-cloudy-day,.weather-forecast .icon.partly-cloudy-night{background-image:url(/images/partly-cloudy.png)}.weather-forecast .icon.thunderstorms{background-image:url(/images/thunderstorms.png)}@media (max-width:450px){.weather-forecast .date,.weather-forecast .description{font-size:.9em}.weather-forecast .current .icon{width:96px;height:96px}.weather-forecast .current .visual{font-size:3em}.weather-forecast .future .oneday .icon{width:32px;height:32px}} -------------------------------------------------------------------------------- /2-36/styles/ud811.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | padding: 0; 7 | margin: 0; 8 | height: 100%; 9 | width: 100%; 10 | font-family: 'Helvetica', 'Verdana', sans-serif; 11 | font-weight: 400; 12 | // Not implemented yet but is a nice solution for async loading fonts 13 | font-display: optional; 14 | color: #444; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | html { 20 | overflow: hidden; 21 | } 22 | 23 | body { 24 | display: flex; 25 | flex-direction: column; 26 | flex-wrap: nowrap; 27 | justify-content: flex-start; 28 | align-items: stretch; 29 | align-content: stretch; 30 | background: #ececec; 31 | } 32 | 33 | .header { 34 | width: 100%; 35 | height: 56px; 36 | color: #FFF; 37 | background: #3F51B5; 38 | position: fixed; 39 | font-size: 20px; 40 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 41 | 0 2px 9px 1px rgba(0, 0, 0, 0.12), 42 | 0 4px 2px -2px rgba(0, 0, 0, 0.2); 43 | padding: 16px 16px 0 16px; 44 | will-change: transform; 45 | display: flex; 46 | flex-direction: row; 47 | flex-wrap: nowrap; 48 | justify-content: flex-start; 49 | align-items: stretch; 50 | align-content: center; 51 | transition: transform 0.233s cubic-bezier(0,0,0.21,1) 0.1s; 52 | z-index: 1000; 53 | 54 | .headerButton { 55 | width: 24px; 56 | height: 24px; 57 | margin-right: 16px; 58 | text-indent: -30000px; 59 | overflow: hidden; 60 | opacity: 0.54; 61 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1); 62 | border: none; 63 | outline: none; 64 | cursor: pointer; 65 | } 66 | #butRefresh { 67 | background: url(/images/ic_refresh_white_24px.svg) center center no-repeat; 68 | } 69 | #butAdd { 70 | background: url(/images/ic_add_white_24px.svg) center center no-repeat; 71 | } 72 | 73 | } 74 | 75 | .header__title { 76 | font-weight: 400; 77 | font-size: 20px; 78 | margin: 0; 79 | flex: 1; 80 | } 81 | 82 | .loader { 83 | left: 50%; 84 | top: 50%; 85 | position: fixed; 86 | transform: translate(-50%, -50%); 87 | 88 | #ud811Spinner { 89 | box-sizing: border-box; 90 | stroke: #673AB7; 91 | stroke-width: 3px; 92 | transform-origin: 50%; 93 | 94 | animation: line 1.6s cubic-bezier(0.4, 0.0, 0.2, 1) infinite, 95 | rotate 1.6s linear infinite; 96 | } 97 | 98 | @keyframes rotate { 99 | 100 | from { 101 | transform: rotate(0) 102 | } 103 | 104 | to { 105 | transform: rotate(450deg); 106 | } 107 | } 108 | 109 | @keyframes line { 110 | 0% { 111 | stroke-dasharray: 2, 85.964; 112 | transform: rotate(0); 113 | } 114 | 115 | 50% { 116 | stroke-dasharray: 65.973, 21.9911; 117 | stroke-dashoffset: 0; 118 | } 119 | 120 | 100% { 121 | stroke-dasharray: 2, 85.964; 122 | stroke-dashoffset: -65.973; 123 | transform: rotate(90deg); 124 | } 125 | } 126 | } 127 | 128 | .main { 129 | padding-top: 60px; 130 | flex: 1; 131 | overflow-x: hidden; 132 | overflow-y: auto; 133 | -webkit-overflow-scrolling: touch; 134 | } 135 | 136 | .dialog-container { 137 | background: rgba(0,0,0,0.57); 138 | position: fixed; 139 | left: 0; 140 | top: 0; 141 | width: 100%; 142 | height: 100%; 143 | opacity: 0; 144 | pointer-events: none; 145 | will-change: opacity; 146 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1); 147 | } 148 | 149 | .dialog-container--visible { 150 | opacity: 1; 151 | pointer-events: auto; 152 | } 153 | 154 | .dialog { 155 | background: #FFF; 156 | border-radius: 2px; 157 | box-shadow: 0 0 14px rgba(0,0,0,.24), 158 | 0 14px 28px rgba(0,0,0,.48); 159 | min-width: 280px; 160 | position: absolute; 161 | left: 50%; 162 | top: 50%; 163 | transform: translate(-50%, -50%) translateY(30px); 164 | transition: transform 0.333s cubic-bezier(0,0,0.21,1) 0.05s; 165 | padding: 24px; 166 | } 167 | 168 | .card { 169 | padding: 16px; 170 | position: relative; 171 | box-sizing: border-box; 172 | background: #fff; 173 | border-radius: 2px; 174 | margin: 16px; 175 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 176 | 0 3px 1px -2px rgba(0,0,0,.2), 177 | 0 1px 5px 0 rgba(0,0,0,.12); 178 | } 179 | 180 | .weather-forecast { 181 | 182 | .location { 183 | font-size: 1.75em; 184 | } 185 | .date, .description { 186 | font-size: 1.25em; 187 | } 188 | 189 | .current { 190 | display: flex; 191 | 192 | .icon { 193 | width: 128px; 194 | height: 128px; 195 | } 196 | 197 | .visual { 198 | display: flex; 199 | font-size: 4em; 200 | 201 | .scale { 202 | font-size: 0.5em; 203 | vertical-align: super; 204 | } 205 | } 206 | 207 | .visual, .description { 208 | flex-grow: 1; 209 | } 210 | 211 | .feels-like:before { 212 | content: "Feels like: "; 213 | color: #888; 214 | } 215 | 216 | .wind:before { 217 | content: "Wind: "; 218 | color: #888; 219 | } 220 | 221 | .precip:before { 222 | content: "Precipitation: "; 223 | color: #888; 224 | } 225 | 226 | .humidity:before { 227 | content: "Humidity: "; 228 | color: #888; 229 | } 230 | 231 | .pollen:before { 232 | content: "Pollen Count: "; 233 | color: #888; 234 | } 235 | 236 | .pcount:before { 237 | content: "Pollen "; 238 | color: #888; 239 | } 240 | } 241 | 242 | .future { 243 | display: flex; 244 | 245 | .oneday { 246 | flex-grow: 1; 247 | text-align: center; 248 | 249 | .icon { 250 | width: 64px; 251 | height: 64px; 252 | margin-left: auto; 253 | margin-right: auto; 254 | } 255 | 256 | .temp-high, .temp-low { 257 | display: inline-block; 258 | } 259 | 260 | .temp-low { 261 | color: #888; 262 | } 263 | } 264 | } 265 | 266 | .icon { 267 | background-repeat: no-repeat; 268 | background-size: contain; 269 | &.clear-day { background-image: url('/images/clear.png'); } 270 | &.clear-night { background-image: url('/images/clear.png'); } 271 | &.rain { background-image: url('/images/rain.png'); } 272 | &.snow { background-image: url('/images/snow.png'); } 273 | &.sleet { background-image: url('/images/sleet.png'); } 274 | &.wind { background-image: url('/images/wind.png'); } 275 | &.fog { background-image: url('/images/fog.png'); } 276 | &.cloudy { background-image: url('/images/cloudy.png'); } 277 | &.partly-cloudy-day { background-image: url('/images/partly-cloudy.png'); } 278 | &.partly-cloudy-night { background-image: url('/images/partly-cloudy.png'); } 279 | &.thunderstorms { background-image: url('/images/thunderstorms.png'); } 280 | } 281 | } 282 | 283 | @media (max-width: 450px) { 284 | .weather-forecast { 285 | .date, .description { 286 | font-size: 0.9em; 287 | } 288 | .current { 289 | .icon { 290 | width: 96px; 291 | height: 96px; 292 | } 293 | .visual { 294 | font-size: 3em; 295 | } 296 | } 297 | .future { 298 | .oneday { 299 | .icon { 300 | width: 32px; 301 | height: 32px; 302 | } 303 | } 304 | } 305 | 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /3-12/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/favicon.ico -------------------------------------------------------------------------------- /3-12/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firebase": "udacity-ud811", 3 | "public": ".", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } -------------------------------------------------------------------------------- /3-12/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserSync = require('browser-sync'); 3 | var reload = browserSync.reload; 4 | var sass = require('gulp-sass'); 5 | var autoprefixer = require('gulp-autoprefixer'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var del = require('del'); 9 | var swPrecache = require('sw-precache'); 10 | 11 | gulp.task('sass', function () { 12 | return gulp 13 | .src('./styles/*.scss') 14 | .pipe(sass()) 15 | .pipe(autoprefixer()) 16 | .pipe(gulp.dest('./styles/')) 17 | .pipe(minifyCss({})) 18 | .pipe(rename({suffix: '.min'})) 19 | .pipe(gulp.dest('./styles/')); 20 | }); 21 | 22 | gulp.task('generate-sw', function() { 23 | var swOptions = { 24 | staticFileGlobs: [ 25 | './index.html', 26 | './images/*.{png,svg,gif,jpg}', 27 | './scripts/*.js', 28 | './styles/*.css' 29 | ], 30 | stripPrefix: '.', 31 | runtimeCaching: [{ 32 | urlPattern: /^https:\/\/publicdata-weather\.firebaseio\.com/, 33 | handler: 'networkFirst', 34 | options: { 35 | cache: { 36 | name: 'weatherData-v3' 37 | } 38 | } 39 | }] 40 | }; 41 | return swPrecache.write('./service-worker.js', swOptions); 42 | }); 43 | 44 | gulp.task('serve', ['generate-sw'], function() { 45 | gulp.watch('./styles/*.scss', ['sass']); 46 | browserSync({ 47 | notify: false, 48 | logPrefix: 'weatherPWA', 49 | server: ['.'], 50 | open: false 51 | }); 52 | gulp.watch([ 53 | './*.html', 54 | './scripts/*.js', 55 | './styles/*.css', 56 | '!./service-worker.js', 57 | '!./gulpfile.js' 58 | ], ['generate-sw'], browserSync.reload); 59 | }); 60 | 61 | gulp.task('default', ['serve']); 62 | -------------------------------------------------------------------------------- /3-12/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/clear.png -------------------------------------------------------------------------------- /3-12/images/cloudy-scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/cloudy-scattered-showers.png -------------------------------------------------------------------------------- /3-12/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/cloudy.png -------------------------------------------------------------------------------- /3-12/images/cloudy_s_sunny-assets/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/cloudy_s_sunny-assets/cloudy_s_sunny.png -------------------------------------------------------------------------------- /3-12/images/cloudy_s_sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/cloudy_s_sunny.png -------------------------------------------------------------------------------- /3-12/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/fog.png -------------------------------------------------------------------------------- /3-12/images/ic_add_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /3-12/images/ic_refresh_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /3-12/images/icons/apple-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/apple-120.png -------------------------------------------------------------------------------- /3-12/images/icons/apple-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/apple-152.png -------------------------------------------------------------------------------- /3-12/images/icons/apple-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/apple-167.png -------------------------------------------------------------------------------- /3-12/images/icons/apple-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/apple-180.png -------------------------------------------------------------------------------- /3-12/images/icons/apple-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/apple-60.png -------------------------------------------------------------------------------- /3-12/images/icons/apple-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/apple-76.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-256x256.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-48x48.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /3-12/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /3-12/images/partly-cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/partly-cloudy.png -------------------------------------------------------------------------------- /3-12/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/rain.png -------------------------------------------------------------------------------- /3-12/images/scattered-showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/scattered-showers.png -------------------------------------------------------------------------------- /3-12/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/sleet.png -------------------------------------------------------------------------------- /3-12/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/snow.png -------------------------------------------------------------------------------- /3-12/images/thunderstorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/thunderstorm.png -------------------------------------------------------------------------------- /3-12/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ud811/eeca2499201fd4635bdac24ee12de08a5e88c9bb/3-12/images/wind.png -------------------------------------------------------------------------------- /3-12/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Weather 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |

Weather PWA

28 | 29 | 30 |
31 | 32 |
33 | 130 |
131 | 132 |
133 |
134 |
Add new city
135 |
136 | 144 |
145 |
146 | 147 | 148 |
149 |
150 |
151 | 152 |
153 | 154 | 155 | 156 |
157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /3-12/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Weather", 3 | "short_name": "Weather", 4 | "icons": [{ 5 | "src": "/images/icons/icon-48x48.png", 6 | "sizes": "48x48", 7 | "type": "image/png" 8 | }, { 9 | "src": "/images/icons/icon-96x96.png", 10 | "sizes": "96x96", 11 | "type": "image/png" 12 | }, { 13 | "src": "/images/icons/icon-128x128.png", 14 | "sizes": "128x128", 15 | "type": "image/png" 16 | }, { 17 | "src": "/images/icons/icon-144x144.png", 18 | "sizes": "144x144", 19 | "type": "image/png" 20 | }, { 21 | "src": "/images/touch/icon-192x192.png", 22 | "sizes": "192x192", 23 | "type": "image/png" 24 | }, { 25 | "src": "/images/touch/icon-256x256.png", 26 | "sizes": "256x256", 27 | "type": "image/png" 28 | }, { 29 | "src": "/images/touch/icon-384x384.png", 30 | "sizes": "384x384", 31 | "type": "image/png" 32 | }, { 33 | "src": "/images/touch/icon-512x512.png", 34 | "sizes": "512x512", 35 | "type": "image/png" 36 | }], 37 | "start_url": "/index.html", 38 | "display": "standalone", 39 | "background_color": "#3E4EB8", 40 | "theme_color": "#2F3BA2" 41 | } 42 | -------------------------------------------------------------------------------- /3-12/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WeatherPWA-UD811", 3 | "description": "Your first Progressive Web App for Udacity UD811", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache2", 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "browser-sync": "^2.11.2", 10 | "del": "^2.2.0", 11 | "gulp": "^3.9.1", 12 | "gulp-autoprefixer": "^3.1.0", 13 | "gulp-minify-css": "^1.2.4", 14 | "gulp-rename": "^1.2.2", 15 | "gulp-sass": "^2.2.0", 16 | "sw-precache": "^3.1.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /3-12/scripts/app.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | var injectedForecast = { 6 | key: 'newyork', 7 | label: 'New York, NY', 8 | currently: { 9 | time: 1453489481, 10 | summary: 'Clear', 11 | icon: 'partly-cloudy-day', 12 | temperature: 52.74, 13 | apparentTemperature: 74.34, 14 | precipProbability: 0.20, 15 | humidity: 0.77, 16 | windBearing: 125, 17 | windSpeed: 1.52 18 | }, 19 | daily: { 20 | data: [ 21 | {icon: 'clear-day', temperatureMax: 55, temperatureMin: 34}, 22 | {icon: 'rain', temperatureMax: 55, temperatureMin: 34}, 23 | {icon: 'snow', temperatureMax: 55, temperatureMin: 34}, 24 | {icon: 'sleet', temperatureMax: 55, temperatureMin: 34}, 25 | {icon: 'fog', temperatureMax: 55, temperatureMin: 34}, 26 | {icon: 'wind', temperatureMax: 55, temperatureMin: 34}, 27 | {icon: 'partly-cloudy-day', temperatureMax: 55, temperatureMin: 34} 28 | ] 29 | } 30 | }; 31 | 32 | var weatherAPIUrlBase = 'https://publicdata-weather.firebaseio.com/'; 33 | 34 | var app = { 35 | isLoading: true, 36 | visibleCards: {}, 37 | selectedCities: [], 38 | spinner: document.querySelector('.loader'), 39 | cardTemplate: document.querySelector('.cardTemplate'), 40 | container: document.querySelector('.main'), 41 | addDialog: document.querySelector('.dialog-container'), 42 | daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 43 | }; 44 | 45 | 46 | /***************************************************************************** 47 | * 48 | * Event listeners for UI elements 49 | * 50 | ****************************************************************************/ 51 | 52 | /* Event listener for refresh button */ 53 | document.getElementById('butRefresh').addEventListener('click', function() { 54 | app.updateForecasts(); 55 | }); 56 | 57 | /* Event listener for add new city button */ 58 | document.getElementById('butAdd').addEventListener('click', function() { 59 | // Open/show the add new city dialog 60 | app.toggleAddDialog(true); 61 | }); 62 | 63 | /* Event listener for add city button in add city dialog */ 64 | document.getElementById('butAddCity').addEventListener('click', function() { 65 | var select = document.getElementById('selectCityToAdd'); 66 | var selected = select.options[select.selectedIndex]; 67 | var key = selected.value; 68 | var label = selected.textContent; 69 | app.getForecast(key, label); 70 | app.selectedCities.push({key: key, label: label}); 71 | app.saveSelectedCities(); 72 | app.toggleAddDialog(false); 73 | }); 74 | 75 | /* Event listener for cancel button in add city dialog */ 76 | document.getElementById('butAddCancel').addEventListener('click', function() { 77 | app.toggleAddDialog(false); 78 | }); 79 | 80 | 81 | /***************************************************************************** 82 | * 83 | * Methods to update/refresh the UI 84 | * 85 | ****************************************************************************/ 86 | 87 | // Toggles the visibility of the add new city dialog. 88 | app.toggleAddDialog = function(visible) { 89 | if (visible) { 90 | app.addDialog.classList.add('dialog-container--visible'); 91 | } else { 92 | app.addDialog.classList.remove('dialog-container--visible'); 93 | } 94 | }; 95 | 96 | // Updates a weather card with the latest weather forecast. If the card 97 | // doesn't already exist, it's cloned from the template. 98 | app.updateForecastCard = function(data) { 99 | var card = app.visibleCards[data.key]; 100 | if (!card) { 101 | card = app.cardTemplate.cloneNode(true); 102 | card.classList.remove('cardTemplate'); 103 | card.querySelector('.location').textContent = data.label; 104 | card.removeAttribute('hidden'); 105 | app.container.appendChild(card); 106 | app.visibleCards[data.key] = card; 107 | } 108 | 109 | // Verify data is newer than what we already have, if not, bail. 110 | var dateElem = card.querySelector('.date'); 111 | if (dateElem.getAttribute('data-dt') >= data.currently.time) { 112 | return; 113 | } 114 | 115 | dateElem.setAttribute('data-dt', data.currently.time); 116 | dateElem.textContent = new Date(data.currently.time * 1000); 117 | card.querySelector('.description').textContent = data.currently.summary; 118 | card.querySelector('.current .icon').classList.add(data.currently.icon); 119 | card.querySelector('.current .temperature .value').textContent = 120 | Math.round(data.currently.temperature); 121 | card.querySelector('.current .feels-like .value').textContent = 122 | Math.round(data.currently.apparentTemperature); 123 | card.querySelector('.current .precip').textContent = 124 | Math.round(data.currently.precipProbability * 100) + '%'; 125 | card.querySelector('.current .humidity').textContent = 126 | Math.round(data.currently.humidity * 100) + '%'; 127 | card.querySelector('.current .wind .value').textContent = 128 | Math.round(data.currently.windSpeed); 129 | card.querySelector('.current .wind .direction').textContent = 130 | data.currently.windBearing; 131 | var nextDays = card.querySelectorAll('.future .oneday'); 132 | var today = new Date(); 133 | today = today.getDay(); 134 | for (var i = 0; i < 7; i++) { 135 | var nextDay = nextDays[i]; 136 | var daily = data.daily.data[i]; 137 | if (daily && nextDay) { 138 | nextDay.querySelector('.date').textContent = 139 | app.daysOfWeek[(i + today) % 7]; 140 | nextDay.querySelector('.icon').classList.add(daily.icon); 141 | nextDay.querySelector('.temp-high .value').textContent = 142 | Math.round(daily.temperatureMax); 143 | nextDay.querySelector('.temp-low .value').textContent = 144 | Math.round(daily.temperatureMin); 145 | } 146 | } 147 | if (app.isLoading) { 148 | app.spinner.setAttribute('hidden', true); 149 | app.container.removeAttribute('hidden'); 150 | app.isLoading = false; 151 | } 152 | }; 153 | 154 | 155 | /***************************************************************************** 156 | * 157 | * Methods for dealing with the model 158 | * 159 | ****************************************************************************/ 160 | 161 | // Gets a forecast for a specific city and update the card with the data 162 | app.getForecast = function(key, label) { 163 | var url = weatherAPIUrlBase + key + '.json'; 164 | if ('caches' in window) { 165 | caches.match(url).then(function(response) { 166 | if (response) { 167 | response.json().then(function(json) { 168 | json.key = key; 169 | json.label = label; 170 | app.updateForecastCard(json); 171 | }); 172 | } 173 | }); 174 | } 175 | // Make the XHR to get the data, then update the card 176 | var request = new XMLHttpRequest(); 177 | request.onreadystatechange = function() { 178 | if (request.readyState === XMLHttpRequest.DONE) { 179 | if (request.status === 200) { 180 | var response = JSON.parse(request.response); 181 | response.key = key; 182 | response.label = label; 183 | app.updateForecastCard(response); 184 | } 185 | } 186 | }; 187 | request.open('GET', url); 188 | request.send(); 189 | }; 190 | 191 | // Iterate all of the cards and attempt to get the latest forecast data 192 | app.updateForecasts = function() { 193 | var keys = Object.keys(app.visibleCards); 194 | keys.forEach(function(key) { 195 | app.getForecast(key); 196 | }); 197 | }; 198 | 199 | app.saveSelectedCities = function() { 200 | window.localforage.setItem('selectedCities', app.selectedCities); 201 | }; 202 | 203 | document.addEventListener('DOMContentLoaded', function() { 204 | window.localforage.getItem('selectedCities', function(err, cityList) { 205 | if (cityList) { 206 | app.selectedCities = cityList; 207 | app.selectedCities.forEach(function(city) { 208 | app.getForecast(city.key, city.label); 209 | }); 210 | } else { 211 | app.updateForecastCard(injectedForecast); 212 | app.selectedCities = [ 213 | {key: injectedForecast.key, label: injectedForecast.label} 214 | ]; 215 | app.saveSelectedCities(); 216 | } 217 | }); 218 | }); 219 | 220 | if ('serviceWorker' in navigator) { 221 | navigator.serviceWorker 222 | .register('/service-worker.js') 223 | .then(function() { 224 | console.log('Service Worker Registered'); 225 | }); 226 | } 227 | 228 | })(); 229 | -------------------------------------------------------------------------------- /3-12/styles/ud811.min.css: -------------------------------------------------------------------------------- 1 | .header,body{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;-webkit-box-direction:normal}*,.card,.loader #ud811Spinner{box-sizing:border-box}body,html{padding:0;margin:0;height:100%;width:100%;font-family:Helvetica,Verdana,sans-serif;font-weight:400;font-display:optional;color:#444;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html{overflow:hidden}body{display:flex;-webkit-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;background:#ececec}.header{width:100%;height:56px;color:#FFF;background:#3F51B5;position:fixed;font-size:20px;box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 2px 9px 1px rgba(0,0,0,.12),0 4px 2px -2px rgba(0,0,0,.2);padding:16px 16px 0;will-change:transform;display:flex;-webkit-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-transition:-webkit-transform 233ms cubic-bezier(0,0,.21,1) .1s;transition:-webkit-transform 233ms cubic-bezier(0,0,.21,1) .1s;transition:transform 233ms cubic-bezier(0,0,.21,1) .1s;transition:transform 233ms cubic-bezier(0,0,.21,1) .1s,-webkit-transform 233ms cubic-bezier(0,0,.21,1) .1s;z-index:1000}.header .headerButton{width:24px;height:24px;margin-right:16px;text-indent:-30000px;overflow:hidden;opacity:.54;-webkit-transition:opacity 333ms cubic-bezier(0,0,.21,1);transition:opacity 333ms cubic-bezier(0,0,.21,1);border:none;outline:0;cursor:pointer}.card,.dialog{border-radius:2px}.header #butRefresh{background:url(/images/ic_refresh_white_24px.svg) center center no-repeat}.header #butAdd{background:url(/images/ic_add_white_24px.svg) center center no-repeat}.header__title{font-weight:400;font-size:20px;margin:0;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.loader{left:50%;top:50%;position:fixed;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.loader #ud811Spinner{stroke:#673AB7;stroke-width:3px;-webkit-transform-origin:50%;transform-origin:50%;-webkit-animation:line 1.6s cubic-bezier(.4,0,.2,1) infinite,rotate 1.6s linear infinite;animation:line 1.6s cubic-bezier(.4,0,.2,1) infinite,rotate 1.6s linear infinite}@-webkit-keyframes rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(450deg);transform:rotate(450deg)}}@keyframes rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(450deg);transform:rotate(450deg)}}@-webkit-keyframes line{0%{stroke-dasharray:2,85.964;-webkit-transform:rotate(0);transform:rotate(0)}50%{stroke-dasharray:65.973,21.9911;stroke-dashoffset:0}100%{stroke-dasharray:2,85.964;stroke-dashoffset:-65.973;-webkit-transform:rotate(90deg);transform:rotate(90deg)}}@keyframes line{0%{stroke-dasharray:2,85.964;-webkit-transform:rotate(0);transform:rotate(0)}50%{stroke-dasharray:65.973,21.9911;stroke-dashoffset:0}100%{stroke-dasharray:2,85.964;stroke-dashoffset:-65.973;-webkit-transform:rotate(90deg);transform:rotate(90deg)}}.main{padding-top:60px;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.dialog-container{background:rgba(0,0,0,.57);position:fixed;left:0;top:0;width:100%;height:100%;opacity:0;pointer-events:none;will-change:opacity;-webkit-transition:opacity 333ms cubic-bezier(0,0,.21,1);transition:opacity 333ms cubic-bezier(0,0,.21,1)}.dialog-container--visible{opacity:1;pointer-events:auto}.dialog{background:#FFF;box-shadow:0 0 14px rgba(0,0,0,.24),0 14px 28px rgba(0,0,0,.48);min-width:280px;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) translateY(30px);transform:translate(-50%,-50%) translateY(30px);-webkit-transition:-webkit-transform 333ms cubic-bezier(0,0,.21,1) 50ms;transition:-webkit-transform 333ms cubic-bezier(0,0,.21,1) 50ms;transition:transform 333ms cubic-bezier(0,0,.21,1) 50ms;transition:transform 333ms cubic-bezier(0,0,.21,1) 50ms,-webkit-transform 333ms cubic-bezier(0,0,.21,1) 50ms;padding:24px}.card{padding:16px;position:relative;background:#fff;margin:16px;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.weather-forecast .location{font-size:1.75em}.weather-forecast .date,.weather-forecast .description{font-size:1.25em}.weather-forecast .current{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.weather-forecast .current .icon{width:128px;height:128px}.weather-forecast .current .visual{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:4em}.weather-forecast .current .visual .scale{font-size:.5em;vertical-align:super}.weather-forecast .current .description,.weather-forecast .current .visual{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.weather-forecast .current .feels-like:before{content:"Feels like: ";color:#888}.weather-forecast .current .wind:before{content:"Wind: ";color:#888}.weather-forecast .current .precip:before{content:"Precipitation: ";color:#888}.weather-forecast .current .humidity:before{content:"Humidity: ";color:#888}.weather-forecast .current .pollen:before{content:"Pollen Count: ";color:#888}.weather-forecast .current .pcount:before{content:"Pollen ";color:#888}.weather-forecast .future{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.weather-forecast .future .oneday{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.weather-forecast .future .oneday .icon{width:64px;height:64px;margin-left:auto;margin-right:auto}.weather-forecast .future .oneday .temp-high,.weather-forecast .future .oneday .temp-low{display:inline-block}.weather-forecast .future .oneday .temp-low{color:#888}.weather-forecast .icon{background-repeat:no-repeat;background-size:contain}.weather-forecast .icon.clear-day,.weather-forecast .icon.clear-night{background-image:url(/images/clear.png)}.weather-forecast .icon.rain{background-image:url(/images/rain.png)}.weather-forecast .icon.snow{background-image:url(/images/snow.png)}.weather-forecast .icon.sleet{background-image:url(/images/sleet.png)}.weather-forecast .icon.wind{background-image:url(/images/wind.png)}.weather-forecast .icon.fog{background-image:url(/images/fog.png)}.weather-forecast .icon.cloudy{background-image:url(/images/cloudy.png)}.weather-forecast .icon.partly-cloudy-day,.weather-forecast .icon.partly-cloudy-night{background-image:url(/images/partly-cloudy.png)}.weather-forecast .icon.thunderstorms{background-image:url(/images/thunderstorms.png)}@media (max-width:450px){.weather-forecast .date,.weather-forecast .description{font-size:.9em}.weather-forecast .current .icon{width:96px;height:96px}.weather-forecast .current .visual{font-size:3em}.weather-forecast .future .oneday .icon{width:32px;height:32px}} -------------------------------------------------------------------------------- /3-12/styles/ud811.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | padding: 0; 7 | margin: 0; 8 | height: 100%; 9 | width: 100%; 10 | font-family: 'Helvetica', 'Verdana', sans-serif; 11 | font-weight: 400; 12 | // Not implemented yet but is a nice solution for async loading fonts 13 | font-display: optional; 14 | color: #444; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | html { 20 | overflow: hidden; 21 | } 22 | 23 | body { 24 | display: flex; 25 | flex-direction: column; 26 | flex-wrap: nowrap; 27 | justify-content: flex-start; 28 | align-items: stretch; 29 | align-content: stretch; 30 | background: #ececec; 31 | } 32 | 33 | .header { 34 | width: 100%; 35 | height: 56px; 36 | color: #FFF; 37 | background: #3F51B5; 38 | position: fixed; 39 | font-size: 20px; 40 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 41 | 0 2px 9px 1px rgba(0, 0, 0, 0.12), 42 | 0 4px 2px -2px rgba(0, 0, 0, 0.2); 43 | padding: 16px 16px 0 16px; 44 | will-change: transform; 45 | display: flex; 46 | flex-direction: row; 47 | flex-wrap: nowrap; 48 | justify-content: flex-start; 49 | align-items: stretch; 50 | align-content: center; 51 | transition: transform 0.233s cubic-bezier(0,0,0.21,1) 0.1s; 52 | z-index: 1000; 53 | 54 | .headerButton { 55 | width: 24px; 56 | height: 24px; 57 | margin-right: 16px; 58 | text-indent: -30000px; 59 | overflow: hidden; 60 | opacity: 0.54; 61 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1); 62 | border: none; 63 | outline: none; 64 | cursor: pointer; 65 | } 66 | #butRefresh { 67 | background: url(/images/ic_refresh_white_24px.svg) center center no-repeat; 68 | } 69 | #butAdd { 70 | background: url(/images/ic_add_white_24px.svg) center center no-repeat; 71 | } 72 | 73 | } 74 | 75 | .header__title { 76 | font-weight: 400; 77 | font-size: 20px; 78 | margin: 0; 79 | flex: 1; 80 | } 81 | 82 | .loader { 83 | left: 50%; 84 | top: 50%; 85 | position: fixed; 86 | transform: translate(-50%, -50%); 87 | 88 | #ud811Spinner { 89 | box-sizing: border-box; 90 | stroke: #673AB7; 91 | stroke-width: 3px; 92 | transform-origin: 50%; 93 | 94 | animation: line 1.6s cubic-bezier(0.4, 0.0, 0.2, 1) infinite, 95 | rotate 1.6s linear infinite; 96 | } 97 | 98 | @keyframes rotate { 99 | 100 | from { 101 | transform: rotate(0) 102 | } 103 | 104 | to { 105 | transform: rotate(450deg); 106 | } 107 | } 108 | 109 | @keyframes line { 110 | 0% { 111 | stroke-dasharray: 2, 85.964; 112 | transform: rotate(0); 113 | } 114 | 115 | 50% { 116 | stroke-dasharray: 65.973, 21.9911; 117 | stroke-dashoffset: 0; 118 | } 119 | 120 | 100% { 121 | stroke-dasharray: 2, 85.964; 122 | stroke-dashoffset: -65.973; 123 | transform: rotate(90deg); 124 | } 125 | } 126 | } 127 | 128 | .main { 129 | padding-top: 60px; 130 | flex: 1; 131 | overflow-x: hidden; 132 | overflow-y: auto; 133 | -webkit-overflow-scrolling: touch; 134 | } 135 | 136 | .dialog-container { 137 | background: rgba(0,0,0,0.57); 138 | position: fixed; 139 | left: 0; 140 | top: 0; 141 | width: 100%; 142 | height: 100%; 143 | opacity: 0; 144 | pointer-events: none; 145 | will-change: opacity; 146 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1); 147 | } 148 | 149 | .dialog-container--visible { 150 | opacity: 1; 151 | pointer-events: auto; 152 | } 153 | 154 | .dialog { 155 | background: #FFF; 156 | border-radius: 2px; 157 | box-shadow: 0 0 14px rgba(0,0,0,.24), 158 | 0 14px 28px rgba(0,0,0,.48); 159 | min-width: 280px; 160 | position: absolute; 161 | left: 50%; 162 | top: 50%; 163 | transform: translate(-50%, -50%) translateY(30px); 164 | transition: transform 0.333s cubic-bezier(0,0,0.21,1) 0.05s; 165 | padding: 24px; 166 | } 167 | 168 | .card { 169 | padding: 16px; 170 | position: relative; 171 | box-sizing: border-box; 172 | background: #fff; 173 | border-radius: 2px; 174 | margin: 16px; 175 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 176 | 0 3px 1px -2px rgba(0,0,0,.2), 177 | 0 1px 5px 0 rgba(0,0,0,.12); 178 | } 179 | 180 | .weather-forecast { 181 | 182 | .location { 183 | font-size: 1.75em; 184 | } 185 | .date, .description { 186 | font-size: 1.25em; 187 | } 188 | 189 | .current { 190 | display: flex; 191 | 192 | .icon { 193 | width: 128px; 194 | height: 128px; 195 | } 196 | 197 | .visual { 198 | display: flex; 199 | font-size: 4em; 200 | 201 | .scale { 202 | font-size: 0.5em; 203 | vertical-align: super; 204 | } 205 | } 206 | 207 | .visual, .description { 208 | flex-grow: 1; 209 | } 210 | 211 | .feels-like:before { 212 | content: "Feels like: "; 213 | color: #888; 214 | } 215 | 216 | .wind:before { 217 | content: "Wind: "; 218 | color: #888; 219 | } 220 | 221 | .precip:before { 222 | content: "Precipitation: "; 223 | color: #888; 224 | } 225 | 226 | .humidity:before { 227 | content: "Humidity: "; 228 | color: #888; 229 | } 230 | 231 | .pollen:before { 232 | content: "Pollen Count: "; 233 | color: #888; 234 | } 235 | 236 | .pcount:before { 237 | content: "Pollen "; 238 | color: #888; 239 | } 240 | } 241 | 242 | .future { 243 | display: flex; 244 | 245 | .oneday { 246 | flex-grow: 1; 247 | text-align: center; 248 | 249 | .icon { 250 | width: 64px; 251 | height: 64px; 252 | margin-left: auto; 253 | margin-right: auto; 254 | } 255 | 256 | .temp-high, .temp-low { 257 | display: inline-block; 258 | } 259 | 260 | .temp-low { 261 | color: #888; 262 | } 263 | } 264 | } 265 | 266 | .icon { 267 | background-repeat: no-repeat; 268 | background-size: contain; 269 | &.clear-day { background-image: url('/images/clear.png'); } 270 | &.clear-night { background-image: url('/images/clear.png'); } 271 | &.rain { background-image: url('/images/rain.png'); } 272 | &.snow { background-image: url('/images/snow.png'); } 273 | &.sleet { background-image: url('/images/sleet.png'); } 274 | &.wind { background-image: url('/images/wind.png'); } 275 | &.fog { background-image: url('/images/fog.png'); } 276 | &.cloudy { background-image: url('/images/cloudy.png'); } 277 | &.partly-cloudy-day { background-image: url('/images/partly-cloudy.png'); } 278 | &.partly-cloudy-night { background-image: url('/images/partly-cloudy.png'); } 279 | &.thunderstorms { background-image: url('/images/thunderstorms.png'); } 280 | } 281 | } 282 | 283 | @media (max-width: 450px) { 284 | .weather-forecast { 285 | .date, .description { 286 | font-size: 0.9em; 287 | } 288 | .current { 289 | .icon { 290 | width: 96px; 291 | height: 96px; 292 | } 293 | .visual { 294 | font-size: 3em; 295 | } 296 | } 297 | .future { 298 | .oneday { 299 | .icon { 300 | width: 32px; 301 | height: 32px; 302 | } 303 | } 304 | } 305 | 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @udacity/active-public-content -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Udacity 4 | Portions copyright (c) 2015-2016 Google Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /forecast-io_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var http = require('http'); 4 | var https = require('https'); 5 | 6 | var port = 9800; 7 | var forecastIOKey = 'PUT_FORECAST_IO_API_KEY_HERE'; 8 | var cachedForecasts = {}; 9 | 10 | var cityToLatLon = { 11 | 'austin': '30.3079823,-97.8938293', 12 | 'boston': '42.313479,-71.1273685', 13 | 'chicago': '41.8339032,-87.8725822', 14 | 'bangalore': '12.9539598,77.4905096', 15 | 'buenosaires': '-34.6158036,-58.5035321', 16 | 'capetown': '-33.9137107,18.0942365', 17 | 'london': '51.5285578,-0.2420244', 18 | 'mcmurdo': '-77.8401137,166.6441437', 19 | 'newyork': '40.7128,-74.0059', 20 | 'portland': '45.5425909,-122.7948484', 21 | 'sanfrancisco': '37.7578149,-122.5078116', 22 | 'seattle': '47.6149938,-122.4763319', 23 | 'sydney': '-33.7966053,150.6415579', 24 | 'tokyo': '35.6732615,139.5699601' 25 | }; 26 | 27 | function handleRequest(request, response) { 28 | response.setHeader('Access-Control-Allow-Origin', '*'); 29 | var cityName = request.url.substring(1).replace('.json', ''); 30 | var cityCoords = cityToLatLon[cityName]; 31 | if (!cityCoords) { 32 | response.statusCode = 404; 33 | response.end(); 34 | console.log('404 ', request.url); 35 | return; 36 | } 37 | console.log('REQ ', request.url); 38 | response.setHeader('Content-Type', 'application/json'); 39 | response.setHeader('X-Powered-By', 'fIO.proxy'); 40 | var cachedForecast = cachedForecasts[cityName]; 41 | if (cachedForecast && Date.now() < cachedForecast.expiresAt) { 42 | response.end(JSON.stringify(cachedForecast)); 43 | console.log('RESP ', cityName, '[cache]'); 44 | } else { 45 | getForecast(cityCoords, function(freshForecast) { 46 | response.end(freshForecast); 47 | var forecast = JSON.parse(freshForecast); 48 | forecast.expiresAt = Date.now() + (1000 * 60); 49 | cachedForecasts[cityName] = forecast; 50 | console.log('RESP ', cityName, '[network]'); 51 | }); 52 | } 53 | } 54 | 55 | function getForecast(coords, callback) { 56 | var options = { 57 | host: 'api.forecast.io', 58 | path: '/forecast/' + forecastIOKey + '/' + coords 59 | }; 60 | https.request(options, function(response) { 61 | var resp = ''; 62 | response.on('data', function(chunk) { 63 | resp += chunk; 64 | }); 65 | response.on('end', function() { 66 | if (callback) { 67 | callback(resp); 68 | } 69 | }); 70 | }).end(); 71 | } 72 | 73 | var httpServer = http.createServer(handleRequest); 74 | 75 | httpServer.listen(port, function() { 76 | console.log('Forecast.io proxy server started...', port); 77 | }); 78 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Udacity UD811 Course Material 2 | 3 | These are the course materials for UD811. 4 | 5 | 6 | ## Firebase Weather API 7 | 8 | Unfortunately the Firebase Open Data Set APIs have recently been shut 9 | down. They still return data from March 31st, but are no longer updating. 10 | 11 | For live data, with lots more cities, try [Forecast.io](http://forecast.io/). 12 | You'll need to register for an API key, and set up a proxy as it does not 13 | allow CORS requests. 14 | 15 | There's an included proxy (forecast-io_proxy.js) that will work for local 16 | development, but won't work when published live. 17 | 18 | Proxies to check out: 19 | * https://crossorigin.me/ 20 | * https://jsonp.afeld.me/ 21 | --------------------------------------------------------------------------------