🥅 Adds graceful error hadling to widgets

This commit is contained in:
Alicia Sykes 2021-12-13 21:40:13 +00:00
parent 19d3c03001
commit 0a4d021b4e
13 changed files with 148 additions and 79 deletions

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -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();
}, },
}, },
}; };

View File

@ -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 */

View File

@ -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

View File

@ -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;

View File

@ -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() {

View File

@ -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() {

View File

@ -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;
}
} }
} }

View File

@ -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>

View File

@ -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 */

View File

@ -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