From fad1e5ff50eb27b3ba68254dea594a4139932d42 Mon Sep 17 00:00:00 2001
From: Alicia Sykes <sykes.alicia@gmail.com>
Date: Sat, 18 Dec 2021 14:57:24 +0000
Subject: [PATCH] :sparkles: Adds NASA APOD widget

---
 docs/widgets.md                       |  42 +++++++++-
 src/components/Widgets/Apod.vue       | 112 ++++++++++++++++++++++++++
 src/components/Widgets/WidgetBase.vue |  11 ++-
 src/utils/defaults.js                 |   1 +
 4 files changed, 163 insertions(+), 3 deletions(-)
 create mode 100644 src/components/Widgets/Apod.vue

diff --git a/docs/widgets.md b/docs/widgets.md
index c6eef10b..4b6e3e24 100644
--- a/docs/widgets.md
+++ b/docs/widgets.md
@@ -18,6 +18,7 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
   - [Stock Price History](#stock-price-history)
   - [Joke of the Day](#joke)
   - [Flight Data](#flight-data)
+  - [NASA APOD](#astronomy-picture-of-the-day)
   - [GitHub Trending](#github-trending)
   - [GitHub Profile Stats](#github-profile-stats)
   - [Public IP Address](#public-ip)
@@ -29,7 +30,11 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
 - [Dynamic Widgets](#dynamic-widgets)
   - [Iframe Widget](#iframe-widget)
   - [HTML Embed Widget](#html-embedded-widget)
-- [Build your own Widget](#build-your-own-widget)
+  - [API Response](#api-response)
+  - [Data Feed](#data-feed)
+- [Custom Widgets](#custom-widgets)
+  - [Build your own Widget](#build-your-own-widget)
+  - [Widget Styling](#widget-styling)
 
 ## General Widgets
 
@@ -433,6 +438,24 @@ Displays airport departure and arrival flights, using data from [AeroDataBox](ht
 
 ---
 
+### Astronomy Picture of the Day
+
+Show the NASA Astronomy Pictore of the Day. Data is fetched from [APOD](https://apod.nasa.gov/apod/) using [PawelPleskaczynski/apod_api](https://github.com/PawelPleskaczynski/apod_api).
+
+<p align="center"><img width="400" src="https://i.ibb.co/ZMkgLFK/apod.png" /></p>
+
+##### Options
+
+_No config options._
+
+##### Example 
+
+```yaml
+- type: apod
+```
+
+---
+
 ### GitHub Trending
 
 Displays currently trending projects on GitHub. Optionally specify a language and time-frame. Data is fetched from [Lissy93/gh-trending-no-cors](https://github.com/Lissy93/gh-trending-no-cors) using the GitHub API.
@@ -668,6 +691,21 @@ Or
 
 ---
 
-## Build your own Widget
+### API Response
+
+---
+
+### Data Feed
+
+---
+
+## Custom Widgets
+
+### Build your own Widget
 
 For a full tutorial on creating your own widget, you can follow [this guide](https://github.com/Lissy93/dashy/blob/master/docs/development-guides.md#building-a-widget), or take a look at [here](https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e) for a code example. 
+
+---
+
+### Widget Styling
+
diff --git a/src/components/Widgets/Apod.vue b/src/components/Widgets/Apod.vue
new file mode 100644
index 00000000..6bd9e0c6
--- /dev/null
+++ b/src/components/Widgets/Apod.vue
@@ -0,0 +1,112 @@
+<template>
+<div class="apod-wrapper" v-if="image">
+  <a :href="link" class="title" target="__blank" title="View Article">
+    {{ title }}
+  </a>
+  <a :href="hdImage" title="View HD Image" class="picture" target="__blank">
+    <img :src="image" />
+  </a>
+  <p class="copyright">{{ copyright }}</p>
+  <p class="description">{{ truncatedDescription }}</p>
+  <p @click="toggleShowFull" class="expend-details-btn">
+    {{ showFullDesc ? 'Show Less' : ' Expand Details' }}
+  </p>
+</div>
+</template>
+
+<script>
+import axios from 'axios';
+import WidgetMixin from '@/mixins/WidgetMixin';
+import { widgetApiEndpoints } from '@/utils/defaults';
+
+export default {
+  mixins: [WidgetMixin],
+  data() {
+    return {
+      title: null,
+      image: null,
+      hdImage: null,
+      link: null,
+      description: null,
+      copyright: null,
+      showFullDesc: false,
+    };
+  },
+  computed: {
+    truncatedDescription() {
+      return this.showFullDesc ? this.description : `${this.description.substring(0, 100)}...`;
+    },
+  },
+  methods: {
+    fetchData() {
+      axios.get(widgetApiEndpoints.astronomyPictureOfTheDay)
+        .then((response) => {
+          this.processData(response.data);
+        })
+        .catch((dataFetchError) => {
+          this.error('Unable to fetch data', dataFetchError);
+        })
+        .finally(() => {
+          this.finishLoading();
+        });
+    },
+    processData(data) {
+      this.title = data.title;
+      this.image = data.url;
+      this.hdImage = data.hdurl;
+      this.link = data.apod_site;
+      this.description = data.description;
+      this.copyright = data.copyright;
+    },
+    toggleShowFull() {
+      this.showFullDesc = !this.showFullDesc;
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.apod-wrapper {
+  a.title {
+    font-size: 1.5rem;
+    margin: 0.5rem 0;
+    color: var(--widget-text-color);
+    text-decoration: none;
+    &:hover { text-decoration: underline; }
+  }
+  a.picture img {
+    width: 100%;
+    margin: 0.5rem auto;
+    border-radius: var(--curve-factor);
+  }
+  p.copyright {
+    font-size: 0.8rem;
+    margin: 0.2rem 0;
+    opacity: var(--dimming-factor);
+    color: var(--widget-text-color);
+  }
+  p.description {
+    color: var(--widget-text-color);
+    font-size: 1rem;
+    margin: 0.5rem 0;
+  }
+  p.expend-details-btn {
+    cursor: pointer;
+    float: right;
+    margin: 0;
+    padding: 0.1rem 0.25rem;
+    border: 1px solid transparent;
+    color: var(--widget-text-color);
+    opacity: var(--dimming-factor);
+    border-radius: var(--curve-factor);
+    &:hover {
+      border: 1px solid var(--widget-text-color);
+    }
+    &:focus, &:active {
+      background: var(--widget-text-color);
+      color: var(--widget-background-color);
+    }
+  }
+}
+
+</style>
diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue
index e1fbb019..07e5f2a7 100644
--- a/src/components/Widgets/WidgetBase.vue
+++ b/src/components/Widgets/WidgetBase.vue
@@ -18,8 +18,15 @@
     </div>
     <!-- Widget -->
     <div v-else class="widget-wrap">
+      <Apod
+        v-if="widgetType === 'apod'"
+        :options="widgetOptions"
+        @loading="setLoaderState"
+        @error="handleError"
+        :ref="widgetRef"
+      />
       <Clock
-        v-if="widgetType === 'clock'"
+        v-else-if="widgetType === 'clock'"
         :options="widgetOptions"
         @loading="setLoaderState"
         @error="handleError"
@@ -194,6 +201,7 @@ import OpenIcon from '@/assets/interface-icons/open-new-tab.svg';
 import LoadingAnimation from '@/assets/interface-icons/loader.svg';
 
 // Import available widgets (add new widgets alphabetically)
+import Apod from '@/components/Widgets/Apod.vue';
 import Clock from '@/components/Widgets/Clock.vue';
 import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue';
 import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue';
@@ -227,6 +235,7 @@ export default {
     OpenIcon,
     LoadingAnimation,
     // Register widget components
+    Apod,
     Clock,
     CodeStats,
     CryptoPriceChart,
diff --git a/src/utils/defaults.js b/src/utils/defaults.js
index 933ae217..9302887f 100644
--- a/src/utils/defaults.js
+++ b/src/utils/defaults.js
@@ -222,6 +222,7 @@ module.exports = {
     publicIp: 'http://ip-api.com/json',
     readMeStats: 'https://github-readme-stats.vercel.app/api',
     githubTrending: 'https://gh-trending-repos.herokuapp.com/',
+    astronomyPictureOfTheDay: 'https://apodapi.herokuapp.com/api',
   },
   /* URLs for web search engines */
   searchEngineUrls: {