mirror of
https://github.com/Lissy93/dashy.git
synced 2025-07-28 08:04:46 +02:00
✨ Adds a crypto price watch-list widget
This commit is contained in:
parent
d3c4fb50ae
commit
985b0000fa
@ -86,6 +86,36 @@ Displays the weather (temperature and conditions) for the next few days for a gi
|
|||||||
units: imperial
|
units: imperial
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Crypto Watch List
|
||||||
|
|
||||||
|
Keep track of price changes of your favorite crypto assets. Data is fetched from [CoinGecko](https://www.coingecko.com/)
|
||||||
|
|
||||||
|
##### Options
|
||||||
|
|
||||||
|
**Field** | **Type** | **Required** | **Description**
|
||||||
|
--- | --- | --- | ---
|
||||||
|
**`assets`** | `string` | Required | An array of cryptocurrencies, coins and tokens. See [list of supported assets](https://api.coingecko.com/api/v3/asset_platforms)
|
||||||
|
**`currency`** | `string` | _Optional_ | The fiat currency to display price in, expressed as an ISO-4217 alpha code (see [list of currencies](https://www.iban.com/currency-codes)). Defaults to `USD`
|
||||||
|
**`sortBy`** | `number` | _Optional_ | The method of sorting results. Can be `marketCap`, `volume` or `alphabetical`. Defaults to `marketCap`.
|
||||||
|
|
||||||
|
##### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Crypto Prices
|
||||||
|
icon: fas fa-rocket
|
||||||
|
type: crypto-watch-list
|
||||||
|
options:
|
||||||
|
currency: GBP
|
||||||
|
sortBy: marketCap
|
||||||
|
assets:
|
||||||
|
- bitcoin
|
||||||
|
- ethereum
|
||||||
|
- monero
|
||||||
|
- cosmos
|
||||||
|
- polkadot
|
||||||
|
- dogecoin
|
||||||
|
```
|
||||||
|
|
||||||
### Crypto Token Price History
|
### Crypto Token Price History
|
||||||
|
|
||||||
Shows recent price history for a given crypto asset, using price data fetched from [CoinGecko](https://www.coingecko.com/)
|
Shows recent price history for a given crypto asset, using price data fetched from [CoinGecko](https://www.coingecko.com/)
|
||||||
@ -94,7 +124,7 @@ Shows recent price history for a given crypto asset, using price data fetched fr
|
|||||||
|
|
||||||
**Field** | **Type** | **Required** | **Description**
|
**Field** | **Type** | **Required** | **Description**
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
**`asset`** | `string` | Required | Name of a crypto asset, coin or token to fetch price data for
|
**`asset`** | `string` | Required | Name of a crypto asset, coin or token to fetch price data for, see [list of supported assets](https://api.coingecko.com/api/v3/asset_platforms)
|
||||||
**`currency`** | `string` | _Optional_ | The fiat currency to display results in, expressed as an ISO-4217 alpha code (see [list of currencies](https://www.iban.com/currency-codes)). Defaults to `USD`
|
**`currency`** | `string` | _Optional_ | The fiat currency to display results in, expressed as an ISO-4217 alpha code (see [list of currencies](https://www.iban.com/currency-codes)). Defaults to `USD`
|
||||||
**`numDays`** | `number` | _Optional_ | The number of days of price history to render. Defaults to `7`, min: `1`, max: `30` days
|
**`numDays`** | `number` | _Optional_ | The number of days of price history to render. Defaults to `7`, min: `1`, max: `30` days
|
||||||
|
|
||||||
|
154
src/components/Widgets/CryptoWatchList.vue
Normal file
154
src/components/Widgets/CryptoWatchList.vue
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<div class="wallet-balance">
|
||||||
|
<template v-if="cryptoData">
|
||||||
|
<div
|
||||||
|
v-for="(asset, index) in cryptoData"
|
||||||
|
:key="index"
|
||||||
|
class="asset-wrapper"
|
||||||
|
v-tooltip="tooltip(asset.info)"
|
||||||
|
>
|
||||||
|
<img class="icon" :src="asset.image" />
|
||||||
|
<p class="name">{{ asset.name }}</p>
|
||||||
|
<p class="price">{{ asset.price | currency }}</p>
|
||||||
|
<p :class="`percent ${asset.percentChange > 0 ? 'up' : 'down'}`">
|
||||||
|
{{ asset.percentChange | percentage }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
import { findCurrencySymbol, convertTimestampToDate } from '@/utils/MiscHelpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [WidgetMixin],
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
cryptoData: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/* The crypto assets to fetch price data for */
|
||||||
|
assets() {
|
||||||
|
return this.options.assets.join(',');
|
||||||
|
},
|
||||||
|
/* The fiat currency to calculate price data in */
|
||||||
|
currency() {
|
||||||
|
const userChoice = this.options.currency;
|
||||||
|
if (typeof userChoice === 'string') return userChoice;
|
||||||
|
return 'USD';
|
||||||
|
},
|
||||||
|
/* How results should be sorted */
|
||||||
|
order() {
|
||||||
|
const userChoice = this.options.sortBy;
|
||||||
|
switch (userChoice) {
|
||||||
|
case ('alphabetical'): return 'id_asc';
|
||||||
|
case ('volume'): return 'volume_desc';
|
||||||
|
case ('marketCap'): return 'market_cap_desc';
|
||||||
|
default: return 'market_cap_desc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* The formatted GET request API endpoint to fetch crypto data from */
|
||||||
|
endpoint() {
|
||||||
|
return `${widgetApiEndpoints.cryptoWatchList}?`
|
||||||
|
+ `ids=${this.assets}&vs_currency=${this.currency}&order=${this.order}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
/* Append currency symbol to price */
|
||||||
|
currency(price) {
|
||||||
|
return `${findCurrencySymbol('usd')}${price}`;
|
||||||
|
},
|
||||||
|
/* Append percentage symbol, and up/ down arrow */
|
||||||
|
percentage(change) {
|
||||||
|
const symbol = change > 0 ? '↑' : '↓';
|
||||||
|
return `${symbol} ${change.toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/* Make GET request to CoinGecko API endpoint */
|
||||||
|
fetchData() {
|
||||||
|
axios.get(this.endpoint)
|
||||||
|
.then((response) => {
|
||||||
|
this.processData(response.data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
ErrorHandler('Unable to fetch crypto watch list', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/* Convert response data into JSON to be consumed by the UI */
|
||||||
|
processData(data) {
|
||||||
|
const results = [];
|
||||||
|
data.forEach((token) => {
|
||||||
|
results.push({
|
||||||
|
name: token.name,
|
||||||
|
image: token.image,
|
||||||
|
price: token.current_price,
|
||||||
|
percentChange: token.price_change_percentage_24h,
|
||||||
|
info: {
|
||||||
|
symbol: token.symbol,
|
||||||
|
rank: token.market_cap_rank,
|
||||||
|
marketCap: token.market_cap,
|
||||||
|
supply: token.circulating_supply,
|
||||||
|
maxSupply: token.max_supply,
|
||||||
|
allTimeHigh: token.ath,
|
||||||
|
allTimeHighDate: token.ath_date,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.cryptoData = results;
|
||||||
|
},
|
||||||
|
/* Show additional info as a tooltip on hover */
|
||||||
|
tooltip(info) {
|
||||||
|
const maxSupply = info.maxSupply ? ` out of max supply of <b>${info.maxSupply}</b>` : '';
|
||||||
|
const content = `Rank: <b>${info.rank}</b> with market cap of `
|
||||||
|
+ `<b>${this.$options.filters.currency(info.marketCap)}</b>`
|
||||||
|
+ `<br>Circulating Supply: <b>${info.supply} ${info.symbol.toUpperCase()}</b>${maxSupply}`
|
||||||
|
+ `<br>All-time-high of <b>${info.allTimeHigh}</b> `
|
||||||
|
+ `at <b>${convertTimestampToDate(info.allTimeHighDate)}</b>`;
|
||||||
|
return {
|
||||||
|
content, html: true, trigger: 'hover focus', delay: 250,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
.asset-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: var(--widget-text-color);
|
||||||
|
.icon {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.percent, .price {
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
&.up { color: var(--success); }
|
||||||
|
&.down { color: var(--danger); }
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px dashed var(--widget-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -15,6 +15,7 @@
|
|||||||
<WeatherForecast v-else-if="widgetType === 'weather-forecast'" :options="widgetOptions" />
|
<WeatherForecast v-else-if="widgetType === 'weather-forecast'" :options="widgetOptions" />
|
||||||
<TflStatus v-else-if="widgetType === 'tfl-status'" :options="widgetOptions" />
|
<TflStatus v-else-if="widgetType === 'tfl-status'" :options="widgetOptions" />
|
||||||
<CryptoPriceChart v-else-if="widgetType === 'crypto-price-chart'" :options="widgetOptions" />
|
<CryptoPriceChart v-else-if="widgetType === 'crypto-price-chart'" :options="widgetOptions" />
|
||||||
|
<CryptoWatchList v-else-if="widgetType === 'crypto-watch-list'" :options="widgetOptions" />
|
||||||
</Collapsable>
|
</Collapsable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -25,6 +26,7 @@ import Weather from '@/components/Widgets/Weather.vue';
|
|||||||
import WeatherForecast from '@/components/Widgets/WeatherForecast.vue';
|
import WeatherForecast from '@/components/Widgets/WeatherForecast.vue';
|
||||||
import TflStatus from '@/components/Widgets/TflStatus.vue';
|
import TflStatus from '@/components/Widgets/TflStatus.vue';
|
||||||
import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue';
|
import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue';
|
||||||
|
import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue';
|
||||||
import Collapsable from '@/components/LinkItems/Collapsable.vue';
|
import Collapsable from '@/components/LinkItems/Collapsable.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -36,6 +38,7 @@ export default {
|
|||||||
WeatherForecast,
|
WeatherForecast,
|
||||||
TflStatus,
|
TflStatus,
|
||||||
CryptoPriceChart,
|
CryptoPriceChart,
|
||||||
|
CryptoWatchList,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
widget: Object,
|
widget: Object,
|
||||||
|
@ -209,6 +209,7 @@ module.exports = {
|
|||||||
weatherForecast: 'https://api.openweathermap.org/data/2.5/forecast/daily',
|
weatherForecast: 'https://api.openweathermap.org/data/2.5/forecast/daily',
|
||||||
tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status',
|
tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status',
|
||||||
cryptoPrices: 'https://api.coingecko.com/api/v3/coins/',
|
cryptoPrices: 'https://api.coingecko.com/api/v3/coins/',
|
||||||
|
cryptoWatchList: 'https://api.coingecko.com/api/v3/coins/markets/',
|
||||||
},
|
},
|
||||||
/* URLs for web search engines */
|
/* URLs for web search engines */
|
||||||
searchEngineUrls: {
|
searchEngineUrls: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user