├── CHANGELOG.md ├── _config.yml ├── other ├── preview.gif └── preview.png ├── src ├── index.js ├── style.css ├── VueWeatherWidget.vue ├── utils.js └── script.js ├── examples └── vue │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── main.js │ └── App.vue │ ├── vue.config.js │ ├── .gitignore │ ├── README.md │ └── package.json ├── .prettierrc ├── .npmignore ├── .gitignore ├── .travis.yml ├── .editorconfig ├── .eslintrc.js ├── .codeclimate.yml ├── .babelrc ├── circle.yml ├── .github ├── FUNDING.yml └── workflows │ └── publish-gh-pages.yml ├── package.json ├── README.md └── LICENSE /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /other/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/HEAD/other/preview.gif -------------------------------------------------------------------------------- /other/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/HEAD/other/preview.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import VueWeatherWidget from "./VueWeatherWidget.vue"; 2 | 3 | export default VueWeatherWidget; 4 | -------------------------------------------------------------------------------- /examples/vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/HEAD/examples/vue/public/favicon.ico -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": false, 4 | "printWidth": 100, 5 | "semi": true, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /examples/vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | new Vue({ 5 | render: h => h(App), 6 | }).$mount('#app') 7 | -------------------------------------------------------------------------------- /examples/vue/vue.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@vue/cli-service').ProjectOptions} 3 | */ 4 | module.exports = { 5 | publicPath: "", 6 | }; 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .gitignore 3 | .npmignore 4 | .idea/ 5 | .git/ 6 | _config.yml 7 | bower.json 8 | circle.yml 9 | CHANGELOG.md 10 | .codeclimate.yml 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .idea 4 | node_modules 5 | bower_components 6 | npm-debug.log 7 | .nyc_output 8 | coverage 9 | reports 10 | selenium-debug.log 11 | local.log 12 | sauce_connect.log 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "lts/*" 5 | - "6" 6 | - "7" 7 | - "8" 8 | - "9" 9 | 10 | install: 11 | - npm install 12 | 13 | script: 14 | - npm run prod 15 | 16 | notifications: 17 | email: false 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | }, 5 | extends: ["eslint:recommended", "plugin:vue/essential"], 6 | parserOptions: { 7 | ecmaVersion: 8, 8 | sourceType: "module", 9 | }, 10 | plugins: ["vue"], 11 | rules: {}, 12 | }; 13 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - javascript 9 | ratings: 10 | paths: 11 | - "src/**/*" 12 | exclude_paths: 13 | - "dist/**.js" 14 | - "examples/**/*" 15 | - "test/**/*" 16 | - "build/*" -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeHelpers": true, 3 | "externalHelpers": false, 4 | "exclude": "node_modules/**", 5 | "presets": [ 6 | [ 7 | "latest", 8 | { 9 | "es2015": { 10 | "modules": false 11 | } 12 | } 13 | ] 14 | ], 15 | "plugins": [ 16 | "external-helpers", 17 | "transform-object-assign" 18 | ] 19 | } -------------------------------------------------------------------------------- /examples/vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | package-lock.json 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /examples/vue/README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vww-example", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.39.0", 12 | "vue": "^2.7.16", 13 | "vue-weather-widget": "file:../../" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-babel": "~5.0.8", 17 | "@vue/cli-service": "~5.0.8", 18 | "vue-template-compiler": "^2.7.16" 19 | }, 20 | "browserslist": [ 21 | "> 1%", 22 | "last 2 versions", 23 | "not dead" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6.11.2 4 | environment: 5 | YARN_VERSION: 0.27.5 6 | PATH: "${PATH}:${HOME}/.yarn/bin:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" 7 | dependencies: 8 | pre: 9 | - | 10 | if [[ ! -e ~/.yarn/bin/yarn || $(yarn --version) != "${YARN_VERSION}" ]]; then 11 | curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version $YARN_VERSION 12 | fi 13 | cache_directories: 14 | - ~/.yarn 15 | - ~/.cache/yarn 16 | override: 17 | - NODE_ENV=dev yarn install 18 | 19 | compile: 20 | override: 21 | - yarn run build 22 | 23 | test: 24 | override: 25 | - yarn run test 26 | -------------------------------------------------------------------------------- /examples/vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://paypal.me/sd1pu'] 14 | -------------------------------------------------------------------------------- /.github/workflows/publish-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will publish dist to gh-pages on every push to master branch 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | gh-pages: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 14 19 | 20 | - name: Build and Lint 21 | run: | 22 | npm i 23 | npm run lint 24 | 25 | - name: Recreating gh-pages 26 | run: | 27 | cd examples/vue 28 | npm i 29 | npm run build 30 | cd dist 31 | cp -r ../../../.git . 32 | git config --local user.email "dipu.sudipta@gmail.com" 33 | git config --local user.name "dipu-bd" 34 | git branch -D gh-pages || true 35 | git checkout -b gh-pages 36 | git add --all 37 | git commit -m "[GHA] Update gh-pages" 38 | 39 | - name: Publish changes 40 | uses: ad-m/github-push-action@master 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | branch: gh-pages 44 | force: true 45 | directory: examples/vue/dist 46 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 52 | 53 | 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-weather-widget", 3 | "version": "4.4.3", 4 | "description": "Weather forecast widget for Vuejs using DarkSky api", 5 | "exportName": "vue-weather-widget", 6 | "main": "src/index.js", 7 | "module": "examples/static/dist/js/vue-weather-widget.esm.js", 8 | "unpkg": "examples/static/dist/js/vue-weather-widget.min.js", 9 | "browser": { 10 | "VueWeatherWidget": "src/VueWeatherWidget.vue" 11 | }, 12 | "files": [ 13 | "src", 14 | "dist" 15 | ], 16 | "scripts": { 17 | "lint": "eslint src", 18 | "lint-fix": "eslint --fix src", 19 | "patch": "npm version patch --no-git-tag-version && git add package.json package-lock.json && git commit -m \"Update version:patch\" && git push && npm publish", 20 | "minor": "npm version minor --no-git-tag-version && git add package.json package-lock.json && git commit -m \"Update version:minor\" && git push && npm publish", 21 | "major": "npm version major --no-git-tag-version && git add package.json package-lock.json && git commit -m \"Update version:major\" && git push && npm publish" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/dipu-bd/vue-weather-widget.git" 26 | }, 27 | "keywords": [ 28 | "weather", 29 | "forecast", 30 | "darksky", 31 | "vue-weather", 32 | "vue-weather-widget", 33 | "vuejs-weather", 34 | "vuejs-weather-widget", 35 | "vue", 36 | "vuejs", 37 | "vue-plugin", 38 | "vue-component" 39 | ], 40 | "author": "Sudipto Chandra ", 41 | "license": "Apache-2.0", 42 | "bugs": { 43 | "url": "https://github.com/dipu-bd/vue-weather-widget/issues" 44 | }, 45 | "homepage": "https://github.com/dipu-bd/vue-weather-widget#readme", 46 | "dependencies": { 47 | "jsonp": "^0.2.1", 48 | "vue-skycons": "^4.3.4" 49 | }, 50 | "devDependencies": { 51 | "eslint": "^8.57.1", 52 | "eslint-plugin-vue": "^9.28.0", 53 | "vue": "^2.7.16" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .vww__widget { 2 | width: 100%; 3 | min-width: 250px; 4 | max-width: 800px; 5 | } 6 | 7 | .vww__header { 8 | position: relative; 9 | padding: 10px; 10 | border-bottom-style: solid; 11 | border-bottom-width: 2px; 12 | } 13 | 14 | .vww__title { 15 | font-size: 18px; 16 | font-weight: bold; 17 | text-transform: capitalize; 18 | } 19 | 20 | .vww__content { 21 | min-height: 150px; 22 | height: 180px; 23 | display: flex; 24 | align-items: center; 25 | padding: 8px; 26 | overflow: hidden; 27 | } 28 | 29 | .vww__loading { 30 | width: 100%; 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | .vww__loading span { 36 | display: block; 37 | margin-left: 10px; 38 | } 39 | 40 | .vww__error { 41 | width: 100%; 42 | text-align: center; 43 | } 44 | .vww__error span { 45 | display: block; 46 | padding: 10px; 47 | } 48 | 49 | .vww__currently { 50 | width: 100%; 51 | height: 100%; 52 | display: flex; 53 | flex-direction: column; 54 | align-items: center; 55 | justify-content: center; 56 | } 57 | 58 | .vww__currently > div { 59 | display: flex; 60 | align-items: center; 61 | } 62 | 63 | .vww__currently .vww__title { 64 | margin-top: 10px; 65 | } 66 | 67 | .vww__temp { 68 | padding: 0 10px; 69 | font-size: 50px; 70 | font-weight: bold; 71 | line-height: 0.65em; 72 | } 73 | 74 | .vww__temp > div { 75 | display: block; 76 | text-align: center; 77 | padding-right: 10px; 78 | } 79 | 80 | .vww__wind { 81 | font-size: 14px; 82 | } 83 | 84 | .vww__daily { 85 | display: none; 86 | height: 100%; 87 | } 88 | 89 | @media screen and (min-width: 600px) { 90 | .vww__currently { 91 | width: 300px; 92 | } 93 | .vww__daily { 94 | display: block; 95 | height: 100%; 96 | width: calc(100% - 300px); 97 | display: flex; 98 | align-items: center; 99 | justify-content: space-between; 100 | overflow-x: auto; 101 | } 102 | } 103 | 104 | .vww__day { 105 | height: 100%; 106 | text-align: start; 107 | position: relative; 108 | min-width: 50px; 109 | display: flex; 110 | flex-flow: column; 111 | justify-content: flex-start; 112 | align-items: center; 113 | text-align: center; 114 | } 115 | 116 | .vww__day > span { 117 | display: block; 118 | font-size: 14px; 119 | font-weight: bold; 120 | margin-bottom: 5px; 121 | } 122 | 123 | .vww__day-bar { 124 | margin-top: 20px; 125 | width: 30px; 126 | height: calc(100% - 100px); 127 | } 128 | 129 | .vww__day-bar div { 130 | margin: 0 5px; 131 | display: flex; 132 | } 133 | 134 | .vww__day-bar div:first-child { 135 | align-items: flex-end; 136 | } 137 | 138 | .vww__day-bar div:last-child { 139 | align-items: flex-start; 140 | } 141 | 142 | .vww__day-bar span { 143 | display: block; 144 | font-size: 12px; 145 | } 146 | -------------------------------------------------------------------------------- /src/VueWeatherWidget.vue: -------------------------------------------------------------------------------- 1 | 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 | [![vue 2x](https://img.shields.io/badge/vuejs-2.x-brightgreen.svg)](https://vuejs.org/) 4 | [![npm](https://img.shields.io/npm/v/vue-weather-widget)](http://npmjs.com/package/vue-weather-widget) 5 | [![npm download per month](https://img.shields.io/npm/dm/vue-weather-widget)](http://npmjs.com/package/vue-weather-widget) 6 | [![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/vue-weather-widget?color=red)](https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/master/package.json) 7 | [![NPM license](https://img.shields.io/npm/l/vue-weather-widget?color=blueviolet)](https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/master/LICENSE) 8 | [![Build](https://github.com/dipu-bd/vue-weather-widget/actions/workflows/publish-gh-pages.yml/badge.svg)](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 | [![Preview](https://raw.githubusercontent.com/dipu-bd/vue-weather-widget/master/other/preview.gif)](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 | 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 | --------------------------------------------------------------------------------