From cd3a3047440777967688d7fd3f537acad7fd78e5 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 13 Nov 2021 20:30:55 +0000 Subject: [PATCH 001/118] :construction: Started working on itnitial state for widgets --- src/store.js | 9 ++++++--- src/utils/ConfigAccumalator.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/store.js b/src/store.js index 7f9aaaf9..2df242ec 100644 --- a/src/store.js +++ b/src/store.js @@ -51,12 +51,15 @@ const store = new Vuex.Store({ appConfig(state) { return state.config.appConfig || {}; }, - theme(state) { - return state.config.appConfig.theme; - }, sections(state) { return filterUserSections(state.config.sections || []); }, + widgets(state) { + return state.config.widgets || []; + }, + theme(state) { + return state.config.appConfig.theme; + }, webSearch(state, getters) { return getters.appConfig.webSearch || {}; }, diff --git a/src/utils/ConfigAccumalator.js b/src/utils/ConfigAccumalator.js index 041306e9..018c4f25 100644 --- a/src/utils/ConfigAccumalator.js +++ b/src/utils/ConfigAccumalator.js @@ -78,12 +78,23 @@ export default class ConfigAccumulator { return sections; } + /* Widgets */ + widgets() { + const localWidgets = localStorage[localStorageKeys.CONF_WIDGETS]; + if (localWidgets) { + const json = JSON.parse(localWidgets); + if (json.length >= 1) return json; + } + return this.conf.widgets || []; + } + /* Complete config */ config() { return { appConfig: this.appConfig(), pageInfo: this.pageInfo(), sections: this.sections(), + widgets: this.widgets(), }; } } From 50164bf790bfc34dbe42d2de9379874741ebdb37 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 28 Nov 2021 21:23:51 +0000 Subject: [PATCH 002/118] :art: Adds new CSS vars for widgets --- src/styles/color-palette.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/styles/color-palette.scss b/src/styles/color-palette.scss index d672c2eb..8a38381a 100644 --- a/src/styles/color-palette.scss +++ b/src/styles/color-palette.scss @@ -57,9 +57,13 @@ --config-settings-color: var(--primary); --config-settings-background: var(--background-darker); --config-code-color: var(--background); - --config-code-background: #fff; + --config-code-background: var(--white); --code-editor-color: var(--black); --code-editor-background: var(--white); + // Widgets + --widget-text-color: var(--primary); + --widget-background-color: var(--background-darker); + --widget-accent-color: var(--background); // Interactive editor --interactive-editor-color: var(--primary); --interactive-editor-background: var(--background); From 6b4fbfe25bc8c11f777a762f8bec36d1269b0df7 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 28 Nov 2021 21:24:25 +0000 Subject: [PATCH 003/118] :zap: Adds mixin for widgets --- src/mixins/WidgetMixin.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/mixins/WidgetMixin.js diff --git a/src/mixins/WidgetMixin.js b/src/mixins/WidgetMixin.js new file mode 100644 index 00000000..7d91a295 --- /dev/null +++ b/src/mixins/WidgetMixin.js @@ -0,0 +1,10 @@ +const WidgetMixin = { + props: { + options: { + type: Object, + default: {}, + }, + }, +}; + +export default WidgetMixin; From 490c8e73fa6f84fbe490896bf459121cf3c7b0ab Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 28 Nov 2021 21:26:08 +0000 Subject: [PATCH 004/118] :construction: Basic structure for widgets --- src/components/Widgets/WidgetBase.vue | 52 ++++++++++++++++++++++++++ src/components/Widgets/WidgetGroup.vue | 30 +++++++++++++++ src/views/Home.vue | 7 ++++ 3 files changed, 89 insertions(+) create mode 100644 src/components/Widgets/WidgetBase.vue create mode 100644 src/components/Widgets/WidgetGroup.vue diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue new file mode 100644 index 00000000..89bc6a5d --- /dev/null +++ b/src/components/Widgets/WidgetBase.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/components/Widgets/WidgetGroup.vue b/src/components/Widgets/WidgetGroup.vue new file mode 100644 index 00000000..7efcebf2 --- /dev/null +++ b/src/components/Widgets/WidgetGroup.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/views/Home.vue b/src/views/Home.vue index 85a8b9da..92a32f31 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -27,6 +27,8 @@ + (singleSectionView ? 'single-section-view ' : '') + (this.colCount ? `col-count-${this.colCount} ` : '')" > + +
Date: Sun, 28 Nov 2021 21:26:40 +0000 Subject: [PATCH 005/118] :card_file_box: Adds data key for widget local storage --- src/utils/defaults.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/defaults.js b/src/utils/defaults.js index bf70fee3..81d49b47 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -101,6 +101,7 @@ module.exports = { THEME: 'theme', CUSTOM_COLORS: 'customColors', CONF_SECTIONS: 'confSections', + CONF_WIDGETS: 'confSections', PAGE_INFO: 'pageInfo', APP_CONFIG: 'appConfig', BACKUP_ID: 'backupId', From 080e68445c26f05bcb91331367e3c9c496821c5a Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 28 Nov 2021 21:27:30 +0000 Subject: [PATCH 006/118] :sparkles: Made a simple clock widget --- docs/widgets.md | 42 +++++++++++++ public/fonts/Digital-Regular.ttf | Bin 0 -> 34360 bytes src/components/Widgets/Clock.vue | 104 +++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 docs/widgets.md create mode 100644 public/fonts/Digital-Regular.ttf create mode 100644 src/components/Widgets/Clock.vue diff --git a/docs/widgets.md b/docs/widgets.md new file mode 100644 index 00000000..2589685e --- /dev/null +++ b/docs/widgets.md @@ -0,0 +1,42 @@ +# Widgets + +Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets availible out-of-the-box (with more on the way!) as well as support for custom widgets to display stats from almost any service with an accessible API. + +##### Contents +- [Built-In Widgets](#built-in-widgets) +- [Dynamic Widgets](#dynamic-widgets) +- [Build your own Widget](#build-your-own-widget) + +## Built-In Widgets + +### Clock + +A simple, live-updating time and date widget with time-zone support. All options are optional. + +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`timeZone`** | `string` | _Optional_ | The time zone to display date and time in.
Specified as Region/City, for example: `Australia/Melbourne`. See the [Time Zone DB](https://timezonedb.com/time-zones) for a full list of supported TZs. Defaults to the browser / device's local time +**`format`** | `string` | _Optional_ | A country code for displaying the date and time in local format.
Specified as `[ISO-3166]-[ISO-639]`, for example: `en-AU`. See [here](https://www.fincher.org/Utilities/CountryLanguageList.shtml) for a full list of locales. Defaults to the browser / device's region +**`hideDate`** | `boolean` | _Optional_ | If set to `true`, the date and city will not be shown. Defaults to `false` + +##### Example + +```yaml +- name: London Time + icon: fas fa-clock + type: clock + options: + timeZone: Europe/London + format: en-GB + hideDate: false +``` + +--- + +## Dynamic Widgets + +--- + +## Build your own Widget diff --git a/public/fonts/Digital-Regular.ttf b/public/fonts/Digital-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5dbe6f908239d00a9f88c335df3efb9261ecc3cc GIT binary patch literal 34360 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 +
+
+

{{ timeZone | getCity }}

+

{{ date }}

+
+

{{ time }}

+
+ + + + + From c9f2483c3ec4c7064b4e33ba2c5dbf9e7d90b36c Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Tue, 30 Nov 2021 15:52:15 +0000 Subject: [PATCH 007/118] :art: Improved code style in Section --- src/components/LinkItems/Section.vue | 51 +++++++++++++--------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/components/LinkItems/Section.vue b/src/components/LinkItems/Section.vue index 6d197694..9c5a8d6f 100644 --- a/src/components/LinkItems/Section.vue +++ b/src/components/LinkItems/Section.vue @@ -13,7 +13,7 @@ >
- No Items to Show Yet + {{ $t('home.no-items-section') }}
-
60) interval = 60; + if (interval < 1) interval = 0; + return interval; }, }, methods: { @@ -180,19 +190,6 @@ export default { triggerModal(url) { this.$refs[`iframeModal-${this.groupId}`].show(url); }, - /* Determines if user has enabled online status checks */ - shouldEnableStatusCheck(itemPreference) { - const globalPreference = this.appConfig.statusCheck || false; - return itemPreference !== undefined ? itemPreference : globalPreference; - }, - /* Determine how often to re-fire status checks */ - getStatusCheckInterval() { - let interval = this.appConfig.statusCheckInterval; - if (!interval) return 0; - if (interval > 60) interval = 60; - if (interval < 1) interval = 0; - return interval; - }, /* Sorts items alphabetically using the title attribute */ sortAlphabetically(items) { return items.sort((a, b) => (a.title > b.title ? 1 : -1)); @@ -205,7 +202,7 @@ export default { return items; }, /* Sorts items by most recently used */ - sortBLastUsed(items) { + sortByLastUsed(items) { const usageCount = JSON.parse(localStorage.getItem(localStorageKeys.LAST_USED) || '{}'); const glu = (item) => usageCount[item.id] || 0; items.reverse().sort((a, b) => (glu(a) < glu(b) ? 1 : -1)); From 57554ddcdf2f85b8bff72fae822d8260684848cb Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Tue, 30 Nov 2021 15:58:47 +0000 Subject: [PATCH 008/118] :zap: Reliability improvements for icon fallbacks --- src/components/LinkItems/Item.vue | 4 +- src/components/LinkItems/ItemIcon.vue | 157 ++++++++++++++++---------- src/utils/defaults.js | 34 +++--- 3 files changed, 116 insertions(+), 79 deletions(-) diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue index e66712df..da51469a 100644 --- a/src/components/LinkItems/Item.vue +++ b/src/components/LinkItems/Item.vue @@ -452,7 +452,7 @@ export default { height: 2rem; padding-top: 4px; max-width: 14rem; - div img, div svg.missing-image { + div img { width: 2rem; } .tile-title { @@ -473,7 +473,7 @@ export default { flex-direction: column; align-items: center; height: auto; - div img, div svg.missing-image { + div img { width: 2.5rem; margin-bottom: 0.25rem; } diff --git a/src/components/LinkItems/ItemIcon.vue b/src/components/LinkItems/ItemIcon.vue index 5e3a9667..41786b8e 100644 --- a/src/components/LinkItems/ItemIcon.vue +++ b/src/components/LinkItems/ItemIcon.vue @@ -1,13 +1,14 @@ @@ -25,8 +26,8 @@ import BrokenImage from '@/assets/interface-icons/broken-icon.svg'; import ErrorHandler from '@/utils/ErrorHandler'; import EmojiUnicodeRegex from '@/utils/EmojiUnicodeRegex'; import emojiLookup from '@/utils/emojis.json'; -import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults'; import { asciiHash } from '@/utils/MiscHelpers'; +import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults'; export default { name: 'Icon', @@ -36,7 +37,7 @@ export default { size: String, // Either small, medium or large }, components: { - BrokenImage, + BrokenImage, // Used when the desired image returns a 404 }, computed: { /* Get appConfig from store */ @@ -60,6 +61,39 @@ export default { }; }, methods: { + /* Determine icon type, e.g. local or remote asset, SVG, favicon, font-awesome, etc */ + determineImageType(img) { + let imgType = ''; + if (!img) imgType = 'none'; + else if (this.isUrl(img)) imgType = 'url'; + else if (this.isImage(img)) imgType = 'img'; + else if (img.includes('fa-')) imgType = 'font-awesome'; + else if (img.includes('mdi-')) imgType = 'mdi'; + else if (img.includes('si-')) imgType = 'si'; + else if (img.includes('hl-')) imgType = 'home-lab-icons'; + else if (img.includes('favicon-')) imgType = 'custom-favicon'; + else if (img === 'favicon') imgType = 'favicon'; + else if (img === 'generative') imgType = 'generative'; + else if (this.isEmoji(img).isEmoji) imgType = 'emoji'; + else imgType = 'none'; + return imgType; + }, + /* Return the path to icon asset, depending on icon type */ + getIconPath(img, url) { + switch (this.determineImageType(img)) { + case 'url': return img; + case 'img': return this.getLocalImagePath(img); + case 'favicon': return this.getFavicon(url); + case 'custom-favicon': return this.getCustomFavicon(url, img); + case 'generative': return this.getGenerativeIcon(url); + case 'mdi': return img; // Material design icons + case 'simple-icons': return this.getSimpleIcon(img); + case 'home-lab-icons': return this.getHomeLabIcon(img); + case 'svg': return img; // Local SVG icon + case 'emoji': return img; // Emoji/ unicode + default: return ''; + } + }, /* Check if a string is in a URL format. Used to identify tile icon source */ isUrl(str) { const pattern = new RegExp(/(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-/]))?/); @@ -73,7 +107,7 @@ export default { if (splitPath.length >= 1) return validImgExtensions.includes(splitPath[1]); return false; }, - /* Determins if a given string is an emoji, and if so what type it is */ + /* Determines if a given string is an emoji, and if so what type it is */ isEmoji(img) { if (EmojiUnicodeRegex.test(img) && img.match(/./gu).length) { // Is a unicode emoji return { isEmoji: true, emojiType: 'glyph' }; @@ -84,15 +118,27 @@ export default { } return { isEmoji: false, emojiType: '' }; }, - /* Formats and gets emoji from unicode or shortcode */ + /* Returns the corresponding emoji for a shortcode, or shows error if not found */ + getShortCodeEmoji(emojiCode) { + if (emojiLookup[emojiCode]) { + return emojiLookup[emojiCode]; + } else { + this.imageNotFound(`No emoji found with name '${emojiCode}'`); + return null; + } + }, + /* Formats and gets emoji from either unicode, shortcode or glyph */ getEmoji(emojiCode) { const { emojiType } = this.isEmoji(emojiCode); - if (emojiType === 'shortcode') { - if (emojiLookup[emojiCode]) return emojiLookup[emojiCode]; - } else if (emojiType === 'unicode') { + if (emojiType === 'shortcode') { // Short code emoji + return this.getShortCodeEmoji(emojiCode); + } else if (emojiType === 'unicode') { // Unicode emoji return String.fromCodePoint(parseInt(emojiCode.substr(2), 16)); + } else if (emojiType === 'glyph') { // Emoji is a glyph + return emojiCode; } - return emojiCode; // Emoji is a glyph already, just return + this.imageNotFound(`Unrecognized emoji: '${emojiCode}'`); + return null; }, /* Get favicon URL, for items which use the favicon as their icon */ getFavicon(fullUrl, specificApi) { @@ -109,16 +155,17 @@ export default { }, /* Get the URL for a favicon, but using the non-default favicon API */ getCustomFavicon(fullUrl, faviconIdentifier) { + let errorMsg = ''; const faviconApi = faviconIdentifier.split('favicon-')[1]; if (!faviconApi) { - ErrorHandler('Favicon API not specified'); + errorMsg = 'Favicon API not specified'; } else if (!Object.keys(faviconApiEndpoints).includes(faviconApi)) { - ErrorHandler(`The specified favicon API, '${faviconApi}' cannot be found.`); + errorMsg = `The specified favicon API, '${faviconApi}' cannot be found.`; } else { return this.getFavicon(fullUrl, faviconApi); } // Error encountered, favicon service not found - this.broken = true; + this.imageNotFound(errorMsg); return undefined; }, /* If using favicon for icon, and if service is running locally (determined by local IP) */ @@ -140,69 +187,53 @@ export default { getSimpleIcon(img) { const imageName = img.replace('si-', ''); const icon = simpleIcons.Get(imageName); + if (!icon) { + this.imageNotFound(`No icon was found for '${imageName}' in Simple Icons`); + return null; + } return icon.path; }, /* Gets home-lab icon from GitHub */ - getHomeLabIcon(img) { + getHomeLabIcon(img, cdn) { const imageName = img.replace('hl-', '').toLocaleLowerCase(); - return iconCdns.homeLabIcons.replace('{icon}', imageName); - }, - /* Checks if the icon is from a local image, remote URL, SVG or font-awesome */ - getIconPath(img, url) { - switch (this.determineImageType(img)) { - case 'url': return img; - case 'img': return this.getLocalImagePath(img); - case 'favicon': return this.getFavicon(url); - case 'custom-favicon': return this.getCustomFavicon(url, img); - case 'generative': return this.getGenerativeIcon(url); - case 'mdi': return img; // Material design icons - case 'simple-icons': return this.getSimpleIcon(img); - case 'home-lab-icons': return this.getHomeLabIcon(img); - case 'svg': return img; // Local SVG icon - case 'emoji': return img; // Emoji/ unicode - default: return ''; - } - }, - /* Checks if the icon is from a local image, remote URL, SVG or font-awesome */ - determineImageType(img) { - let imgType = ''; - if (!img) imgType = 'none'; - else if (this.isUrl(img)) imgType = 'url'; - else if (this.isImage(img)) imgType = 'img'; - else if (img.includes('fa-')) imgType = 'font-awesome'; - else if (img.includes('mdi-')) imgType = 'mdi'; - else if (img.includes('si-')) imgType = 'si'; - else if (img.includes('hl-')) imgType = 'home-lab-icons'; - else if (img.includes('favicon-')) imgType = 'custom-favicon'; - else if (img === 'favicon') imgType = 'favicon'; - else if (img === 'generative') imgType = 'generative'; - else if (this.isEmoji(img).isEmoji) imgType = 'emoji'; - else imgType = 'none'; - return imgType; + return (cdn || iconCdns.homeLabIcons).replace('{icon}', imageName); }, /* For a given URL, return the hostname only. Used for favicon and generative icons */ getHostName(url) { - try { return new URL(url).hostname; } catch (e) { return url; } + try { + return new URL(url).hostname.split('.').slice(-2).join('.'); + } catch (e) { + ErrorHandler('Unable to format URL'); + return url; + } }, /* Called when the path to the image cannot be resolved */ - imageNotFound() { + imageNotFound(errorMsg) { + let outputMessage = ''; + if (errorMsg && typeof errorMsg === 'string') { + outputMessage = errorMsg; + } else if (!this.icon) { + outputMessage = 'Icon not specified'; + } else { + outputMessage = `The path to '${this.icon}' could not be resolved`; + } + ErrorHandler(outputMessage); this.broken = true; - ErrorHandler(`The path to '${this.icon}' could not be resolved`); }, /* Called when initial icon has resulted in 404. Attempts to find new icon */ getFallbackIcon() { if (this.attemptedFallback) return undefined; // If this is second attempt, then give up const { iconType } = this; - const markAsSttempted = () => { - this.broken = false; - this.attemptedFallback = true; - }; + const markAsAttempted = () => { this.broken = false; this.attemptedFallback = true; }; if (iconType.includes('favicon')) { // Specify fallback for favicon-based icons - markAsSttempted(); + markAsAttempted(); return this.getFavicon(this.url, 'local'); } else if (iconType === 'generative') { - markAsSttempted(); + markAsAttempted(); return this.getGenerativeIcon(this.url, iconCdns.generativeFallback); + } else if (iconType === 'home-lab-icons') { + markAsAttempted(); + return this.getHomeLabIcon(this.icon, iconCdns.homeLabIconsFallback); } return undefined; }, @@ -290,7 +321,13 @@ export default { } /* Icon Not Found */ .missing-image { - width: 3.5rem; + width: 2rem; + &.small { + width: 1.5rem !important; + } + &.large { + width: 2.5rem; + } path { fill: currentColor; } diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 81d49b47..16418044 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -27,6 +27,8 @@ module.exports = { faviconApi: 'allesedv', /* The default sort order for sections */ sortOrder: 'default', + /* If no 'target' specified, this is the default opening method */ + openingMethod: 'newtab', /* The page paths for each route within the app for the router */ routePaths: { home: '/home', @@ -74,6 +76,18 @@ module.exports = { 'high-contrast-dark', 'high-contrast-light', ], + /* Default color options for the theme configurator swatches */ + swatches: [ + ['#eb5cad', '#985ceb', '#5346f3', '#5c90eb'], + ['#5cdfeb', '#00CCB4', '#5ceb8d', '#afeb5c'], + ['#eff961', '#ebb75c', '#eb615c', '#eb2d6c'], + ['#060913', '#141b33', '#1c2645', '#263256'], + ['#2b2d42', '#1a535c', '#372424', '#312437'], + ['#f5f5f5', '#d9d9d9', '#bfbfbf', '#9a9a9a'], + ['#636363', '#363636', '#313941', '#0d0d0d'], + ], + /* Which CSS variables to show in the first view of theme configurator */ + mainCssVars: ['primary', 'background', 'background-darker'], /* Which structural components should be visible by default */ visibleComponents: { splashScreen: false, @@ -88,8 +102,6 @@ module.exports = { 'minimal', 'login', 'download', - 'landing-page-minimal', - // '404', ], /* Key names for local storage identifiers */ localStorageKeys: { @@ -138,17 +150,14 @@ module.exports = { PAGE_INFO: 'pageInfo', APP_CONFIG: 'appConfig', SECTIONS: 'sections', + WIDGETS: 'widgets', }, - /* Which CSS variables to show in the first view of theme configurator */ - mainCssVars: ['primary', 'background', 'background-darker'], /* Amount of time to show splash screen, when enabled, in milliseconds */ - splashScreenTime: 1900, + splashScreenTime: 1000, /* Page meta-data, rendered in the header of each view */ metaTagData: [ { name: 'description', content: 'A simple static homepage for you\'re server' }, ], - /* If no 'target' specified, this is the default opening method */ - openingMethod: 'newtab', /* Default option for Toast messages */ toastedOptions: { position: 'bottom-center', @@ -192,6 +201,7 @@ module.exports = { localPath: './item-icons', faviconName: 'favicon.ico', homeLabIcons: 'https://raw.githubusercontent.com/WalkxCode/dashboard-icons/master/png/{icon}.png', + homeLabIconsFallback: 'https://raw.githubusercontent.com/NX211/homer-icons/master/png/{icon}.png', }, /* URLs for web search engines */ searchEngineUrls: { @@ -233,16 +243,6 @@ module.exports = { '/so': 'stackoverflow', '/wa': 'wolframalpha', }, - /* Available built-in colors for the theme builder */ - swatches: [ - ['#eb5cad', '#985ceb', '#5346f3', '#5c90eb'], - ['#5cdfeb', '#00CCB4', '#5ceb8d', '#afeb5c'], - ['#eff961', '#ebb75c', '#eb615c', '#eb2d6c'], - ['#060913', '#141b33', '#1c2645', '#263256'], - ['#2b2d42', '#1a535c', '#372424', '#312437'], - ['#f5f5f5', '#d9d9d9', '#bfbfbf', '#9a9a9a'], - ['#636363', '#363636', '#313941', '#0d0d0d'], - ], /* Use your own self-hosted Sentry instance. Only used if error reporting is turned on */ sentryDsn: 'https://3138ea85f15a4fa883a5b27a4dc8ee28@o937511.ingest.sentry.io/5887934', /* A JS enum for indicating the user state, when guest mode + authentication is enabled */ From c14eaf0aa2fdf3538b0c28baff1d8391bfd0733b Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Thu, 2 Dec 2021 18:57:52 +0000 Subject: [PATCH 009/118] :construction: Working on the weather widget --- src/components/Widgets/Weather.vue | 199 +++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/components/Widgets/Weather.vue diff --git a/src/components/Widgets/Weather.vue b/src/components/Widgets/Weather.vue new file mode 100644 index 00000000..02aea987 --- /dev/null +++ b/src/components/Widgets/Weather.vue @@ -0,0 +1,199 @@ + + + + + From 01fe0f0dbd95e800c90ab2772540a7077b7dbe20 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 3 Dec 2021 16:51:19 +0000 Subject: [PATCH 010/118] :bento: Adds assets for weather widget --- public/widget-resources/WeatherIcons.eot | Bin 0 -> 5908 bytes public/widget-resources/WeatherIcons.svg | 69 +++++++++++++++++++++ public/widget-resources/WeatherIcons.ttf | Bin 0 -> 5724 bytes public/widget-resources/WeatherIcons.woff | Bin 0 -> 3624 bytes public/widget-resources/WeatherIcons.woff2 | Bin 0 -> 3028 bytes src/styles/weather-icons.scss | 43 +++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 public/widget-resources/WeatherIcons.eot create mode 100644 public/widget-resources/WeatherIcons.svg create mode 100644 public/widget-resources/WeatherIcons.ttf create mode 100644 public/widget-resources/WeatherIcons.woff create mode 100644 public/widget-resources/WeatherIcons.woff2 create mode 100644 src/styles/weather-icons.scss diff --git a/public/widget-resources/WeatherIcons.eot b/public/widget-resources/WeatherIcons.eot new file mode 100644 index 0000000000000000000000000000000000000000..30cde10b5416ac068a9161280de4a6432ae23005 GIT binary patch literal 5908 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/widget-resources/WeatherIcons.ttf b/public/widget-resources/WeatherIcons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..55925b45aab21e1187ffdb36275959905b86c590 GIT binary patch literal 5724 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 literal 0 HcmV?d00001 diff --git a/public/widget-resources/WeatherIcons.woff b/public/widget-resources/WeatherIcons.woff new file mode 100644 index 0000000000000000000000000000000000000000..05b3b4067aec431cac8f3b8a17f15b1a05e02f65 GIT binary patch literal 3624 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 literal 0 HcmV?d00001 diff --git a/public/widget-resources/WeatherIcons.woff2 b/public/widget-resources/WeatherIcons.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e7176427c2188851179605af2c78c21275f78ac3 GIT binary patch literal 3028 zcmV;_3oG<@Pew8T0RR9101MOr3jhEB02W*T01Jiy0RR9100000000000000000000 z0000SR0dW6glr0)0GBZVHUcCAGz%O61Rw>3Mh9^lD-uzZZ&sh|ha^eAAc8k0fJ6A$ z!vP8a002M%03djxwcnfBTTCsQCX#CU32!7qH)~xyLp+EkJA|8dAs6rvw35(9Y7U)f z>YLZu90q}I!zwmY_U1D&rXp{{Y$mi0t)ImH{p6F1$QPp2d`pPotFN! zUuyp*Ws(?j6|%-u`Gungfjiz1ZvaUZSXFj>#}lxOtL;Rn3Zd&{4b~ixAp|Ble4Js(Y(YH3ikus%H#j`7MmC4XvR$H*695pOtGgz_h<$Kb zSysCsUSad49{jd@K_x;g$zjaZQyQS~%-w8UlIQCeef;|7crqaL@?pm^`YR(8Q_`?Q zRXsu=TL}F$H^V_6TdvT9cFd)-!^()>6Nr6^O7U(t@Dbzs5B^Jig7ov}+ru9K`BWeaxM{n0jpTmGs8C3V{TjrM6bU1G9g}xYPZr+%4okI&+(O6w1!)0Pc-4{H6xE( zK9xdC7e3axf>}ypzZYMn<1MB~mxCDeGREc<9!DnYXfUmu-#?F_2t-8Al$l0RS^DWj z^?<_t0i*LUAfUP#%wC5vNR7I#yM?r=-yp3B*Pk@BLIz%gt@jFtf3;RynRP3{g60I= z12F`5#1Xl0CBxiYEVZ1%rNmyNC@9yYoQN5UzwZhM-PTy(_N?&jai{B-qVfTSv{n<< zXxUUN-L&p?pjQuTuC5$%wC}TJnz42rPC?q{)nS~=&|<(BDUB`+m_aCkXiN_^OXspT;eSme<2NXEKT+O zR^4{*yHr|cszR;%LVf!Y>K^I-07%EeF{*4$2+(dNH}m@|Q=N<5LmTW0PPnMeDajf6 zcz#a}Onzz}MeI!dkv$9T{qJxRL)5!^^3-;|OjWE2^NTGRx^osP5ds*m`!24TcHj(* zg*1qO5y%1OLfN_z(6yj*_KYpEo0=6q58R6ZEP6N!&juZXAkfqh4_@$94fY4X9or6Z=Y+8d>LD#i(nN+2RiEZZR+GgDn z-;_X)fBI9eaC(4V4cD@KAjW-gT{aM99Qq2698Q$H5 z=xcu#M=fo$baboh??X6Ya-s;S`X}tjv-IY#mtVl;&G^ajN^4v|A6XH}>o035h}_%o zT%w0I$NU4^W^?mZKTD#~8N~^W#f=Gk0$H@t8AmWKaxMo}RYzc4#LszS6g@~z6C*V~ zEyze(k`kA^Bbjk^TpY(9r73bD-$|b+k2IS#=C!fgh^VCP$>Jzj#4k*yn3qYdcmXy- zD@H&86WO}+^$_>nhDWdpRPD#C#e6-c4($>_0iFd~yXCfEP~djSKA07XpcCr@t!=W3 z6%}Y{r>KC|c2I(fs1A8;bgl{v!O9E$>Y@>k+*eXv`|~>$;fwY?(UFxD274_DXO6O$ z>6?tQ-KhHJ-!F&%W9}ct@txa9v_bZmWo>M23q#bjC@iARm*qz3!vA*{?pe1*PVaW) z?Fa2{k+fb1>>$QqE(mlj%2$mCIp$WmfIUiKZ;W%9rIm8loh5f&L(MXm>#^Ns+}kht z^2%F3GpJcEZh-x!U^jvZus@vsL==6dq5=~zrCs@&e*+bh3d%Wv)1k0OOn)t`$Gn(z zmH|OzSmE>)YRc6@S1~&9VR{OZQ!wYTmaHHpz5WP~-o~7zPtc@cbNk!{Yt9<48MGd> zc#yXu+{|`MD7M<5ry40tMi!UOqcy%%k3bnl%C3hn^2k)1Hm)Krtj%;MnC8`e`_?|x zIrG!zys;7a5o1_goZc#S`qAtRkvO*FB(&?#Gs;(NjO_+1pe<+7(24&q8rapuvTw8R ziJgDI0$VBC^B74f*0kQOq~@Syq62LQ6WO1P3@m_RPE&9jygR@Ak+5$R*}CyYk1MuD zvHt#zMzQuWV|I}0ZRO3Gd?NiZFa4kNypOagzy6Yn$F@Hgi-#W9MMSl~7aiU<==q2K zNk@$cX@G3M$F!G3Kh4hzk|_EHXef~IuBHxV)03DG9?d_XQ~voEMd2D%o`oYGV*HvZ zD^s(@_fym~Il=NLnMfrjOp1?%qpL2da#AbP0-?o(kyyOWmR4yhQC&K_v~j|W%F5~E zH(s89nI33gw?t#qED2TH)ErepYUB9nxWtSJ8!v&<8|)3KE=n%R_U*^@8_5pj28wz* zgU7G~xB;N!C*YOxtOz`%TTx<>rNJS`0O#agWpcl1^hqyOD##mF@+Kd$xeqLF>mPAR z!e^a*2CCbA*cL7VyvjMX^~6J{HWynb)VV*k|Cs^Kk+qN27t-KlFg27!a`6|T!w{y{ zf`gaDaIhT`37=<`;F}L%oZNT_Z+UixC7kk%AR=gju!P)P+&i3jyr76D140U8NQM%{ zHki-yMc`rit_({O7a<7CpJ#+IDDba^QzU(?g1xRvsOjwR`)W%CcfH466COwZq*AD< z9qjkfnQ0kuFIOb^gc^ID;G_pN)3pumthzcE?W*y6=|aDcJd?-crvo+qA%deWt*Wjr z&?Y_I#a=PZ;r9Y70(R=CLZF7uIPl}6mQs?_O+6m$Q<9hxsPeP5mcbW}CX+NWBt;SV zqB^6$fD@WQG@JcvX~0bub*gni>w|0X$!-V&4px*#RS6fM4e9Da zE?G)zIpJk{ABTm%a-@h56oCkah!F`QMP!H^Q6Nf0h2X%{WIA=vw>rzIgfNF%V@+m; W=WCzo#4@wYS&m9<$j)#A0002mI Date: Fri, 3 Dec 2021 16:54:11 +0000 Subject: [PATCH 011/118] :memo: Adds docs for the weather widget --- docs/widgets.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/widgets.md b/docs/widgets.md index 2589685e..b7dab491 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -33,6 +33,32 @@ A simple, live-updating time and date widget with time-zone support. All options hideDate: false ``` +### Weather + +A simple, live-updating local weather component, showing temperature, conditions and more info. + +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`apiKey`** | `string` | Required | Your OpenWeatherMap API key. You can get one for free at [openweathermap.org](https://openweathermap.org/) +**`city`** | `string` | Required | A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format +**`units`** | `string` | _Optional_ | The units to use for displaying data, can be either `metric` or `imperial`. Defaults to `metric` +**`hideDetails`** | `boolean` | _Optional_ | If set to `true`, the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to `false` + +##### Example + +```yaml +- name: Local Weather + icon: fas fa-clouds + type: weather + options: + apiKey: 6e29c7d514cf890f846d58178b6d418f + city: London + units: metric + hideDetails: false +``` + --- ## Dynamic Widgets From 7851467397366cab74157d913ae0d1c9f79a49d1 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 3 Dec 2021 16:55:07 +0000 Subject: [PATCH 012/118] :zap: Refactors and improves the icon component --- src/components/LinkItems/ItemIcon.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LinkItems/ItemIcon.vue b/src/components/LinkItems/ItemIcon.vue index 41786b8e..0cfa4872 100644 --- a/src/components/LinkItems/ItemIcon.vue +++ b/src/components/LinkItems/ItemIcon.vue @@ -159,7 +159,7 @@ export default { const faviconApi = faviconIdentifier.split('favicon-')[1]; if (!faviconApi) { errorMsg = 'Favicon API not specified'; - } else if (!Object.keys(faviconApiEndpoints).includes(faviconApi)) { + } else if (!Object.keys(faviconApiEndpoints).includes(faviconApi) && faviconApi !== 'local') { errorMsg = `The specified favicon API, '${faviconApi}' cannot be found.`; } else { return this.getFavicon(fullUrl, faviconApi); From de97f562a12032407d917e6504a7b8da35cb1839 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 3 Dec 2021 16:55:57 +0000 Subject: [PATCH 013/118] :alien: Adds constants for weather API endpoint --- src/utils/defaults.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 16418044..9dd99fcd 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -203,6 +203,10 @@ module.exports = { homeLabIcons: 'https://raw.githubusercontent.com/WalkxCode/dashboard-icons/master/png/{icon}.png', homeLabIconsFallback: 'https://raw.githubusercontent.com/NX211/homer-icons/master/png/{icon}.png', }, + /* API endpoints for widgets that need to fetch external data */ + widgetApiEndpoints: { + weather: 'https://api.openweathermap.org/data/2.5/weather', + }, /* URLs for web search engines */ searchEngineUrls: { // Common From 3f8b180553814f0da8ebb16cd0566521e044d7aa Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 3 Dec 2021 16:56:33 +0000 Subject: [PATCH 014/118] :construction: Nearly finished the weather widget --- src/components/Widgets/Weather.vue | 151 ++++++++++++++------------ src/components/Widgets/WidgetBase.vue | 3 + 2 files changed, 87 insertions(+), 67 deletions(-) diff --git a/src/components/Widgets/Weather.vue b/src/components/Widgets/Weather.vue index 02aea987..cb6d2d68 100644 --- a/src/components/Widgets/Weather.vue +++ b/src/components/Widgets/Weather.vue @@ -7,44 +7,18 @@

{{ description }}

- -
-

- Minimum - {{ minTemp }} -

-

- Maximum - {{ maxTemp }} -

-

- Feels Like - {{ feelsLike }} -

-
- -
-

- Wind - {{ wind }} -

-

- Clouds - {{ clouds }} -

-

- Humidity - {{ humidity }} -

-

- Pressure - {{ pressure }} -

-

- Visibility - {{ visibility }} -

+
+
+

+ {{weather.label}} + {{ weather.value }} +

+
+ +

+ {{ showDetails ? 'Show Less' : ' Expand Details' }} +

@@ -63,14 +37,8 @@ export default { icon: null, description: null, temp: null, - minTemp: null, - maxTemp: null, - feelsLike: null, - pressure: null, - visibility: null, - humidity: null, - wind: null, - clouds: null, + showDetails: false, + weatherDetails: [], }; }, computed: { @@ -97,9 +65,11 @@ export default { }, }, methods: { + /* Adds units symbol to temperature, depending on metric or imperial */ processTemp(temp) { return `${Math.round(temp)}${this.tempDisplayUnits}`; }, + /* Fetches the weather from OpenWeatherMap, and processes results */ fetchWeather() { axios.get(this.endpoint) .then((response) => { @@ -107,19 +77,36 @@ export default { this.icon = data.weather[0].icon; this.description = data.weather[0].description; this.temp = this.processTemp(data.main.temp); - this.minTemp = this.processTemp(data.main.temp_min); - this.maxTemp = this.processTemp(data.main.temp_max); - this.feelsLike = this.processTemp(data.main.feels_like); - this.pressure = `${data.main.pressure}hPa`; - this.humidity = `${data.main.humidity}%`; - this.visibility = data.visibility; - this.wind = `${data.wind.speed}${this.speedDisplayUnits}`; - this.clouds = `${data.clouds.all}%`; + if (!this.options.hideDetails) { + this.makeWeatherData(data); + } }) .catch(() => { this.throwError('Failed to fetch weather'); }); }, + /* If showing additional info, then generate this data too */ + makeWeatherData(data) { + this.weatherDetails = [ + [ + { label: 'Min Temp', value: this.processTemp(data.main.temp_min) }, + { label: 'Max Temp', value: this.processTemp(data.main.temp_max) }, + { label: 'Feels Like', value: this.processTemp(data.main.feels_like) }, + ], + [ + { label: 'Pressure', value: `${data.main.pressure}hPa` }, + { label: 'Humidity', value: `${data.main.humidity}%` }, + { label: 'visibility', value: data.visibility }, + { label: 'wind', value: `${data.wind.speed}${this.speedDisplayUnits}` }, + { label: 'clouds', value: `${data.clouds.all}%` }, + ], + ]; + }, + /* Show/ hide additional weather info */ + toggleDetails() { + this.showDetails = !this.showDetails; + }, + /* Validate input props, and print warning if incorrect */ checkProps() { const ops = this.options; let valid = true; @@ -137,6 +124,7 @@ export default { } return valid; }, + /* Just outputs an error message */ throwError(error) { ErrorHandler(error); this.error = true; @@ -153,9 +141,14 @@ export default { diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index 9b06dab0..3a0f5233 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -12,6 +12,7 @@ > + @@ -19,14 +20,16 @@ + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index 3a0f5233..f466ffc0 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -1,19 +1,20 @@ @@ -21,6 +22,7 @@ import Clock from '@/components/Widgets/Clock.vue'; import Weather from '@/components/Widgets/Weather.vue'; import WeatherForecast from '@/components/Widgets/WeatherForecast.vue'; +import TflStatus from '@/components/Widgets/TflStatus.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -30,6 +32,7 @@ export default { Clock, Weather, WeatherForecast, + TflStatus, }, props: { widget: Object, diff --git a/src/utils/defaults.js b/src/utils/defaults.js index c29aaf19..467e45cc 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -207,6 +207,7 @@ module.exports = { widgetApiEndpoints: { weather: 'https://api.openweathermap.org/data/2.5/weather', weatherForecast: 'https://api.openweathermap.org/data/2.5/forecast/daily', + tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status', }, /* URLs for web search engines */ searchEngineUrls: { From 9915100c13690342623a9306727c8477cefd4577 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 18:44:22 +0000 Subject: [PATCH 017/118] :heavy_plus_sign: Adds Frappe charts, for showing widget data --- package.json | 3 ++- yarn.lock | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fe10ee49..8e2b1280 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "connect-history-api-fallback": "^1.6.0", "crypto-js": "^4.1.1", "express": "^4.17.1", + "frappe-charts": "^1.6.2", "js-yaml": "^4.1.0", "keycloak-js": "^15.0.2", "register-service-worker": "^1.6.2", @@ -95,4 +96,4 @@ "> 1%", "last 2 versions" ] -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 7f30c03e..3a90a685 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4691,6 +4691,11 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +frappe-charts@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.6.2.tgz#4671a943a8606e5020180fa65c8ea1835c510baf" + integrity sha512-9TC3/+YVUi84yYoEbxFiSqu+1FQ5If/ydUNj6i8FRpwynd08t6a7RkS+IRJozAk6NfdL8/LVTTE1DUOjjKZZxg== + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" From e8fa255e2ecc110f903c6b97fa9713871af2069f Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 18:44:59 +0000 Subject: [PATCH 018/118] :zap: Adds second param to ErrorHandler for printing stack trace --- src/utils/ErrorHandler.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/utils/ErrorHandler.js b/src/utils/ErrorHandler.js index 280c4a0a..4c56e900 100644 --- a/src/utils/ErrorHandler.js +++ b/src/utils/ErrorHandler.js @@ -22,10 +22,15 @@ const appendToErrorLog = (msg) => { * If error reporting is enabled, will also log the message to Sentry * If you wish to use your own error logging service, put code for it here */ -const ErrorHandler = function handler(msg) { - warningMsg(msg); // Print to console - appendToErrorLog(msg); // Save to local storage - Sentry.captureMessage(`[USER-WARN] ${msg}`); // Report to bug tracker (if enabled) +const ErrorHandler = function handler(msg, errorStack) { + // Print to console + warningMsg(msg); + // If stack trace included, then print that too + if (errorStack) console.warn(errorStack); // eslint-disable-line no-console + // Save to local storage + appendToErrorLog(msg); + // Report to bug tracker (if enabled) + Sentry.captureMessage(`[USER-WARN] ${msg}`); }; /* Similar to error handler, but for recording general info */ From c12eac2bbfe4c7c688d148b9cd7c445a005bdf2c Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 18:46:39 +0000 Subject: [PATCH 019/118] :lipstick: Adds Red styling for Line Closed status --- src/components/Widgets/TflStatus.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Widgets/TflStatus.vue b/src/components/Widgets/TflStatus.vue index ea92b4e4..14bb21f4 100644 --- a/src/components/Widgets/TflStatus.vue +++ b/src/components/Widgets/TflStatus.vue @@ -84,6 +84,7 @@ export default { getStatusColor(code) { if (code <= 6) return 'red'; if (code <= 9) return 'orange'; + if (code === 20) return 'dark'; return 'green'; }, /* If user only wants to see results from certain lines, filter the rest out */ @@ -128,6 +129,7 @@ export default { &.green { color: var(--success); } &.orange { color: var(--warning); } &.red { color: var(--danger); } + &.dark { color: #fa360f; } } .disruption { opacity: var(--dimming-factor); From ebd4c483008b6133df8394b1ff13558531141c2c Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 18:50:11 +0000 Subject: [PATCH 020/118] :sparkles: Develops a crypto price chart widget --- docs/widgets.md | 31 +++- src/components/Widgets/CryptoPriceChart.vue | 182 ++++++++++++++++++++ src/components/Widgets/WidgetBase.vue | 3 + 3 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/components/Widgets/CryptoPriceChart.vue diff --git a/docs/widgets.md b/docs/widgets.md index 83accd78..86465ca8 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -86,6 +86,30 @@ Displays the weather (temperature and conditions) for the next few days for a gi units: imperial ``` +### Crypto Token Price History + +Shows recent price history for a given crypto asset, using price data fetched from [CoinGecko](https://www.coingecko.com/) + +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`asset`** | `string` | Required | Name of a crypto asset, coin or token to fetch price data for +**`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 + +##### Example + +```yaml +- name: Bitcoin Price + icon: fab fa-bitcoin + type: crypto-price-chart + options: + asset: bitcoin + currency: GBP + numDays: 7 +``` + ### TFL Status Shows real-time tube status of the London Underground. All options are optional. @@ -101,7 +125,12 @@ Shows real-time tube status of the London Underground. All options are optional. ##### Example ```yaml - - name: TFL Status +- name: London Underground + type: tfl-status +``` + +```yaml + - name: Commute icon: '🚋' type: tfl-status options: diff --git a/src/components/Widgets/CryptoPriceChart.vue b/src/components/Widgets/CryptoPriceChart.vue new file mode 100644 index 00000000..ac1e3559 --- /dev/null +++ b/src/components/Widgets/CryptoPriceChart.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index f466ffc0..ad55984e 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -14,6 +14,7 @@ + @@ -23,6 +24,7 @@ import Clock from '@/components/Widgets/Clock.vue'; import Weather from '@/components/Widgets/Weather.vue'; import WeatherForecast from '@/components/Widgets/WeatherForecast.vue'; import TflStatus from '@/components/Widgets/TflStatus.vue'; +import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -33,6 +35,7 @@ export default { Weather, WeatherForecast, TflStatus, + CryptoPriceChart, }, props: { widget: Object, From 65733d2af722b4c4ccaffd8ed8793b7629978111 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 18:50:31 +0000 Subject: [PATCH 021/118] :sparkles: Develops a crypto price chart widget --- src/utils/defaults.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 467e45cc..92baa584 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -208,6 +208,7 @@ module.exports = { weather: 'https://api.openweathermap.org/data/2.5/weather', weatherForecast: 'https://api.openweathermap.org/data/2.5/forecast/daily', tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status', + cryptoPrices: 'https://api.coingecko.com/api/v3/coins/', }, /* URLs for web search engines */ searchEngineUrls: { From d5432774cc6563b11419c859a35db1029116d91b Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 20:46:22 +0000 Subject: [PATCH 022/118] :zap: Adds helper functions for converting date, and currency --- src/utils/MiscHelpers.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/utils/MiscHelpers.js b/src/utils/MiscHelpers.js index da9a683f..3591b7cd 100644 --- a/src/utils/MiscHelpers.js +++ b/src/utils/MiscHelpers.js @@ -46,3 +46,36 @@ export const applyItemId = (inputSections) => { }); return sections; }; + +export const convertTimestampToDate = (timestamp) => { + const localFormat = navigator.language; + const dateFormat = { + weekday: 'short', day: 'numeric', month: 'short', year: 'numeric', + }; + const date = new Date(timestamp).toLocaleDateString(localFormat, dateFormat); + return `${date}`; +}; + +/* Given a currency code, return the corresponding unicode symbol */ +export const findCurrencySymbol = (currencyCode) => { + const code = currencyCode.toUpperCase().trim(); + const currencies = { + USD: '$', // US Dollar + EUR: '€', // Euro + CRC: '₡', // Costa Rican Colón + GBP: '£', // British Pound Sterling + ILS: '₪', // Israeli New Sheqel + INR: '₹', // Indian Rupee + JPY: '¥', // Japanese Yen + KRW: '₩', // South Korean Won + NGN: '₦', // Nigerian Naira + PHP: '₱', // Philippine Peso + PLN: 'zł', // Polish Zloty + PYG: '₲', // Paraguayan Guarani + THB: '฿', // Thai Baht + UAH: '₴', // Ukrainian Hryvnia + VND: '₫', // Vietnamese Dong + }; + if (currencies[code]) return currencies[code]; + return code; +}; From d3c4fb50aea0dbde6d331216cba6ffc19cb76509 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 21:10:05 +0000 Subject: [PATCH 023/118] :train: Updates train status codes --- src/components/Widgets/TflStatus.vue | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Widgets/TflStatus.vue b/src/components/Widgets/TflStatus.vue index 14bb21f4..51293591 100644 --- a/src/components/Widgets/TflStatus.vue +++ b/src/components/Widgets/TflStatus.vue @@ -82,10 +82,11 @@ export default { }, /* Get color, depending on the status code */ getStatusColor(code) { - if (code <= 6) return 'red'; - if (code <= 9) return 'orange'; - if (code === 20) return 'dark'; - return 'green'; + if (code === 20) return 'dark'; // Strike action + if (code === 0) return 'info'; // Special service or upcoming planned works + if (code <= 6) return 'red'; // Closed, part-closed or severe delays + if (code <= 9) return 'orange'; // Minor delays, planned bus replacement + return 'green'; // Good Service - Everything is awesome! }, /* If user only wants to see results from certain lines, filter the rest out */ filterByLineName(allLines, usersLines) { @@ -129,6 +130,7 @@ export default { &.green { color: var(--success); } &.orange { color: var(--warning); } &.red { color: var(--danger); } + &.info { color: var(--info); } &.dark { color: #fa360f; } } .disruption { From 985b0000fa98fc3bfb8c3c2a7f7f83f604a6211a Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 21:11:07 +0000 Subject: [PATCH 024/118] :sparkles: Adds a crypto price watch-list widget --- docs/widgets.md | 32 ++++- src/components/Widgets/CryptoWatchList.vue | 154 +++++++++++++++++++++ src/components/Widgets/WidgetBase.vue | 3 + src/utils/defaults.js | 1 + 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/components/Widgets/CryptoWatchList.vue diff --git a/docs/widgets.md b/docs/widgets.md index 86465ca8..180d10a1 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -86,6 +86,36 @@ Displays the weather (temperature and conditions) for the next few days for a gi 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 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** --- | --- | --- | --- -**`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` **`numDays`** | `number` | _Optional_ | The number of days of price history to render. Defaults to `7`, min: `1`, max: `30` days diff --git a/src/components/Widgets/CryptoWatchList.vue b/src/components/Widgets/CryptoWatchList.vue new file mode 100644 index 00000000..56d6fced --- /dev/null +++ b/src/components/Widgets/CryptoWatchList.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index ad55984e..fe4b0a9c 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -15,6 +15,7 @@ + @@ -25,6 +26,7 @@ import Weather from '@/components/Widgets/Weather.vue'; import WeatherForecast from '@/components/Widgets/WeatherForecast.vue'; import TflStatus from '@/components/Widgets/TflStatus.vue'; import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue'; +import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -36,6 +38,7 @@ export default { WeatherForecast, TflStatus, CryptoPriceChart, + CryptoWatchList, }, props: { widget: Object, diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 92baa584..cbc567d2 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -209,6 +209,7 @@ module.exports = { weatherForecast: 'https://api.openweathermap.org/data/2.5/forecast/daily', tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status', cryptoPrices: 'https://api.coingecko.com/api/v3/coins/', + cryptoWatchList: 'https://api.coingecko.com/api/v3/coins/markets/', }, /* URLs for web search engines */ searchEngineUrls: { From 9d21fa48d017c78100a4fb9a0279b9b60449a840 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 22:29:52 +0000 Subject: [PATCH 025/118] :sparkles: Adds XKCD comic widget --- docs/widgets.md | 20 +++++++ src/components/Widgets/WidgetBase.vue | 3 + src/components/Widgets/XkcdComic.vue | 82 +++++++++++++++++++++++++++ src/utils/defaults.js | 1 + 4 files changed, 106 insertions(+) create mode 100644 src/components/Widgets/XkcdComic.vue diff --git a/docs/widgets.md b/docs/widgets.md index 180d10a1..4ad7b798 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -140,6 +140,26 @@ Shows recent price history for a given crypto asset, using price data fetched fr numDays: 7 ``` +### XKCD Comics + +Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webcomic website covering everything from Linux, math, romance, science and language. + +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`comic`** | `string | number` | _Optional_ | Choose which comic to display. Set to either `random`, `latest` or the series number of a specific comic, like `627`. Defaults to `latest` + +##### Example + +```yaml +- name: XKCD of the Day + icon: fas fa-laugh + type: xkcd-comic + options: + comic: latest +``` + ### TFL Status Shows real-time tube status of the London Underground. All options are optional. diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index fe4b0a9c..d4b47836 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -16,6 +16,7 @@ + @@ -27,6 +28,7 @@ import WeatherForecast from '@/components/Widgets/WeatherForecast.vue'; import TflStatus from '@/components/Widgets/TflStatus.vue'; import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue'; import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue'; +import XkcdComic from '@/components/Widgets/XkcdComic.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -39,6 +41,7 @@ export default { TflStatus, CryptoPriceChart, CryptoWatchList, + XkcdComic, }, props: { widget: Object, diff --git a/src/components/Widgets/XkcdComic.vue b/src/components/Widgets/XkcdComic.vue new file mode 100644 index 00000000..99af5e59 --- /dev/null +++ b/src/components/Widgets/XkcdComic.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/utils/defaults.js b/src/utils/defaults.js index cbc567d2..9e07e316 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -210,6 +210,7 @@ module.exports = { tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status', cryptoPrices: 'https://api.coingecko.com/api/v3/coins/', cryptoWatchList: 'https://api.coingecko.com/api/v3/coins/markets/', + xkcdComic: 'https://xkcd.vercel.app/', }, /* URLs for web search engines */ searchEngineUrls: { From f6e11921ef76111f4c5948098b82ea87ed64cc87 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 22:31:16 +0000 Subject: [PATCH 026/118] :sparkles: Adds XKCD comic widget --- docs/widgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets.md b/docs/widgets.md index 4ad7b798..9edfcc71 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -148,7 +148,7 @@ Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webc **Field** | **Type** | **Required** | **Description** --- | --- | --- | --- -**`comic`** | `string | number` | _Optional_ | Choose which comic to display. Set to either `random`, `latest` or the series number of a specific comic, like `627`. Defaults to `latest` +**`comic`** | `string / number` | _Optional_ | Choose which comic to display. Set to either `random`, `latest` or the series number of a specific comic, like `627`. Defaults to `latest` ##### Example From 464eef9338d25356aff65c6885f3f36a3697ebed Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Dec 2021 22:36:43 +0000 Subject: [PATCH 027/118] :memo: Adds screenshots to widget docs --- docs/widgets.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/widgets.md b/docs/widgets.md index 9edfcc71..5ae64731 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -13,6 +13,8 @@ Dashy has support for displaying dynamic content in the form of widgets. There a A simple, live-updating time and date widget with time-zone support. All options are optional. +

+ ##### Options **Field** | **Type** | **Required** | **Description** @@ -37,6 +39,8 @@ A simple, live-updating time and date widget with time-zone support. All options A simple, live-updating local weather component, showing temperature, conditions and more info. +

+ ##### Options **Field** | **Type** | **Required** | **Description** @@ -63,6 +67,8 @@ A simple, live-updating local weather component, showing temperature, conditions Displays the weather (temperature and conditions) for the next few days for a given location. Note that this requires either the free [OpenWeatherMap Student Plan](https://home.openweathermap.org/students), or the Premium Plan. +

+ ##### Options **Field** | **Type** | **Required** | **Description** @@ -90,6 +96,8 @@ Displays the weather (temperature and conditions) for the next few days for a gi Keep track of price changes of your favorite crypto assets. Data is fetched from [CoinGecko](https://www.coingecko.com/) +

+ ##### Options **Field** | **Type** | **Required** | **Description** @@ -120,6 +128,8 @@ Keep track of price changes of your favorite crypto assets. Data is fetched from Shows recent price history for a given crypto asset, using price data fetched from [CoinGecko](https://www.coingecko.com/) +

+ ##### Options **Field** | **Type** | **Required** | **Description** @@ -144,6 +154,8 @@ Shows recent price history for a given crypto asset, using price data fetched fr Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webcomic website covering everything from Linux, math, romance, science and language. +

+ ##### Options **Field** | **Type** | **Required** | **Description** @@ -164,6 +176,8 @@ Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webc Shows real-time tube status of the London Underground. All options are optional. +

+ ##### Options **Field** | **Type** | **Required** | **Description** From a77cb9430fca9a4d2a6a98bf342dbfde9f1cc0b2 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 12 Dec 2021 15:04:34 +0000 Subject: [PATCH 028/118] :sparkles: Creates an FX widget --- docs/widgets.md | 30 +++++++ src/components/Widgets/ExchangeRates.vue | 102 +++++++++++++++++++++++ src/components/Widgets/WidgetBase.vue | 3 + src/utils/defaults.js | 1 + 4 files changed, 136 insertions(+) create mode 100644 src/components/Widgets/ExchangeRates.vue diff --git a/docs/widgets.md b/docs/widgets.md index 5ae64731..768d6a29 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -206,6 +206,36 @@ Shows real-time tube status of the London Underground. All options are optional. - Central ``` +### Exchange Rates + +Display current FX rates in your native currency + +

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`apiKey`** | `string` | Required | API key for [exchangerate-api.com](https://www.exchangerate-api.com/), usually a 24-digit alpha-numeric string. You can sign up for a free account [here](https://app.exchangerate-api.com/sign-up) +**`inputCurrency`** | `string` | Required | The base currency to show results in. Specified as a 3-letter ISO-4217 code, see [here](https://www.exchangerate-api.com/docs/supported-currencies) for the full list of supported currencies, and their symbols +**`outputCurrencies`** | `array` | Required | List or currencies to show results for. Specified as a 3-letter ISO-4217 code, see [here](https://www.exchangerate-api.com/docs/supported-currencies) for the full list of supported currencies, and their symbols + +##### Example + +```yaml +- name: Exchange Rates + icon: fas fa-money-bill-wave + type: exchange-rates + options: + apiKey: xxxxxxxxxxxxxxxxxxxxxxxx + inputCurrency: GBP + outputCurrencies: + - USD + - JPY + - HKD + - KPW +``` + --- ## Dynamic Widgets diff --git a/src/components/Widgets/ExchangeRates.vue b/src/components/Widgets/ExchangeRates.vue new file mode 100644 index 00000000..e0303fef --- /dev/null +++ b/src/components/Widgets/ExchangeRates.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index d4b47836..61275373 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -17,6 +17,7 @@ + @@ -29,6 +30,7 @@ import TflStatus from '@/components/Widgets/TflStatus.vue'; import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue'; import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue'; import XkcdComic from '@/components/Widgets/XkcdComic.vue'; +import ExchangeRates from '@/components/Widgets/ExchangeRates.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -42,6 +44,7 @@ export default { CryptoPriceChart, CryptoWatchList, XkcdComic, + ExchangeRates, }, props: { widget: Object, diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 9e07e316..c2273693 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -211,6 +211,7 @@ module.exports = { cryptoPrices: 'https://api.coingecko.com/api/v3/coins/', cryptoWatchList: 'https://api.coingecko.com/api/v3/coins/markets/', xkcdComic: 'https://xkcd.vercel.app/', + exchangeRates: 'https://v6.exchangerate-api.com/v6/', }, /* URLs for web search engines */ searchEngineUrls: { From 51b7e639cc30e2355354ee1d8607c4e68058b719 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 12 Dec 2021 16:30:07 +0000 Subject: [PATCH 029/118] :sparkles: Creates an stock price chart widget --- docs/widgets.md | 25 +++ src/components/Widgets/StockPriceChart.vue | 176 +++++++++++++++++++++ src/components/Widgets/WidgetBase.vue | 3 + src/utils/defaults.js | 1 + 4 files changed, 205 insertions(+) create mode 100644 src/components/Widgets/StockPriceChart.vue diff --git a/docs/widgets.md b/docs/widgets.md index 768d6a29..109bdf2a 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -236,6 +236,31 @@ Display current FX rates in your native currency - KPW ``` +### Stock Price History + +Shows recent price history for a given publicly-traded stock or share + +

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`apiKey`** | `string` | Required | API key for [Alpha Vantage](https://www.alphavantage.co/), you can get a free API key [here](https://www.alphavantage.co/support/#api-key) +**`stock`** | `string` | Required | The stock symbol for the asset to fetch data for +**`priceTime`** | `string` | _Optional_ | The time to fetch price for. Can be `high`, `low`, `open` or `close`. Defaults to `high` + +##### Example + +```yaml +- name: CloudFlare Stock Price + icon: fas fa-analytics + type: stock-price-chart + options: + stock: NET + apiKey: PGUWSWD6CZTXMT8N +``` + --- ## Dynamic Widgets diff --git a/src/components/Widgets/StockPriceChart.vue b/src/components/Widgets/StockPriceChart.vue new file mode 100644 index 00000000..c4dcbf94 --- /dev/null +++ b/src/components/Widgets/StockPriceChart.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index 61275373..ba78aae4 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -18,6 +18,7 @@ + @@ -31,6 +32,7 @@ import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue'; import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue'; import XkcdComic from '@/components/Widgets/XkcdComic.vue'; import ExchangeRates from '@/components/Widgets/ExchangeRates.vue'; +import StockPriceChart from '@/components/Widgets/StockPriceChart.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -45,6 +47,7 @@ export default { CryptoWatchList, XkcdComic, ExchangeRates, + StockPriceChart, }, props: { widget: Object, diff --git a/src/utils/defaults.js b/src/utils/defaults.js index c2273693..8f978675 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -212,6 +212,7 @@ module.exports = { cryptoWatchList: 'https://api.coingecko.com/api/v3/coins/markets/', xkcdComic: 'https://xkcd.vercel.app/', exchangeRates: 'https://v6.exchangerate-api.com/v6/', + stockPriceChart: 'https://www.alphavantage.co/query', }, /* URLs for web search engines */ searchEngineUrls: { From d9759c06b3f92fc20c40dcae2d55e42285cadd9f Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 12 Dec 2021 19:06:32 +0000 Subject: [PATCH 030/118] :sparkles: Creates a joke-fetching widget --- docs/widgets.md | 26 ++++++++ src/components/Widgets/Jokes.vue | 92 +++++++++++++++++++++++++++ src/components/Widgets/WidgetBase.vue | 3 + src/utils/defaults.js | 1 + 4 files changed, 122 insertions(+) create mode 100644 src/components/Widgets/Jokes.vue diff --git a/docs/widgets.md b/docs/widgets.md index 109bdf2a..7cf34112 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -261,6 +261,32 @@ Shows recent price history for a given publicly-traded stock or share apiKey: PGUWSWD6CZTXMT8N ``` +### Joke + +Renders a programming or generic joke. Data is fetched from the [JokesAPI](https://github.com/Sv443/JokeAPI) by @Sv443 + +

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`category`** | `string` | _Optional_ | Set the category of jokes to return. Use a string to specify a single category, or an array to pass in multiple options. Available options are: `all`, `programming`, `pun`, `dark`, `spooky`, `christmas` and `misc`. An up-to-date list of supported categories can be found [here](https://v2.jokeapi.dev/categories). Defaults to `all` +**`safeMode`** | `boolean` | _Optional_ | Set to `true`, to prevent the fetching of any NSFW jokes. Defaults to `false` +**`language`** | `string` | _Optional_ | Specify the language for returned jokes. The following languages are supported: `en`, `cs`, `de`, `es`, `fr` and `pt`, and an up-to-date list of supported languages can be found [here](https://v2.jokeapi.dev/languages). By default, your system language will be used, if it's supported, otherwise English + +##### Example + +```yaml +- name: Joke + icon: fas fa-laugh + type: joke + options: + safeMode: true + language: en + category: Programming +``` + --- ## Dynamic Widgets diff --git a/src/components/Widgets/Jokes.vue b/src/components/Widgets/Jokes.vue new file mode 100644 index 00000000..ebbaf83e --- /dev/null +++ b/src/components/Widgets/Jokes.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index ba78aae4..678f0b6e 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -19,6 +19,7 @@ + @@ -33,6 +34,7 @@ import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue'; import XkcdComic from '@/components/Widgets/XkcdComic.vue'; import ExchangeRates from '@/components/Widgets/ExchangeRates.vue'; import StockPriceChart from '@/components/Widgets/StockPriceChart.vue'; +import Jokes from '@/components/Widgets/Jokes.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; export default { @@ -48,6 +50,7 @@ export default { XkcdComic, ExchangeRates, StockPriceChart, + Jokes, }, props: { widget: Object, diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 8f978675..1e3d7a24 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -213,6 +213,7 @@ module.exports = { xkcdComic: 'https://xkcd.vercel.app/', exchangeRates: 'https://v6.exchangerate-api.com/v6/', stockPriceChart: 'https://www.alphavantage.co/query', + jokes: 'https://v2.jokeapi.dev/joke/', }, /* URLs for web search engines */ searchEngineUrls: { From ae8179ecd7dcba92b9f0bc0a0779a9bbe14bf13d Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Mon, 13 Dec 2021 14:03:39 +0000 Subject: [PATCH 031/118] :sparkles: Creates an embedable webpage widget --- docs/widgets.md | 80 ++++++++++++++----------- src/components/Widgets/IframeWidget.vue | 35 +++++++++++ src/components/Widgets/WidgetBase.vue | 46 ++++++-------- 3 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 src/components/Widgets/IframeWidget.vue diff --git a/docs/widgets.md b/docs/widgets.md index 7cf34112..1fc3f211 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -3,11 +3,24 @@ Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets availible out-of-the-box (with more on the way!) as well as support for custom widgets to display stats from almost any service with an accessible API. ##### Contents -- [Built-In Widgets](#built-in-widgets) +- [General Widgets](#general-widgets) + - [Clock](#clock) + - [Weather](#weather) + - [Weather Forecast](#weather-forecast) + - [Crypto Watch List](#crypto-watch-list) + - [Crypto Price History](#crypto-token-price-history) + - [XKCD Comics](#xkcd-comics) + - [TFL Status](#tfl-status) + - [Exchange Rates](#exchange-rates) + - [Stock Price History](#stock-price-history) + - [Joke of the Day](#joke) +- [Self-Hosted Services Widgets](#dynamic-widgets) - [Dynamic Widgets](#dynamic-widgets) + - [Iframe Widget](#iframe-widget) + - [HTML Widget](#html-widget) - [Build your own Widget](#build-your-own-widget) -## Built-In Widgets +## General Widgets ### Clock @@ -26,9 +39,7 @@ A simple, live-updating time and date widget with time-zone support. All options ##### Example ```yaml -- name: London Time - icon: fas fa-clock - type: clock +- type: clock options: timeZone: Europe/London format: en-GB @@ -53,9 +64,7 @@ A simple, live-updating local weather component, showing temperature, conditions ##### Example ```yaml -- name: Local Weather - icon: fas fa-clouds - type: weather +- type: weather options: apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx city: London @@ -82,9 +91,7 @@ Displays the weather (temperature and conditions) for the next few days for a gi ##### Example ```yaml -- name: Weather Forecast - icon: ':sunny:' - type: weather-forecast +- type: weather-forecast options: city: California numDays: 6 @@ -109,9 +116,7 @@ Keep track of price changes of your favorite crypto assets. Data is fetched from ##### Example ```yaml - - name: Crypto Prices - icon: fas fa-rocket - type: crypto-watch-list + - type: crypto-watch-list options: currency: GBP sortBy: marketCap @@ -141,9 +146,7 @@ Shows recent price history for a given crypto asset, using price data fetched fr ##### Example ```yaml -- name: Bitcoin Price - icon: fab fa-bitcoin - type: crypto-price-chart +- type: crypto-price-chart options: asset: bitcoin currency: GBP @@ -165,9 +168,7 @@ Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webc ##### Example ```yaml -- name: XKCD of the Day - icon: fas fa-laugh - type: xkcd-comic +- type: xkcd-comic options: comic: latest ``` @@ -189,14 +190,11 @@ Shows real-time tube status of the London Underground. All options are optional. ##### Example ```yaml -- name: London Underground - type: tfl-status +- type: tfl-status ``` ```yaml - - name: Commute - icon: '🚋' - type: tfl-status + - type: tfl-status options: showAll: true sortAlphabetically: true @@ -223,9 +221,7 @@ Display current FX rates in your native currency ##### Example ```yaml -- name: Exchange Rates - icon: fas fa-money-bill-wave - type: exchange-rates +- type: exchange-rates options: apiKey: xxxxxxxxxxxxxxxxxxxxxxxx inputCurrency: GBP @@ -253,9 +249,7 @@ Shows recent price history for a given publicly-traded stock or share ##### Example ```yaml -- name: CloudFlare Stock Price - icon: fas fa-analytics - type: stock-price-chart +- type: stock-price-chart options: stock: NET apiKey: PGUWSWD6CZTXMT8N @@ -278,9 +272,7 @@ Renders a programming or generic joke. Data is fetched from the [JokesAPI](https ##### Example ```yaml -- name: Joke - icon: fas fa-laugh - type: joke +- type: joke options: safeMode: true language: en @@ -291,6 +283,26 @@ Renders a programming or generic joke. Data is fetched from the [JokesAPI](https ## Dynamic Widgets +### Iframe Widget + +Embed any webpage into your dashboard as a widget. + +

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`url`** | `string` | Required | The URL to the webpage to embed + +##### Example + +```yaml +- type: iframe + options: + url: https://fiatleak.com/ +``` + --- ## Build your own Widget diff --git a/src/components/Widgets/IframeWidget.vue b/src/components/Widgets/IframeWidget.vue new file mode 100644 index 00000000..7f31bf66 --- /dev/null +++ b/src/components/Widgets/IframeWidget.vue @@ -0,0 +1,35 @@ +