mirror of https://github.com/Lissy93/dashy.git
🥅 Adds graceful error hadling to widgets
This commit is contained in:
parent
19d3c03001
commit
0a4d021b4e
|
@ -6,7 +6,6 @@
|
||||||
import { Chart } from 'frappe-charts/dist/frappe-charts.min.esm';
|
import { Chart } from 'frappe-charts/dist/frappe-charts.min.esm';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -67,6 +66,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Create new chart, using the crypto data */
|
/* Create new chart, using the crypto data */
|
||||||
|
@ -98,11 +98,14 @@ export default {
|
||||||
try {
|
try {
|
||||||
this.lineStatuses = this.processData(response.data);
|
this.lineStatuses = this.processData(response.data);
|
||||||
} catch (chartingError) {
|
} catch (chartingError) {
|
||||||
ErrorHandler('Unable to plot results on chart', chartingError);
|
this.error('Unable to plot results on chart', chartingError);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((dataFetchError) => {
|
.catch((dataFetchError) => {
|
||||||
ErrorHandler('Unable to fetch crypto data', dataFetchError);
|
this.error('Unable to fetch crypto data', dataFetchError);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Generate price history in a format that can be consumed by the chart
|
/* Generate price history in a format that can be consumed by the chart
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
import { findCurrencySymbol, convertTimestampToDate } from '@/utils/MiscHelpers';
|
import { findCurrencySymbol, convertTimestampToDate } from '@/utils/MiscHelpers';
|
||||||
|
|
||||||
|
@ -39,7 +38,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
/* The crypto assets to fetch price data for */
|
/* The crypto assets to fetch price data for */
|
||||||
assets() {
|
assets() {
|
||||||
return this.options.assets.join(',');
|
const usersChoice = this.options.assets;
|
||||||
|
if (!usersChoice) return '';
|
||||||
|
return usersChoice.join(',');
|
||||||
},
|
},
|
||||||
/* The fiat currency to calculate price data in */
|
/* The fiat currency to calculate price data in */
|
||||||
currency() {
|
currency() {
|
||||||
|
@ -47,6 +48,11 @@ export default {
|
||||||
if (typeof userChoice === 'string') return userChoice;
|
if (typeof userChoice === 'string') return userChoice;
|
||||||
return 'USD';
|
return 'USD';
|
||||||
},
|
},
|
||||||
|
limit() {
|
||||||
|
const userChoice = this.options.limit;
|
||||||
|
if (userChoice && !Number.isNaN(userChoice) && userChoice > 0) return userChoice;
|
||||||
|
return 100;
|
||||||
|
},
|
||||||
/* How results should be sorted */
|
/* How results should be sorted */
|
||||||
order() {
|
order() {
|
||||||
const userChoice = this.options.sortBy;
|
const userChoice = this.options.sortBy;
|
||||||
|
@ -60,7 +66,7 @@ export default {
|
||||||
/* The formatted GET request API endpoint to fetch crypto data from */
|
/* The formatted GET request API endpoint to fetch crypto data from */
|
||||||
endpoint() {
|
endpoint() {
|
||||||
return `${widgetApiEndpoints.cryptoWatchList}?`
|
return `${widgetApiEndpoints.cryptoWatchList}?`
|
||||||
+ `ids=${this.assets}&vs_currency=${this.currency}&order=${this.order}`;
|
+ `ids=${this.assets}&vs_currency=${this.currency}&order=${this.order}&per_page=${this.limit}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
|
@ -70,6 +76,7 @@ export default {
|
||||||
},
|
},
|
||||||
/* Append percentage symbol, and up/ down arrow */
|
/* Append percentage symbol, and up/ down arrow */
|
||||||
percentage(change) {
|
percentage(change) {
|
||||||
|
if (!change) return '';
|
||||||
const symbol = change > 0 ? '↑' : '↓';
|
const symbol = change > 0 ? '↑' : '↓';
|
||||||
return `${symbol} ${change.toFixed(2)}%`;
|
return `${symbol} ${change.toFixed(2)}%`;
|
||||||
},
|
},
|
||||||
|
@ -77,6 +84,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Make GET request to CoinGecko API endpoint */
|
/* Make GET request to CoinGecko API endpoint */
|
||||||
|
@ -86,7 +94,10 @@ export default {
|
||||||
this.processData(response.data);
|
this.processData(response.data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
ErrorHandler('Unable to fetch crypto watch list', error);
|
this.error('Unable to fetch crypto watch list', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Convert response data into JSON to be consumed by the UI */
|
/* Convert response data into JSON to be consumed by the UI */
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
import { findCurrencySymbol } from '@/utils/MiscHelpers';
|
import { findCurrencySymbol } from '@/utils/MiscHelpers';
|
||||||
|
|
||||||
|
@ -54,6 +53,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Make GET request to CoinGecko API endpoint */
|
/* Make GET request to CoinGecko API endpoint */
|
||||||
|
@ -62,7 +62,10 @@ export default {
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.processData(response.data);
|
this.processData(response.data);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
ErrorHandler('Unable to fetch or process exchange rate data', error);
|
this.error('Unable to fetch or process exchange rate data', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Assign data variables to the returned data */
|
/* Assign data variables to the returned data */
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [WidgetMixin],
|
mixins: [WidgetMixin],
|
||||||
|
@ -15,7 +14,7 @@ export default {
|
||||||
frameUrl() {
|
frameUrl() {
|
||||||
const usersChoice = this.options.url;
|
const usersChoice = this.options.url;
|
||||||
if (!usersChoice || typeof usersChoice !== 'string') {
|
if (!usersChoice || typeof usersChoice !== 'string') {
|
||||||
ErrorHandler('Iframe widget expects a URL');
|
this.error('Iframe widget expects a URL');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return usersChoice;
|
return usersChoice;
|
||||||
|
@ -28,7 +27,9 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Refreshes iframe contents, called by parent */
|
/* Refreshes iframe contents, called by parent */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
(document.getElementById(this.frameId) || {}).src = this.frameUrl;
|
(document.getElementById(this.frameId) || {}).src = this.frameUrl;
|
||||||
|
this.finishLoading();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -56,6 +55,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Make GET request to Jokes API endpoint */
|
/* Make GET request to Jokes API endpoint */
|
||||||
|
@ -63,12 +63,15 @@ export default {
|
||||||
axios.get(this.endpoint)
|
axios.get(this.endpoint)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data.error) {
|
if (response.data.error) {
|
||||||
ErrorHandler('No matching jokes returned', response.data.additionalInfo);
|
this.error('No matching jokes returned', response.data.additionalInfo);
|
||||||
}
|
}
|
||||||
this.processData(response.data);
|
this.processData(response.data);
|
||||||
})
|
})
|
||||||
.catch((dataFetchError) => {
|
.catch((dataFetchError) => {
|
||||||
ErrorHandler('Unable to fetch any jokes', dataFetchError);
|
this.error('Unable to fetch any jokes', dataFetchError);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Assign data variables to the returned data */
|
/* Assign data variables to the returned data */
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
import { Chart } from 'frappe-charts/dist/frappe-charts.min.esm';
|
import { Chart } from 'frappe-charts/dist/frappe-charts.min.esm';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -74,6 +73,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Create new chart, using the crypto data */
|
/* Create new chart, using the crypto data */
|
||||||
|
@ -103,15 +103,18 @@ export default {
|
||||||
axios.get(this.endpoint)
|
axios.get(this.endpoint)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data.note) {
|
if (response.data.note) {
|
||||||
ErrorHandler('API Error', response.data.Note);
|
this.error('API Error', response.data.Note);
|
||||||
} else if (response.data['Error Message']) {
|
} else if (response.data['Error Message']) {
|
||||||
ErrorHandler('API Error', response.data['Error Message']);
|
this.error('API Error', response.data['Error Message']);
|
||||||
} else {
|
} else {
|
||||||
this.processData(response.data);
|
this.processData(response.data);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
ErrorHandler('Unable to fetch stock price data', error);
|
this.error('Unable to fetch stock price data', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Convert data returned by API into a format that can be consumed by the chart
|
/* Convert data returned by API into a format that can be consumed by the chart
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -52,6 +51,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Makes GET request to the TFL API */
|
/* Makes GET request to the TFL API */
|
||||||
|
@ -61,7 +61,10 @@ export default {
|
||||||
this.lineStatuses = this.processData(response.data);
|
this.lineStatuses = this.processData(response.data);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
ErrorHandler('Unable to fetch data from TFL API');
|
this.error('Unable to fetch data from TFL API');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Processes the results to be rendered by the UI */
|
/* Processes the results to be rendered by the UI */
|
||||||
|
@ -97,7 +100,7 @@ export default {
|
||||||
const chosenLines = usersLines.map(name => name.toLowerCase());
|
const chosenLines = usersLines.map(name => name.toLowerCase());
|
||||||
const filtered = allLines.filter((line) => chosenLines.includes(line.line.toLowerCase()));
|
const filtered = allLines.filter((line) => chosenLines.includes(line.line.toLowerCase()));
|
||||||
if (filtered.length < 1) {
|
if (filtered.length < 1) {
|
||||||
ErrorHandler('No TFL lines match your filter');
|
this.error('No TFL lines match your filter');
|
||||||
return allLines;
|
return allLines;
|
||||||
}
|
}
|
||||||
return filtered;
|
return filtered;
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -34,7 +33,6 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: false,
|
|
||||||
icon: null,
|
icon: null,
|
||||||
description: null,
|
description: null,
|
||||||
temp: null,
|
temp: null,
|
||||||
|
@ -68,6 +66,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchWeather();
|
this.fetchWeather();
|
||||||
},
|
},
|
||||||
/* Adds units symbol to temperature, depending on metric or imperial */
|
/* Adds units symbol to temperature, depending on metric or imperial */
|
||||||
|
@ -87,8 +86,11 @@ export default {
|
||||||
this.makeWeatherData(data);
|
this.makeWeatherData(data);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
this.throwError('Failed to fetch weather');
|
this.throwError('Failed to fetch weather', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* If showing additional info, then generate this data too */
|
/* If showing additional info, then generate this data too */
|
||||||
|
@ -131,9 +133,8 @@ export default {
|
||||||
return valid;
|
return valid;
|
||||||
},
|
},
|
||||||
/* Just outputs an error message */
|
/* Just outputs an error message */
|
||||||
throwError(error) {
|
throwError(msg, error) {
|
||||||
ErrorHandler(error);
|
this.error(msg, error);
|
||||||
this.error = true;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -41,7 +40,6 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: false,
|
|
||||||
showDetails: false,
|
showDetails: false,
|
||||||
weatherData: [],
|
weatherData: [],
|
||||||
moreInfo: [],
|
moreInfo: [],
|
||||||
|
@ -77,6 +75,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchWeather();
|
this.fetchWeather();
|
||||||
},
|
},
|
||||||
/* Adds units symbol to temperature, depending on metric or imperial */
|
/* Adds units symbol to temperature, depending on metric or imperial */
|
||||||
|
@ -97,8 +96,11 @@ export default {
|
||||||
this.processApiResults(response.data);
|
this.processApiResults(response.data);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
this.throwError('Failed to fetch weather');
|
this.error('Failed to fetch weather', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Process the results from the Axios request */
|
/* Process the results from the Axios request */
|
||||||
|
@ -154,24 +156,19 @@ export default {
|
||||||
const ops = this.options;
|
const ops = this.options;
|
||||||
let valid = true;
|
let valid = true;
|
||||||
if (!ops.apiKey) {
|
if (!ops.apiKey) {
|
||||||
this.throwError('Missing API key for OpenWeatherMap');
|
this.error('Missing API key for OpenWeatherMap');
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (!ops.city) {
|
if (!ops.city) {
|
||||||
this.throwError('A city name is required to fetch weather');
|
this.error('A city name is required to fetch weather');
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (ops.units && ops.units !== 'metric' && ops.units !== 'imperial') {
|
if (ops.units && ops.units !== 'metric' && ops.units !== 'imperial') {
|
||||||
this.throwError('Invalid units specified, must be either \'metric\' or \'imperial\'');
|
this.error('Invalid units specified, must be either \'metric\' or \'imperial\'');
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
return valid;
|
return valid;
|
||||||
},
|
},
|
||||||
/* Just outputs an error message */
|
|
||||||
throwError(error) {
|
|
||||||
ErrorHandler(error);
|
|
||||||
this.error = true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
/* When the widget loads, the props are checked, and weather fetched */
|
/* When the widget loads, the props are checked, and weather fetched */
|
||||||
created() {
|
created() {
|
||||||
|
|
|
@ -1,61 +1,80 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="widget-base">
|
<div class="widget-base">
|
||||||
<Button :click="update" class="update-btn">
|
<Button :click="update" class="action-btn update-btn" v-if="!error && !loading">
|
||||||
<UpdateIcon />
|
<UpdateIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button :click="fullScreenWidget" class="action-btn open-btn" v-if="!error && !loading">
|
||||||
|
<OpenIcon />
|
||||||
|
</Button>
|
||||||
|
<div v-if="loading">Loading...</div>
|
||||||
|
<div v-else-if="error" class="widget-error">
|
||||||
|
<p class="error-msg">An error occurred, see the logs for more info.</p>
|
||||||
|
<p class="error-output">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
<Clock
|
<Clock
|
||||||
v-if="widgetType === 'clock'"
|
v-else-if="widgetType === 'clock'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<Weather
|
<Weather
|
||||||
v-else-if="widgetType === 'weather'"
|
v-else-if="widgetType === 'weather'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<WeatherForecast
|
<WeatherForecast
|
||||||
v-else-if="widgetType === 'weather-forecast'"
|
v-else-if="widgetType === 'weather-forecast'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<TflStatus
|
<TflStatus
|
||||||
v-else-if="widgetType === 'tfl-status'"
|
v-else-if="widgetType === 'tfl-status'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<CryptoPriceChart
|
<CryptoPriceChart
|
||||||
v-else-if="widgetType === 'crypto-price-chart'"
|
v-else-if="widgetType === 'crypto-price-chart'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<CryptoWatchList
|
<CryptoWatchList
|
||||||
v-else-if="widgetType === 'crypto-watch-list'"
|
v-else-if="widgetType === 'crypto-watch-list'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<XkcdComic
|
<XkcdComic
|
||||||
v-else-if="widgetType === 'xkcd-comic'"
|
v-else-if="widgetType === 'xkcd-comic'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<ExchangeRates
|
<ExchangeRates
|
||||||
v-else-if="widgetType === 'exchange-rates'"
|
v-else-if="widgetType === 'exchange-rates'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<StockPriceChart
|
<StockPriceChart
|
||||||
v-else-if="widgetType === 'stock-price-chart'"
|
v-else-if="widgetType === 'stock-price-chart'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<Jokes
|
<Jokes
|
||||||
v-else-if="widgetType === 'joke'"
|
v-else-if="widgetType === 'joke'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
<IframeWidget
|
<IframeWidget
|
||||||
v-else-if="widgetType === 'iframe'"
|
v-else-if="widgetType === 'iframe'"
|
||||||
:options="widgetOptions"
|
:options="widgetOptions"
|
||||||
|
@error="handleError"
|
||||||
:ref="widgetRef"
|
:ref="widgetRef"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,6 +84,7 @@
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
import Button from '@/components/FormElements/Button';
|
import Button from '@/components/FormElements/Button';
|
||||||
import UpdateIcon from '@/assets/interface-icons/widget-update.svg';
|
import UpdateIcon from '@/assets/interface-icons/widget-update.svg';
|
||||||
|
import OpenIcon from '@/assets/interface-icons/open-new-tab.svg';
|
||||||
|
|
||||||
import Clock from '@/components/Widgets/Clock.vue';
|
import Clock from '@/components/Widgets/Clock.vue';
|
||||||
import Weather from '@/components/Widgets/Weather.vue';
|
import Weather from '@/components/Widgets/Weather.vue';
|
||||||
|
@ -83,6 +103,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Button,
|
Button,
|
||||||
UpdateIcon,
|
UpdateIcon,
|
||||||
|
OpenIcon,
|
||||||
Clock,
|
Clock,
|
||||||
Weather,
|
Weather,
|
||||||
WeatherForecast,
|
WeatherForecast,
|
||||||
|
@ -99,6 +120,11 @@ export default {
|
||||||
widget: Object,
|
widget: Object,
|
||||||
index: Number,
|
index: Number,
|
||||||
},
|
},
|
||||||
|
data: () => ({
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
errorMsg: null,
|
||||||
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
/* Returns the widget type, shows error if not specified */
|
/* Returns the widget type, shows error if not specified */
|
||||||
widgetType() {
|
widgetType() {
|
||||||
|
@ -120,6 +146,13 @@ export default {
|
||||||
update() {
|
update() {
|
||||||
this.$refs[this.widgetRef].update();
|
this.$refs[this.widgetRef].update();
|
||||||
},
|
},
|
||||||
|
handleError(msg) {
|
||||||
|
this.error = true;
|
||||||
|
this.errorMsg = msg;
|
||||||
|
},
|
||||||
|
fullScreenWidget() {
|
||||||
|
this.$emit('navigateToSection');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -129,15 +162,14 @@ export default {
|
||||||
.widget-base {
|
.widget-base {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
button.update-btn {
|
button.action-btn {
|
||||||
height: 1.5rem;
|
height: 1rem;
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
width: 2rem;
|
width: 1.75rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.1rem 0;
|
padding: 0.1rem 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -0.25rem;
|
top: 0;
|
||||||
top: -0.25rem;
|
|
||||||
border: none;
|
border: none;
|
||||||
opacity: var(--dimming-factor);
|
opacity: var(--dimming-factor);
|
||||||
color: var(--widget-text-color);
|
color: var(--widget-text-color);
|
||||||
|
@ -145,6 +177,27 @@ export default {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: var(--widget-background-color);
|
color: var(--widget-background-color);
|
||||||
}
|
}
|
||||||
|
&.update-btn {
|
||||||
|
right: -0.25rem;
|
||||||
|
}
|
||||||
|
&.open-btn {
|
||||||
|
right: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-error {
|
||||||
|
p.error-msg {
|
||||||
|
color: var(--warning);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0 auto 0.5rem auto;
|
||||||
|
}
|
||||||
|
p.error-output {
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
color: var(--widget-text-color);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<WidgetBase
|
|
||||||
v-for="(widget, widgetIndex) in widgets"
|
|
||||||
:key="widgetIndex"
|
|
||||||
:widget="widget"
|
|
||||||
:index="widgetIndex"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import WidgetBase from '@/components/Widgets/WidgetBase';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'WidgetGroup',
|
|
||||||
components: {
|
|
||||||
WidgetBase,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
widgets: Array,
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import '@/styles/media-queries.scss';
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -10,7 +10,6 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
|
||||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -47,6 +46,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
/* Extends mixin, and updates data. Called by parent component */
|
/* Extends mixin, and updates data. Called by parent component */
|
||||||
update() {
|
update() {
|
||||||
|
this.startLoading();
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
/* Make GET request to CoinGecko API endpoint */
|
/* Make GET request to CoinGecko API endpoint */
|
||||||
|
@ -56,7 +56,10 @@ export default {
|
||||||
this.processData(response.data);
|
this.processData(response.data);
|
||||||
})
|
})
|
||||||
.catch((dataFetchError) => {
|
.catch((dataFetchError) => {
|
||||||
ErrorHandler('Unable to fetch data', dataFetchError);
|
this.error('Unable to fetch data', dataFetchError);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.finishLoading();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Assign data variables to the returned data */
|
/* Assign data variables to the returned data */
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import ProgressBar from 'rsup-progress';
|
||||||
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
import LoadingAnimation from '@/assets/interface-icons/loader.svg';
|
import LoadingAnimation from '@/assets/interface-icons/loader.svg';
|
||||||
|
|
||||||
const WidgetMixin = {
|
const WidgetMixin = {
|
||||||
|
@ -13,6 +15,7 @@ const WidgetMixin = {
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
loading: true, // Indicates current loading status, to display spinner
|
loading: true, // Indicates current loading status, to display spinner
|
||||||
|
progress: new ProgressBar({ color: 'var(--progress-bar)' }),
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
/* Overridden by widget component. Re-fetches and renders any external data *
|
/* Overridden by widget component. Re-fetches and renders any external data *
|
||||||
|
@ -21,6 +24,21 @@ const WidgetMixin = {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('No update method configured for this widget');
|
console.log('No update method configured for this widget');
|
||||||
},
|
},
|
||||||
|
/* Called when an error occurs */
|
||||||
|
error(msg, stackTrace) {
|
||||||
|
ErrorHandler(msg, stackTrace);
|
||||||
|
this.$emit('error', msg);
|
||||||
|
},
|
||||||
|
/* When a data request update starts, show loader */
|
||||||
|
startLoading() {
|
||||||
|
this.loading = true;
|
||||||
|
this.progress.start();
|
||||||
|
},
|
||||||
|
/* When a data request finishes, hide loader */
|
||||||
|
finishLoading() {
|
||||||
|
this.loading = false;
|
||||||
|
setTimeout(() => { this.progress.end(); }, 500);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// If the mounted function isn't overridden,then hide loader
|
// If the mounted function isn't overridden,then hide loader
|
||||||
|
|
Loading…
Reference in New Issue