From 3a337e6408e547cab013e60a89b73c24dfbe1f47 Mon Sep 17 00:00:00 2001 From: Bever1337 Date: Wed, 20 May 2020 15:28:14 -0400 Subject: [PATCH] Refactor --- .prettierrc | 4 + README.md | 11 +- data/apps.json | 58 ++++++++ package.json | 8 ++ src/App.js | 39 +++--- src/components/appList.js | 94 +++++++++---- src/components/bookmarkList.js | 33 ++--- src/components/button.js | 13 ++ src/components/data/apps.json | 2 +- src/components/data/bookmarks.json | 2 +- src/components/data/search.json | 124 ++++++++--------- src/components/data/themes.json | 2 +- src/components/greeter.js | 75 ++++++---- src/components/searchBar.js | 43 +++--- src/components/settingsModal.js | 111 ++++++++------- src/components/themeManager.js | 22 +-- src/index.js | 8 +- src/selectedTheme.js | 5 + src/serviceWorker.js | 217 +++++++++++++++-------------- yarn.lock | 118 +++++++++++++++- 20 files changed, 631 insertions(+), 358 deletions(-) create mode 100644 .prettierrc create mode 100644 data/apps.json create mode 100644 src/components/button.js create mode 100644 src/selectedTheme.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..96c36f5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "tabWidth": 4 +} diff --git a/README.md b/README.md index d4f9fd9..8e41dee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dashboard -![screenshot](screenshot.png "screenshot") +![screenshot](screenshot.png 'screenshot') Dashboard is just that - a dashboard. It's inspired by [SUI](https://github.com/jeroenpardon/sui) and has all the same features as SUI. @@ -8,9 +8,9 @@ Dashboard is just that - a dashboard. It's inspired by [SUI](https://github.com/ So what makes this thing better than SUI? -- "Display URL" functionality, in case the URL you want to show is different than the URL you want to be redirected to -- Theming through JSON -- Search providers customizable through JSON (SUI has them both in a JSON and hardcoded) +- "Display URL" functionality, in case the URL you want to show is different than the URL you want to be redirected to +- Theming through JSON +- Search providers customizable through JSON (SUI has them both in a JSON and hardcoded) ## Installation @@ -26,7 +26,8 @@ To get Dashboard to run, just clone the repository, download the dependencies us git clone https://github.com/phntxx/dashboard.git cd dashboard yarn -yarn start +yarn build +yarn serve:production ``` alternatively, if you want to work using static files (requires a rebuild for diff --git a/data/apps.json b/data/apps.json new file mode 100644 index 0000000..d0c3fdc --- /dev/null +++ b/data/apps.json @@ -0,0 +1,58 @@ +{ + "apps": [ + { + "name": "my pi hole", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "vpn_lock" + }, + { + "name": "Plex", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "tv" + }, + { + "name": "NextCloud", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "filter_drama" + }, + { + "name": "Ghost", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "rss_feed" + }, + { + "name": "Minecraft", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "games" + }, + { + "name": "pfSense", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "security" + }, + { + "name": "ESXi", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "dns" + }, + { + "name": "Tautulli", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "bar_chart" + }, + { + "name": "Grafana", + "displayURL": "example.com", + "URL": "https://example.com", + "icon": "show_chart" + } + ] +} diff --git a/package.json b/package.json index 02ad091..50121ff 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "http-server": "^0.12.3", "material-icons-react": "^1.0.4", "react": "^16.13.1", "react-dom": "^16.13.1", @@ -14,8 +15,12 @@ "styled-components": "^5.1.0" }, "scripts": { + "serveAppData": "http-server ./data -c-1", + "serveProductionApp": "http-server ./build --proxy http://localhost:8080 --port 3000", "start": "react-scripts start", "build": "react-scripts build", + "serve:dev": "npm-run-all --parallel serveAppData start", + "serve:production": "npm-run-all --parallel serveAppData serveProductionApp", "test": "react-scripts test", "eject": "react-scripts eject" }, @@ -33,5 +38,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "npm-run-all": "^4.1.5" } } diff --git a/src/App.js b/src/App.js index 1338a35..27074a3 100644 --- a/src/App.js +++ b/src/App.js @@ -1,14 +1,13 @@ import React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; -import SearchBar from './components/searchBar' -import Greeter from './components/greeter' -import AppList from './components/appList' -import BookmarkList from './components/bookmarkList' -import SettingsModal from './components/settingsModal' +import SearchBar from './components/searchBar'; +import Greeter from './components/greeter'; +import AppList from './components/appList'; +import BookmarkList from './components/bookmarkList'; +import SettingsModal from './components/settingsModal'; -import themeData from './components/data/themes.json'; -const selectedTheme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : themeData.themes[0]; +import { selectedTheme } from './selectedTheme'; const GlobalStyle = createGlobalStyle` body { @@ -17,22 +16,22 @@ const GlobalStyle = createGlobalStyle` `; const AppContainer = styled.div` - max-width: 80%; - margin: auto; - padding: 10px; + max-width: 80%; + margin: auto; + padding: 10px; `; const App = () => ( - <> - - - - - - - - - + <> + + + + + + + + + ); export default App; diff --git a/src/components/appList.js b/src/components/appList.js index 694dce6..7a6e8d7 100644 --- a/src/components/appList.js +++ b/src/components/appList.js @@ -1,11 +1,9 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import MaterialIcon from 'material-icons-react'; import styled from 'styled-components'; -import appData from './data/apps.json'; - -import themeData from './data/themes.json' -const selectedTheme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : themeData.themes[0]; +import { Button } from './button'; +import { selectedTheme } from '../selectedTheme'; const AppListContainer = styled.div` padding: 2rem 0 2rem 0; @@ -69,25 +67,69 @@ const Description = styled.p` color: ${selectedTheme.accentColor}; `; -const appList = () => ( - - Applications - - { - appData.apps.map((app) => ( - - - - - - {app.name} - {app.displayURL} - - - )) - } - - -); +const ErrorMessage = styled.p` + color: red; +`; -export default appList; \ No newline at end of file +function handleResponse(response) { + if (response.ok) { + return response.json(); + } + throw new Error('Failed to load app data.'); +} + +function useAppData() { + const [appData, setAppData] = useState({ apps: [], error: false }); + const fetchAppData = useCallback(() => { + (process.env.NODE_ENV === 'production' + ? fetch('/apps.json').then(handleResponse) + : import('./data/apps.json') + ) + .then((jsonResponse) => { + setAppData({ ...jsonResponse, error: false }); + }) + .catch((error) => { + setAppData({ apps: [], error: error.message }); + }); + }, []); + useEffect(() => { + fetchAppData(); + }, [fetchAppData]); + return { appData, fetchAppData }; +} + +const AppList = () => { + const { + appData: { apps, error }, + fetchAppData, + } = useAppData(); + return ( + + + Applications + + + {error && {error}} + {apps.map((app, idx) => { + const { name } = app; + return ( + + + + + + {app.name} + {app.displayURL} + + + ); + })} + + + ); +}; + +export default AppList; diff --git a/src/components/bookmarkList.js b/src/components/bookmarkList.js index 47aaa23..2c50ada 100644 --- a/src/components/bookmarkList.js +++ b/src/components/bookmarkList.js @@ -3,8 +3,7 @@ import styled from 'styled-components'; import bookmarkData from './data/bookmarks.json'; -import themeData from './data/themes.json' -const selectedTheme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : themeData.themes[0]; +import { selectedTheme } from '../selectedTheme'; const BookmarksText = styled.h3` font-family: Roboto, sans-serif; @@ -54,26 +53,24 @@ const Bookmark = styled.a` font-size: 14px; `; -const bookmarkList = () => ( +const BookmarkList = () => ( Bookmarks - - { - bookmarkData.groups.map((group) => ( - - {group.name} - { - group.items.map((link) => ( - {link.name} - )) - } - - )) - } - + {bookmarkData.groups.map(({ name, items }) => { + return ( + + {name} + {items.map(({ url, name: linkName }) => ( + + {linkName} + + ))} + + ); + })} ); -export default bookmarkList; \ No newline at end of file +export default BookmarkList; diff --git a/src/components/button.js b/src/components/button.js new file mode 100644 index 0000000..7a01bd5 --- /dev/null +++ b/src/components/button.js @@ -0,0 +1,13 @@ +import styled from 'styled-components'; +import { selectedTheme } from '../selectedTheme'; + +export const Button = styled.button` + font-family: Roboto, sans-serif; + text-transform: uppercase; + font-weight: 400; + border: 1px solid ${selectedTheme.mainColor}; + color: ${selectedTheme.mainColor}; + background: none; + margin-left: 1rem; + min-height: 3em; +`; diff --git a/src/components/data/apps.json b/src/components/data/apps.json index 495f0e4..cce34d9 100644 --- a/src/components/data/apps.json +++ b/src/components/data/apps.json @@ -55,4 +55,4 @@ "icon": "show_chart" } ] -} \ No newline at end of file +} diff --git a/src/components/data/bookmarks.json b/src/components/data/bookmarks.json index b916898..90c90cc 100644 --- a/src/components/data/bookmarks.json +++ b/src/components/data/bookmarks.json @@ -137,4 +137,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/components/data/search.json b/src/components/data/search.json index 243b18e..61a82eb 100644 --- a/src/components/data/search.json +++ b/src/components/data/search.json @@ -1,64 +1,64 @@ { - "providers":[ - { - "name":"Allmusic", - "url":"https://www.allmusic.com/search/all/", - "prefix":"/a" - }, - { - "name":"Discogs", - "url":"https://www.discogs.com/search/?q=", - "prefix":"/di" - }, - { - "name":"Duck Duck Go", - "url":"https://duckduckgo.com/?q=", - "prefix":"/d" - }, - { - "name":"iMDB", - "url":"https://www.imdb.com/find?q=", - "prefix":"/i" - }, - { - "name":"TheMovieDB", - "url":"https://www.themoviedb.org/search?query=", - "prefix":"/m" - }, - { - "name":"Reddit", - "url":"https://www.reddit.com/search?q=", - "prefix":"/r" - }, - { - "name":"Qwant", - "url":"https://www.qwant.com/?q=", - "prefix":"/q" - }, - { - "name":"Soundcloud", - "url":"https://soundcloud.com/search?q=", - "prefix":"/so" - }, - { - "name":"Spotify", - "url":"https://open.spotify.com/search/results/", - "prefix":"/s" - }, - { - "name":"TheTVDB", - "url":"https://www.thetvdb.com/search?q=", - "prefix":"/tv" - }, - { - "name":"Trakt", - "url":"https://trakt.tv/search?query=", - "prefix":"/t" - }, - { - "name": "YouTube", - "url": "https://youtube.com/results?search_query=", - "prefix":"/yt" - } + "providers": [ + { + "name": "Allmusic", + "url": "https://www.allmusic.com/search/all/", + "prefix": "/a" + }, + { + "name": "Discogs", + "url": "https://www.discogs.com/search/?q=", + "prefix": "/di" + }, + { + "name": "Duck Duck Go", + "url": "https://duckduckgo.com/?q=", + "prefix": "/d" + }, + { + "name": "iMDB", + "url": "https://www.imdb.com/find?q=", + "prefix": "/i" + }, + { + "name": "TheMovieDB", + "url": "https://www.themoviedb.org/search?query=", + "prefix": "/m" + }, + { + "name": "Reddit", + "url": "https://www.reddit.com/search?q=", + "prefix": "/r" + }, + { + "name": "Qwant", + "url": "https://www.qwant.com/?q=", + "prefix": "/q" + }, + { + "name": "Soundcloud", + "url": "https://soundcloud.com/search?q=", + "prefix": "/so" + }, + { + "name": "Spotify", + "url": "https://open.spotify.com/search/results/", + "prefix": "/s" + }, + { + "name": "TheTVDB", + "url": "https://www.thetvdb.com/search?q=", + "prefix": "/tv" + }, + { + "name": "Trakt", + "url": "https://trakt.tv/search?query=", + "prefix": "/t" + }, + { + "name": "YouTube", + "url": "https://youtube.com/results?search_query=", + "prefix": "/yt" + } ] - } \ No newline at end of file +} diff --git a/src/components/data/themes.json b/src/components/data/themes.json index c6462a5..10a5543 100644 --- a/src/components/data/themes.json +++ b/src/components/data/themes.json @@ -22,4 +22,4 @@ "backgroundColor": "#ffffff" } ] -} \ No newline at end of file +} diff --git a/src/components/greeter.js b/src/components/greeter.js index 054dd41..6187cdd 100644 --- a/src/components/greeter.js +++ b/src/components/greeter.js @@ -1,8 +1,7 @@ import React from 'react'; import styled from 'styled-components'; -import themeData from './data/themes.json' -const selectedTheme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : themeData.themes[0]; +import { selectedTheme } from '../selectedTheme'; const GreeterContainer = styled.div` padding: 2rem 0 2rem 0; @@ -27,47 +26,75 @@ const DateText = styled.h3` const getGreeting = () => { // Maybe add some expandability for different greetings? - return "Hello World!" -} + return 'Hello World!'; +}; const getExtension = (day) => { - let extension = "" + let extension = ''; if ((day > 4 && day <= 20) || (day > 20 && day % 10 >= 4)) { - extension = "th" + extension = 'th'; } else if (day % 10 === 1) { - extension = "st" + extension = 'st'; } else if (day % 10 === 2) { - extension = "nd" + extension = 'nd'; } else if (day % 10 === 3) { - extension = "rd" + extension = 'rd'; } - return extension -} + return extension; +}; + +const monthNames = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +]; + +const weekDayNames = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +]; const getDateString = () => { let currentDate = new Date(); - const monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; - - const weekDayNames = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; - - return weekDayNames[currentDate.getUTCDay()] + ", " + monthNames[currentDate.getUTCMonth()] + " " + currentDate.getDate() + getExtension(currentDate.getDate()) + " " + currentDate.getFullYear(); -} - -const greeter = () => { + return ( + weekDayNames[currentDate.getUTCDay()] + + ', ' + + monthNames[currentDate.getUTCMonth()] + + ' ' + + currentDate.getDate() + + getExtension(currentDate.getDate()) + + ' ' + + currentDate.getFullYear() + ); +}; +const Greeter = () => { let date = getDateString(); let greeting = getGreeting(); return ( - { date } - { greeting } + {date} + {greeting} ); -} +}; - -export default greeter; \ No newline at end of file +export default Greeter; diff --git a/src/components/searchBar.js b/src/components/searchBar.js index e2558d8..a18589e 100644 --- a/src/components/searchBar.js +++ b/src/components/searchBar.js @@ -1,10 +1,9 @@ -import React, {useState} from 'react'; +import React, { useState } from 'react'; import styled from 'styled-components'; import searchData from './data/search.json'; -import themeData from './data/themes.json'; -const selectedTheme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : themeData.themes[0]; +import { selectedTheme } from '../selectedTheme'; const SearchInput = styled.input` width: 100%; @@ -17,53 +16,53 @@ const SearchInput = styled.input` `; const handleQueryWithProvider = (query) => { - - let queryArray = query.split(" "); + let queryArray = query.split(' '); let prefix = queryArray[0]; queryArray.shift(); - let searchQuery = queryArray.join(" "); - + let searchQuery = queryArray.join(' '); + var foundProvider = false; searchData.providers.forEach((provider) => { if (provider.prefix === prefix) { foundProvider = true; - window.location = provider.url + searchQuery + window.location = provider.url + searchQuery; } - }) + }); if (!foundProvider) { - window.location = "https://google.com/search?q=" + query; + window.location = 'https://google.com/search?q=' + query; } -} +}; const SearchBar = () => { - let [input, setInput] = useState(); const handleSearchQuery = (e) => { - var query = input; - console.log(query) + console.log(query); - if (query.split(" ")[0].includes("/")) { - handleQueryWithProvider(query) + if (query.split(' ')[0].includes('/')) { + handleQueryWithProvider(query); } else { - window.location = "https://google.com/search?q=" + query; + window.location = 'https://google.com/search?q=' + query; } e.preventDefault(); - } + }; return (
handleSearchQuery(e)}> - setInput(e.target.value)}> + setInput(e.target.value)} + >