diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 29359144..82b4e31c 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -32,6 +32,7 @@ jobs: - run: npm run build:${{ matrix.build_target }} env: UNSPLASH_API_KEY: ${{ secrets.UNSPLASH_API_KEY }} + NASA_API_KEY: ${{ secrets.NASA_API_KEY }} - uses: actions/upload-artifact@v3 with: name: tab-nine-${{ matrix.build_target }} diff --git a/src/global.d.ts b/src/global.d.ts index 49733d18..35e490eb 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -4,6 +4,7 @@ declare global { const BUILD_TARGET: "chromium" | "firefox" | "web"; const DEV: boolean; const UNSPLASH_API_KEY: string; + const NASA_API_KEY: string; const VERSION: string; const browser: Browser; diff --git a/src/plugins/backgrounds/apod/Apod.sass b/src/plugins/backgrounds/apod/Apod.sass new file mode 100644 index 00000000..fe9e1e8f --- /dev/null +++ b/src/plugins/backgrounds/apod/Apod.sass @@ -0,0 +1,16 @@ +.Apod + .picture + background-position: 50% 50% + background-size: cover + transition: opacity 0.25s ease-out + + .title + display: flex + flex-direction: column + position: absolute + bottom: 1rem + left: 1rem + right: 1rem + + p + margin: 4px 0 0 diff --git a/src/plugins/backgrounds/apod/Apod.tsx b/src/plugins/backgrounds/apod/Apod.tsx new file mode 100644 index 00000000..f6052914 --- /dev/null +++ b/src/plugins/backgrounds/apod/Apod.tsx @@ -0,0 +1,50 @@ +import React from "react"; + +import Backdrop from "../../../views/shared/Backdrop"; + +import { defaultData, Props } from "./types"; +import { getPicture } from "./api"; +import ApodTitle from "./ApodTitle"; +import "./Apod.sass"; + +const Unsplash: React.FC = ({ + cache, + data = defaultData, + loader, + setCache, +}) => { + const [picture, setPicture] = React.useState(cache); + const mounted = React.useRef(false); + + React.useEffect(() => { + getPicture(data, loader).then(setCache); + if (mounted.current || !picture) getPicture(data, loader).then(setPicture); + mounted.current = true; + }, [data.customDate, data.date]); + + return ( +
+ + + {picture && data.showTitle && ( + + )} +
+ ); +}; + +export default Unsplash; diff --git a/src/plugins/backgrounds/apod/ApodSettings.sass b/src/plugins/backgrounds/apod/ApodSettings.sass new file mode 100644 index 00000000..f5590e23 --- /dev/null +++ b/src/plugins/backgrounds/apod/ApodSettings.sass @@ -0,0 +1,3 @@ +.ApodSettings + .date + margin-left: 4px diff --git a/src/plugins/backgrounds/apod/ApodSettings.tsx b/src/plugins/backgrounds/apod/ApodSettings.tsx new file mode 100644 index 00000000..ed4ad4ac --- /dev/null +++ b/src/plugins/backgrounds/apod/ApodSettings.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import "./ApodSettings.sass"; +import { DebounceInput } from "../../shared"; +import { ApodDate, defaultData, Props } from "./types"; +import { format } from "date-fns"; + +const maxDate = format(new Date(), "yyyy-MM-dd"); + +const UnsplashSettings: React.FC = ({ data = defaultData, setData }) => ( +
+ + + {data.date === "custom" && ( + + )} + + +
+); + +export default UnsplashSettings; diff --git a/src/plugins/backgrounds/apod/ApodTitle.tsx b/src/plugins/backgrounds/apod/ApodTitle.tsx new file mode 100644 index 00000000..096db043 --- /dev/null +++ b/src/plugins/backgrounds/apod/ApodTitle.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Image } from "./types"; + +type Props = Pick; + +const Credit: React.FC = ({ title, copyright }) => ( +
+

{title}

+ {copyright &&

© {copyright}

} +
+); + +export default Credit; diff --git a/src/plugins/backgrounds/apod/api.ts b/src/plugins/backgrounds/apod/api.ts new file mode 100644 index 00000000..8cf3d559 --- /dev/null +++ b/src/plugins/backgrounds/apod/api.ts @@ -0,0 +1,32 @@ +import { API } from "../../types"; +import { Image, Data } from "./types"; +import { format } from "date-fns"; + +type Config = Data; + +const formatDateForApi = (date: string): string => { + return format(new Date(date), "yyyy-MM-dd"); +}; + +export async function getPicture( + data: Config, + loader: API["loader"], +): Promise { + const url = "https://api.nasa.gov/planetary/apod"; + const params = new URLSearchParams(); + + params.set("api_key", NASA_API_KEY); + params.set("thumbs", "true"); + + if (data.date === "custom" && data.customDate) { + params.set("date", formatDateForApi(data.customDate)); + } + + loader.push(); + const res = await fetch(`${url}?${params}`); + const json = await res.json(); + + loader.pop(); + + return json; +} diff --git a/src/plugins/backgrounds/apod/index.ts b/src/plugins/backgrounds/apod/index.ts new file mode 100644 index 00000000..9e131edf --- /dev/null +++ b/src/plugins/backgrounds/apod/index.ts @@ -0,0 +1,14 @@ +import { Config } from "../../types"; +import Apod from "./Apod"; +import ApodSettings from "./ApodSettings"; + +const config: Config = { + key: "background/apod", + name: "Astronomy Picture of the Day", + description: "NASA's sky pictures", + dashboardComponent: Apod, + settingsComponent: ApodSettings, + supportsBackdrop: true, +}; + +export default config; diff --git a/src/plugins/backgrounds/apod/types.ts b/src/plugins/backgrounds/apod/types.ts new file mode 100644 index 00000000..f71ee345 --- /dev/null +++ b/src/plugins/backgrounds/apod/types.ts @@ -0,0 +1,30 @@ +import { API } from "../../types"; + +export type ApodDate = "today" | "custom"; + +export interface Data { + date: ApodDate; + customDate?: string; + showTitle: boolean; +} + +export interface Image { + url: string; + hdurl: string; + + title: string; + date: Date; + media_type: string; + explanation: string; + thumbnail_url: string; + copyright: string; +} + +type Cache = Image; + +export type Props = API; + +export const defaultData: Data = { + date: "today", + showTitle: true, +}; diff --git a/src/plugins/backgrounds/index.ts b/src/plugins/backgrounds/index.ts index 1cc826bf..997d5b5c 100644 --- a/src/plugins/backgrounds/index.ts +++ b/src/plugins/backgrounds/index.ts @@ -2,7 +2,8 @@ import colour from "./colour"; import gradient from "./gradient"; import image from "./image"; import unsplash from "./unsplash"; +import apod from "./apod"; -export const backgroundConfigs = [colour, gradient, image, unsplash]; +export const backgroundConfigs = [colour, gradient, image, unsplash, apod]; backgroundConfigs.sort((a, b) => a.name.localeCompare(b.name)); diff --git a/webpack.config.js b/webpack.config.js index dd4e44fa..fc83608e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -82,6 +82,7 @@ const config = { DEV: JSON.stringify(!isProduction), VERSION: JSON.stringify(version), UNSPLASH_API_KEY: JSON.stringify(process.env.UNSPLASH_API_KEY), + NASA_API_KEY: JSON.stringify(process.env.NASA_API_KEY), }), ], devtool: isWeb || !isProduction ? "source-map" : false,