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 axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -67,6 +66,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Create new chart, using the crypto data */
|
||||
|
@ -98,11 +98,14 @@ export default {
|
|||
try {
|
||||
this.lineStatuses = this.processData(response.data);
|
||||
} catch (chartingError) {
|
||||
ErrorHandler('Unable to plot results on chart', chartingError);
|
||||
this.error('Unable to plot results on chart', chartingError);
|
||||
}
|
||||
})
|
||||
.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
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<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';
|
||||
|
||||
|
@ -39,7 +38,9 @@ export default {
|
|||
computed: {
|
||||
/* The crypto assets to fetch price data for */
|
||||
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 */
|
||||
currency() {
|
||||
|
@ -47,6 +48,11 @@ export default {
|
|||
if (typeof userChoice === 'string') return userChoice;
|
||||
return 'USD';
|
||||
},
|
||||
limit() {
|
||||
const userChoice = this.options.limit;
|
||||
if (userChoice && !Number.isNaN(userChoice) && userChoice > 0) return userChoice;
|
||||
return 100;
|
||||
},
|
||||
/* How results should be sorted */
|
||||
order() {
|
||||
const userChoice = this.options.sortBy;
|
||||
|
@ -60,7 +66,7 @@ export default {
|
|||
/* 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}`;
|
||||
+ `ids=${this.assets}&vs_currency=${this.currency}&order=${this.order}&per_page=${this.limit}`;
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
|
@ -70,6 +76,7 @@ export default {
|
|||
},
|
||||
/* Append percentage symbol, and up/ down arrow */
|
||||
percentage(change) {
|
||||
if (!change) return '';
|
||||
const symbol = change > 0 ? '↑' : '↓';
|
||||
return `${symbol} ${change.toFixed(2)}%`;
|
||||
},
|
||||
|
@ -77,6 +84,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Make GET request to CoinGecko API endpoint */
|
||||
|
@ -86,7 +94,10 @@ export default {
|
|||
this.processData(response.data);
|
||||
})
|
||||
.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 */
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
import { findCurrencySymbol } from '@/utils/MiscHelpers';
|
||||
|
||||
|
@ -54,6 +53,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Make GET request to CoinGecko API endpoint */
|
||||
|
@ -62,7 +62,10 @@ export default {
|
|||
.then(response => {
|
||||
this.processData(response.data);
|
||||
}).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 */
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin],
|
||||
|
@ -15,7 +14,7 @@ export default {
|
|||
frameUrl() {
|
||||
const usersChoice = this.options.url;
|
||||
if (!usersChoice || typeof usersChoice !== 'string') {
|
||||
ErrorHandler('Iframe widget expects a URL');
|
||||
this.error('Iframe widget expects a URL');
|
||||
return null;
|
||||
}
|
||||
return usersChoice;
|
||||
|
@ -28,7 +27,9 @@ export default {
|
|||
methods: {
|
||||
/* Refreshes iframe contents, called by parent */
|
||||
update() {
|
||||
this.startLoading();
|
||||
(document.getElementById(this.frameId) || {}).src = this.frameUrl;
|
||||
this.finishLoading();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -56,6 +55,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Make GET request to Jokes API endpoint */
|
||||
|
@ -63,12 +63,15 @@ export default {
|
|||
axios.get(this.endpoint)
|
||||
.then((response) => {
|
||||
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);
|
||||
})
|
||||
.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 */
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { Chart } from 'frappe-charts/dist/frappe-charts.min.esm';
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -74,6 +73,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Create new chart, using the crypto data */
|
||||
|
@ -103,15 +103,18 @@ export default {
|
|||
axios.get(this.endpoint)
|
||||
.then((response) => {
|
||||
if (response.data.note) {
|
||||
ErrorHandler('API Error', response.data.Note);
|
||||
this.error('API Error', response.data.Note);
|
||||
} else if (response.data['Error Message']) {
|
||||
ErrorHandler('API Error', response.data['Error Message']);
|
||||
this.error('API Error', response.data['Error Message']);
|
||||
} else {
|
||||
this.processData(response.data);
|
||||
}
|
||||
})
|
||||
.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
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -52,6 +51,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Makes GET request to the TFL API */
|
||||
|
@ -61,7 +61,10 @@ export default {
|
|||
this.lineStatuses = this.processData(response.data);
|
||||
})
|
||||
.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 */
|
||||
|
@ -97,7 +100,7 @@ export default {
|
|||
const chosenLines = usersLines.map(name => name.toLowerCase());
|
||||
const filtered = allLines.filter((line) => chosenLines.includes(line.line.toLowerCase()));
|
||||
if (filtered.length < 1) {
|
||||
ErrorHandler('No TFL lines match your filter');
|
||||
this.error('No TFL lines match your filter');
|
||||
return allLines;
|
||||
}
|
||||
return filtered;
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -34,7 +33,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
error: false,
|
||||
icon: null,
|
||||
description: null,
|
||||
temp: null,
|
||||
|
@ -68,6 +66,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchWeather();
|
||||
},
|
||||
/* Adds units symbol to temperature, depending on metric or imperial */
|
||||
|
@ -87,8 +86,11 @@ export default {
|
|||
this.makeWeatherData(data);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.throwError('Failed to fetch weather');
|
||||
.catch((error) => {
|
||||
this.throwError('Failed to fetch weather', error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.finishLoading();
|
||||
});
|
||||
},
|
||||
/* If showing additional info, then generate this data too */
|
||||
|
@ -131,9 +133,8 @@ export default {
|
|||
return valid;
|
||||
},
|
||||
/* Just outputs an error message */
|
||||
throwError(error) {
|
||||
ErrorHandler(error);
|
||||
this.error = true;
|
||||
throwError(msg, error) {
|
||||
this.error(msg, error);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -41,7 +40,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
error: false,
|
||||
showDetails: false,
|
||||
weatherData: [],
|
||||
moreInfo: [],
|
||||
|
@ -77,6 +75,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchWeather();
|
||||
},
|
||||
/* Adds units symbol to temperature, depending on metric or imperial */
|
||||
|
@ -97,8 +96,11 @@ export default {
|
|||
this.processApiResults(response.data);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.throwError('Failed to fetch weather');
|
||||
.catch((error) => {
|
||||
this.error('Failed to fetch weather', error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.finishLoading();
|
||||
});
|
||||
},
|
||||
/* Process the results from the Axios request */
|
||||
|
@ -154,24 +156,19 @@ export default {
|
|||
const ops = this.options;
|
||||
let valid = true;
|
||||
if (!ops.apiKey) {
|
||||
this.throwError('Missing API key for OpenWeatherMap');
|
||||
this.error('Missing API key for OpenWeatherMap');
|
||||
valid = false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 */
|
||||
created() {
|
||||
|
|
|
@ -1,61 +1,80 @@
|
|||
<template>
|
||||
<div class="widget-base">
|
||||
<Button :click="update" class="update-btn">
|
||||
<Button :click="update" class="action-btn update-btn" v-if="!error && !loading">
|
||||
<UpdateIcon />
|
||||
</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
|
||||
v-if="widgetType === 'clock'"
|
||||
v-else-if="widgetType === 'clock'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Weather
|
||||
v-else-if="widgetType === 'weather'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<WeatherForecast
|
||||
v-else-if="widgetType === 'weather-forecast'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<TflStatus
|
||||
v-else-if="widgetType === 'tfl-status'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CryptoPriceChart
|
||||
v-else-if="widgetType === 'crypto-price-chart'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CryptoWatchList
|
||||
v-else-if="widgetType === 'crypto-watch-list'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<XkcdComic
|
||||
v-else-if="widgetType === 'xkcd-comic'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<ExchangeRates
|
||||
v-else-if="widgetType === 'exchange-rates'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<StockPriceChart
|
||||
v-else-if="widgetType === 'stock-price-chart'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Jokes
|
||||
v-else-if="widgetType === 'joke'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<IframeWidget
|
||||
v-else-if="widgetType === 'iframe'"
|
||||
:options="widgetOptions"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
</div>
|
||||
|
@ -65,6 +84,7 @@
|
|||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import Button from '@/components/FormElements/Button';
|
||||
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 Weather from '@/components/Widgets/Weather.vue';
|
||||
|
@ -83,6 +103,7 @@ export default {
|
|||
components: {
|
||||
Button,
|
||||
UpdateIcon,
|
||||
OpenIcon,
|
||||
Clock,
|
||||
Weather,
|
||||
WeatherForecast,
|
||||
|
@ -99,6 +120,11 @@ export default {
|
|||
widget: Object,
|
||||
index: Number,
|
||||
},
|
||||
data: () => ({
|
||||
loading: false,
|
||||
error: false,
|
||||
errorMsg: null,
|
||||
}),
|
||||
computed: {
|
||||
/* Returns the widget type, shows error if not specified */
|
||||
widgetType() {
|
||||
|
@ -120,6 +146,13 @@ export default {
|
|||
update() {
|
||||
this.$refs[this.widgetRef].update();
|
||||
},
|
||||
handleError(msg) {
|
||||
this.error = true;
|
||||
this.errorMsg = msg;
|
||||
},
|
||||
fullScreenWidget() {
|
||||
this.$emit('navigateToSection');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -129,15 +162,14 @@ export default {
|
|||
.widget-base {
|
||||
position: relative;
|
||||
padding-top: 0.75rem;
|
||||
button.update-btn {
|
||||
height: 1.5rem;
|
||||
button.action-btn {
|
||||
height: 1rem;
|
||||
min-width: auto;
|
||||
width: 2rem;
|
||||
width: 1.75rem;
|
||||
margin: 0;
|
||||
padding: 0.1rem 0;
|
||||
position: absolute;
|
||||
right: -0.25rem;
|
||||
top: -0.25rem;
|
||||
top: 0;
|
||||
border: none;
|
||||
opacity: var(--dimming-factor);
|
||||
color: var(--widget-text-color);
|
||||
|
@ -145,6 +177,27 @@ export default {
|
|||
opacity: 1;
|
||||
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>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
export default {
|
||||
|
@ -47,6 +46,7 @@ export default {
|
|||
methods: {
|
||||
/* Extends mixin, and updates data. Called by parent component */
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Make GET request to CoinGecko API endpoint */
|
||||
|
@ -56,7 +56,10 @@ export default {
|
|||
this.processData(response.data);
|
||||
})
|
||||
.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 */
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import ProgressBar from 'rsup-progress';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import LoadingAnimation from '@/assets/interface-icons/loader.svg';
|
||||
|
||||
const WidgetMixin = {
|
||||
|
@ -13,6 +15,7 @@ const WidgetMixin = {
|
|||
},
|
||||
data: () => ({
|
||||
loading: true, // Indicates current loading status, to display spinner
|
||||
progress: new ProgressBar({ color: 'var(--progress-bar)' }),
|
||||
}),
|
||||
methods: {
|
||||
/* Overridden by widget component. Re-fetches and renders any external data *
|
||||
|
@ -21,6 +24,21 @@ const WidgetMixin = {
|
|||
// eslint-disable-next-line no-console
|
||||
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() {
|
||||
// If the mounted function isn't overridden,then hide loader
|
||||
|
|
Loading…
Reference in New Issue