95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import jsonp from "jsonp";
2 |
3 | const IP_CACHE = "vww__cache_ip";
4 | const IP_LOCATION_CACHE = "vww__cache_ip_location";
5 | const GEOCODE_CACHE = "vww__cache_geocode";
6 |
7 | const ICON_MAPPINGS = {
8 | "clear-day": ["01d"],
9 | "clear-night": ["01n"],
10 | cloudy: ["03d", "03n"],
11 | fog: ["50d", "50n"],
12 | "partly-cloudy-day": ["02d", "04d"],
13 | "partly-cloudy-night": ["02n", "04n"],
14 | rain: ["09d", "09n", "10d", "10n", "11d", "11n"],
15 | sleet: ["13d", "13n"],
16 | snow: ["13d", "13n"],
17 | wind: ["50d", "50n"],
18 | };
19 |
20 | const UNIT_MAPPINGS = {
21 | auto: "standard",
22 | us: "imperial",
23 | uk: "metric",
24 | };
25 |
26 | const utils = {
27 | lookupIP: () => {
28 | let cache = localStorage[IP_CACHE] || "{}";
29 | cache = JSON.parse(cache);
30 | if (cache.ip) {
31 | return Promise.resolve(cache);
32 | }
33 |
34 | return fetch("https://www.cloudflare.com/cdn-cgi/trace")
35 | .then((resp) => resp.text())
36 | .then((text) => {
37 | return text
38 | .split("\n")
39 | .map((l) => l.split("="))
40 | .filter((x) => x.length == 2)
41 | .reduce((o, x) => {
42 | o[x[0].trim()] = x[1].trim();
43 | return o;
44 | }, {});
45 | })
46 | .then((data) => {
47 | localStorage[IP_CACHE] = JSON.stringify(data);
48 | return data;
49 | });
50 | },
51 |
52 | fetchLocationByIP: (apiKey, ip) => {
53 | if (!ip) {
54 | return utils.lookupIP().then((data) => {
55 | return utils.fetchLocationByIP(apiKey, data["ip"]);
56 | });
57 | }
58 |
59 | let cache = localStorage[IP_LOCATION_CACHE] || "{}";
60 | cache = JSON.parse(cache);
61 | if (cache[ip]) {
62 | return cache[ip];
63 | }
64 |
65 | apiKey = apiKey || "f8n4kqe8pv4kii";
66 | return fetch(`https://api.ipregistry.co/${ip}?key=${apiKey}`)
67 | .then((resp) => resp.json())
68 | .then((result) => {
69 | cache[ip] = result.location || {};
70 | localStorage[IP_LOCATION_CACHE] = JSON.stringify(cache);
71 | return cache[ip];
72 | });
73 | // latitude, longitude, city, country.name
74 | },
75 |
76 | geocode: (apiKey, query, reversed = false) => {
77 | let cache = localStorage[GEOCODE_CACHE] || "{}";
78 | cache = JSON.parse(cache);
79 | if (cache[query]) {
80 | return Promise.resolve(cache[query]);
81 | }
82 |
83 | apiKey = apiKey || "c3bb8aa0a56b21122dea6a2a8ada70c8";
84 | const apiType = reversed ? "reverse" : "forward";
85 | return fetch(`//api.positionstack.com/v1/${apiType}?access_key=${apiKey}&query=${query}`)
86 | .then((resp) => resp.json())
87 | .then((result) => {
88 | if (result.error) {
89 | throw new Error("(api.positionstack.com) " + result.error.message);
90 | }
91 | cache[query] = result.data[0];
92 | localStorage[GEOCODE_CACHE] = JSON.stringify(cache);
93 | return cache[query];
94 | });
95 | // latitude, longitude, region, country
96 | },
97 |
98 | reverseGeocode: (apiKey, lat, lng) => {
99 | return utils.geocode(apiKey, `${lat},${lng}`, true);
100 | },
101 |
102 | fetchWeather: (opts) => {
103 | opts = opts || {};
104 | opts.units = opts.units || "us";
105 | opts.language = opts.language || "en";
106 | if (!opts.lat || !opts.lng) {
107 | throw new Error("Geolocation is required");
108 | }
109 | // return fetchJsonp(
110 | // `https://api.darksky.net/forecast/${opts.apiKey}` +
111 | // `/${opts.lat},${opts.lng}` +
112 | // `?units=${opts.units}&lang=${opts.language}`
113 | // ).then((resp) => resp.json());
114 | return new Promise((resolve, reject) => {
115 | jsonp(
116 | `https://api.darksky.net/forecast/${opts.apiKey}` +
117 | `/${opts.lat},${opts.lng}` +
118 | `?units=${opts.units}&lang=${opts.language}`,
119 | (err, data) => {
120 | if (err) reject(err);
121 | else resolve(data);
122 | }
123 | );
124 | });
125 | },
126 |
127 | fetchOWMWeather: (opts = {}) => {
128 | opts.units = opts.units || "auto";
129 | opts.version = opts.version || "3.0";
130 | opts.language = opts.language || "en";
131 | if (!opts.lat || !opts.lng) {
132 | throw new Error("Geolocation is required");
133 | }
134 |
135 | const units = UNIT_MAPPINGS[opts.units] || "standard";
136 |
137 | return fetch(
138 | `https://api.openweathermap.org/data/${opts.version}/onecall?appid=${opts.apiKey}` +
139 | `&lat=${opts.lat}` +
140 | `&lon=${opts.lng}` +
141 | `&units=${units}` +
142 | `&lang=${opts.language}`
143 | )
144 | .then((resp) => resp.json())
145 | .then(utils.mapData);
146 | },
147 |
148 | mapData: (data) => {
149 | const { current } = data;
150 | const { weather } = current;
151 | const [currentWeather] = weather;
152 | const { description, icon } = currentWeather;
153 | const iconName = utils.mapIcon(icon);
154 |
155 | return {
156 | currently: Object.assign({}, current, {
157 | icon: iconName,
158 | temperature: current.temp,
159 | summary: description,
160 | windSpeed: current.wind_speed,
161 | windBearing: current.wind_deg,
162 | }),
163 | daily: {
164 | data: data.daily.map((day) => {
165 | return {
166 | temperatureMax: day.temp.max,
167 | temperatureMin: day.temp.min,
168 | time: day.dt,
169 | icon: utils.mapIcon(day.weather[0].icon),
170 | };
171 | }),
172 | },
173 | hourly: {
174 | data: data.hourly.map((hour) => {
175 | return {
176 | temperature: hour.temp,
177 | };
178 | }),
179 | },
180 | };
181 | },
182 |
183 | mapIcon: (code) => {
184 | return Object.keys(ICON_MAPPINGS).find((key) => {
185 | return ICON_MAPPINGS[key].includes(code);
186 | });
187 | },
188 | };
189 |
190 | export default utils;
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue Weather Widget
2 |
3 | [](https://vuejs.org/)
4 | [](http://npmjs.com/package/vue-weather-widget)
5 | [](http://npmjs.com/package/vue-weather-widget)
6 | [](https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/master/package.json)
7 | [](https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/master/LICENSE)
8 | [](https://github.com/dipu-bd/vue-weather-widget/actions/workflows/publish-gh-pages.yml)
9 |
10 | Weather widget inspired by [forecast embeds](https://blog.darksky.net/forecast-embeds/) and powered by [OpenWeatherMap](https://openweathermap.org/) and [DarkSky](https://darksky.net/dev) API.
11 |
12 | ## Demo
13 |
14 | [Browser preview](https://dipu-bd.github.io/vue-weather-widget/)
15 |
16 | [](https://dipu-bd.github.io/vue-weather-widget/)
17 |
18 | ## Install
19 |
20 | ### NPM
21 |
22 | ```
23 | npm i vue-weather-widget
24 | ```
25 |
26 | ### YARN
27 |
28 | ```
29 | yarn add vue-weather-widget
30 | ```
31 |
32 | ## API Keys
33 |
34 | This component works with both the DarkSky API, and the OpenWeatherMap API. Since it is no longer
35 | possible to create a DarkSky API key, it is recommended to use OpenWeatherMap.
36 |
37 | > Generate new API key from https://openweathermap.org/appid
38 |
39 | ## Usage
40 |
41 | ```html
42 |
43 |
44 |
50 |
51 |
52 |
59 |
60 |
61 |
70 | ```
71 |
72 | ## Props
73 |
74 | | Props | Type | Default | Description |
75 | | ----------------- | ------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------- |
76 | | api-key | String (_required_) | - | Your OpenWeatherMap or Dark Sky API key |
77 | | use-dark-sky-api | Boolean | `false` | Use DarkSky API instead of OpenWeatherMap |
78 | | latitude | String | current | The latitude of a location (By default, it will use IP to find location) |
79 | | longitude | String | current | The longitude of a location (By default, it will use IP to find location) |
80 | | language | String | `"en"` | A list of supported languages are given below. |
81 | | units | String | `"us"` | A list of supported units are given below. |
82 | | hide-header | Boolean | `false` | Whether to show or hide the title bar. |
83 | | update-interval | Number | `null` | Interval in _milliseconds_ to update weather data periodically. Set it to `0` or `null` to disable autoupdate. |
84 | | disable-animation | Boolean | `false` | Use static icons when enabled. |
85 | | bar-color | String | `"#444"` | Color of the Temparature bar. |
86 | | text-color | String | `"#333"` | Color of the text. |
87 | | ipregistry-key | String | `"f8n4kqe8pv4kii"` | Your ipregistry key to get current location from IP address |
88 |
89 | |
90 | |
91 |
92 | ## Slots
93 |
94 | | Name | Description |
95 | | ------- | ---------------------------------- |
96 | | header | The header component |
97 | | title | The title inside the header |
98 | | loading | Component to display while loading |
99 | | error | Component to display on error |
100 |
101 | ### Supported units
102 |
103 | List of supported units:
104 |
105 | - `auto`: automatically select units based on geographic location
106 | - `ca`: same as si, except that windSpeed and windGust are in kilometers per hour
107 | - `uk`: same as si, except that nearestStormDistance and visibility are in miles, and windSpeed and windGust are in miles per hour
108 | - `us`: Imperial units (the default)
109 | - `si`: SI units
110 |
111 | ### Supported languages
112 |
113 | - `ar`: Arabic
114 | - `az`: Azerbaijani
115 | - `be`: Belarusian
116 | - `bg`: Bulgarian
117 | - `bs`: Bosnian
118 | - `ca`: Catalan
119 | - `cs`: Czech
120 | - `de`: German
121 | - `el`: Greek
122 | - `en`: English (which is the default)
123 | - `es`: Spanish
124 | - `et`: Estonian
125 | - `fr`: French
126 | - `hr`: Croatian
127 | - `hu`: Hungarian
128 | - `id`: Indonesian
129 | - `it`: Italian
130 | - `is`: Icelandic
131 | - `ka`: Georgian
132 | - `kw`: Cornish
133 | - `nb`: Norwegian Bokmål
134 | - `nl`: Dutch
135 | - `pl`: Polish
136 | - `pt`: Portuguese
137 | - `ru`: Russian
138 | - `sk`: Slovak
139 | - `sl`: Slovenian
140 | - `sr`: Serbian
141 | - `sv`: Swedish
142 | - `tet`: Tetum
143 | - `tr`: Turkish
144 | - `uk`: Ukrainian
145 | - `x-pig-latin`: Igpay Atinlay
146 | - `zh`: simplified Chinese
147 | - `zh-tw`: traditional Chinese
148 |
--------------------------------------------------------------------------------
/src/script.js:
--------------------------------------------------------------------------------
1 | import Utils from "./utils";
2 | import Skycon from "vue-skycons";
3 |
4 | export default {
5 | name: "VueWeatherWidget",
6 |
7 | components: {
8 | Skycon,
9 | },
10 |
11 | props: {
12 | // Pass true to use DarkSky API, otherwise it will use OpenWeatherMap API
13 | useDarkSkyApi: {
14 | type: Boolean,
15 | default: false,
16 | },
17 |
18 | // Your Dark Sky / OpenWeatherMap secret key
19 | apiKey: {
20 | type: String,
21 | required: true,
22 | },
23 |
24 | // // Address to lookup location.
25 | // address: {
26 | // type: String,
27 | // },
28 |
29 | // The latitude of a location (in decimal degrees).
30 | // Positive is north, negative is south.
31 | latitude: {
32 | type: String | Number,
33 | },
34 |
35 | // The longitude of a location (in decimal degrees).
36 | // Positive is east, negative is west.
37 | longitude: {
38 | type: String | Number,
39 | },
40 |
41 | // Return summary properties in the desired language.
42 | // For list of supported languages, visit https://darksky.net/dev/docs/forecast
43 | language: {
44 | type: String,
45 | default: "en",
46 | },
47 |
48 | // Return weather conditions in the requested units.
49 | // For list of supported units, visit https://darksky.net/dev/docs/forecast
50 | units: {
51 | type: String,
52 | default: "us",
53 | },
54 |
55 | // Controls whether to show or hide the title bar.
56 | hideHeader: {
57 | type: Boolean,
58 | default: false,
59 | },
60 |
61 | // Auto update interval in milliseconds
62 | updateInterval: {
63 | type: Number,
64 | },
65 |
66 | // Use static skycons
67 | disableAnimation: {
68 | type: Boolean,
69 | default: false,
70 | },
71 |
72 | // Color of the Temparature bar. Default: '#444'
73 | barColor: {
74 | type: String,
75 | default: "#444",
76 | },
77 |
78 | // Color of the text. Default: '#333'
79 | textColor: {
80 | type: String,
81 | default: "#333",
82 | },
83 |
84 | // // Your positionstack api key for geocoding
85 | // positionstackApi: {
86 | // type: String,
87 | // default: "7f9c71310f410847fceb9537a83f3882",
88 | // },
89 |
90 | // Your ipregistry key to get location from ip address
91 | ipregistryKey: {
92 | type: String,
93 | default: "f8n4kqe8pv4kii",
94 | },
95 | },
96 |
97 | data() {
98 | return {
99 | loading: true,
100 | weather: null,
101 | error: null,
102 | //location: {},
103 | timeout: null,
104 | };
105 | },
106 |
107 | watch: {
108 | apiKey: "hydrate",
109 | // address: "hydrate",
110 | latitude: "hydrate",
111 | longitude: "hydrate",
112 | language: "hydrate",
113 | units: "hydrate",
114 | updateInterval: "hydrate",
115 | },
116 |
117 | mounted() {
118 | this.hydrate();
119 | },
120 |
121 | destroyed() {
122 | clearTimeout(this.timeout);
123 | },
124 |
125 | computed: {
126 | currently() {
127 | return this.weather.currently;
128 | },
129 | isDownward() {
130 | const hourly = this.weather.hourly.data;
131 | const time = new Date().getTime() / 1e3;
132 | for (let i = 0; i < hourly.length; i++) {
133 | if (hourly[i].time <= time) continue;
134 | return hourly[i].temperature < this.currently.temperature;
135 | }
136 | },
137 | windBearing() {
138 | const t = Math.round(this.currently.windBearing / 45);
139 | return ["N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"][t];
140 | },
141 | daily() {
142 | const forecasts = [];
143 | let globalMaxTemp = -Infinity;
144 | let globalMinTemp = Infinity;
145 |
146 | const tomorrow = new Date(new Date().toDateString());
147 | const today = tomorrow.getTime() / 1e3 + 24 * 3600 - 1;
148 |
149 | const daily = this.weather.daily.data;
150 | for (let i = 0; i < daily.length; i++) {
151 | const day = daily[i];
152 | if (day.temperatureMax > globalMaxTemp) {
153 | globalMaxTemp = day.temperatureMax;
154 | }
155 | if (day.temperatureMin < globalMinTemp) {
156 | globalMinTemp = day.temperatureMin;
157 | }
158 | forecasts.push(Object.assign({}, day));
159 | }
160 |
161 | const tempRange = globalMaxTemp - globalMinTemp;
162 | for (let i = 0; i < forecasts.length; ++i) {
163 | const day = forecasts[i];
164 | if (day.time <= today) {
165 | day.weekName = "Today";
166 | } else {
167 | day.weekName = new Date(day.time * 1000).toLocaleDateString(this.language, {
168 | weekday: "short",
169 | });
170 | }
171 | const max = day.temperatureMax;
172 | const min = day.temperatureMin;
173 | day.height = Math.round((100 * (max - min)) / tempRange);
174 | day.top = Math.round((100 * (globalMaxTemp - max)) / tempRange);
175 | day.bottom = 100 - (day.top + day.height);
176 | }
177 | return forecasts;
178 | },
179 | },
180 |
181 | methods: {
182 | async loadWeather() {
183 | const fetchWeatherMethod = this.useDarkSkyApi ? Utils.fetchWeather : Utils.fetchOWMWeather;
184 | const data = await fetchWeatherMethod({
185 | apiKey: this.apiKey,
186 | lat: this.latitude,
187 | lng: this.longitude,
188 | units: this.units,
189 | language: this.language,
190 | });
191 | this.$set(this, "weather", data);
192 | },
193 |
194 | autoupdate() {
195 | clearTimeout(this.timeout);
196 | const time = Number(this.updateInterval);
197 | if (!time || time < 10 || this.destroyed) {
198 | return;
199 | }
200 | this.timeout = setTimeout(() => this.hydrate(false), time);
201 | },
202 |
203 | hydrate(setLoading = true) {
204 | this.$set(this, "loading", setLoading);
205 | return this.$nextTick()
206 | .then(this.processLocation)
207 | .then(this.loadWeather)
208 | .then(() => {
209 | this.$set(this, "error", null);
210 | })
211 | .catch((err) => {
212 | this.$set(this, "error", "" + err);
213 | })
214 | .finally(() => {
215 | this.$set(this, "loading", false);
216 | this.autoupdate();
217 | });
218 | },
219 |
220 | processLocation() {
221 | if (!this.latitude || !this.longitude) {
222 | throw new Error("VueWeatherWidget: Latitude or longitude is required");
223 | // if (!this.address) {
224 | // return Utils.fetchLocationByIP(this.ipregistryKey).then((data) => {
225 | // this.$set(this, "location", {
226 | // lat: data.latitude,
227 | // lng: data.longitude,
228 | // name: `${data.city}, ${data.country.name}`,
229 | // });
230 | // });
231 | // } else {
232 | // return Utils.geocode(this.positionstackApi, this.address).then((data) => {
233 | // this.$set(this, "location", {
234 | // lat: data.latitude,
235 | // lng: data.longitude,
236 | // name: `${data.region}, ${data.country}`,
237 | // });
238 | // });
239 | // }
240 | } else {
241 | // return Utils.reverseGeocode(this.positionstackApi, this.latitude, this.longitude).then(
242 | // (data) => {
243 | // this.$set(this, "location", {
244 | // lat: this.latitude,
245 | // lng: this.longitude,
246 | // name: `${data.region}, ${data.country}`,
247 | // });
248 | // }
249 | // );
250 | }
251 | },
252 | },
253 | };
254 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------