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 7a6e6f1..314be84 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, such as simple customization through JSON-files and a handy search bar to search the internet more efficiently. @@ -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 a16d350..2cbca25 100644 --- a/src/App.js +++ b/src/App.js @@ -16,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 f8a96c1..1e66f24 100644 --- a/src/components/appList.js +++ b/src/components/appList.js @@ -43,25 +43,69 @@ const App = styled.div` padding: 1rem; `; -const appList = () => ( - - Applications - - {appData.apps.map((app, index) => ( - - - - - - - {app.name} - {app.displayURL} - - - - ))} - - -); +const ErrorMessage = styled.p` + color: red; +`; -export default appList; +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; \ No newline at end of file diff --git a/src/components/bookmarkList.js b/src/components/bookmarkList.js index e08ce6f..7a1f9c2 100644 --- a/src/components/bookmarkList.js +++ b/src/components/bookmarkList.js @@ -34,20 +34,20 @@ const bookmarkList = () => ( Bookmarks - {bookmarkData.groups.map((group, index) => ( - + {bookmarkData.groups.map(({ name, items }) => ( + - {group.name} - {group.items.map(link => ( - - {link.name} + {name} + {group.items.map(({ url, name: linkName }) => ( + + {linkName} ))} - ))} + ))} ); -export default bookmarkList; +export default bookmarkList; \ No newline at end of file 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 24d20cc..1f0184d 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 abf8d55..ac8e00c 100644 --- a/src/components/data/bookmarks.json +++ b/src/components/data/bookmarks.json @@ -95,4 +95,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 a791a1f..65d9377 100644 --- a/src/components/greeter.js +++ b/src/components/greeter.js @@ -25,73 +25,76 @@ const DateText = styled.h3` `; const getGreeting = () => { - // Maybe add some expandability for different greetings? - return "Hello World!"; + // Maybe add some expandability for different greetings? + return 'Hello World!'; }; -const getExtension = day => { - let extension = ""; +const getExtension = (day) => { + let extension = ''; - if ((day > 4 && day <= 20) || (day > 20 && day % 10 >= 4)) { - extension = "th"; - } else if (day % 10 === 1) { - extension = "st"; - } else if (day % 10 === 2) { - extension = "nd"; - } else if (day % 10 === 3) { - extension = "rd"; - } + if ((day > 4 && day <= 20) || (day > 20 && day % 10 >= 4)) { + extension = 'th'; + } else if (day % 10 === 1) { + extension = 'st'; + } else if (day % 10 === 2) { + extension = 'nd'; + } else if (day % 10 === 3) { + 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() - ); + let currentDate = new Date(); + + return ( + weekDayNames[currentDate.getUTCDay()] + + ', ' + + monthNames[currentDate.getUTCMonth()] + + ' ' + + currentDate.getDate() + + getExtension(currentDate.getDate()) + + ' ' + + currentDate.getFullYear() + ); }; -const greeter = () => { - let date = getDateString(); - let greeting = getGreeting(); +const Greeter = () => { + let date = getDateString(); + let greeting = getGreeting(); - return ( - - {date} - {greeting} - - ); + return ( + + {date} + {greeting} + + ); }; -export default greeter; +export default Greeter; \ No newline at end of file diff --git a/src/components/settingsModal.js b/src/components/settingsModal.js index 5922501..410fb35 100644 --- a/src/components/settingsModal.js +++ b/src/components/settingsModal.js @@ -4,10 +4,11 @@ import styled from "styled-components"; import Select from "react-select"; -import searchData from "./data/search.json"; -import themeData from "./data/themes.json"; +import searchData from './data/search.json'; +import themeData from './data/themes.json'; import selectedTheme, { setTheme } from "./themeManager"; +import { Button } from './button'; const ModalButton = styled.button` float: right; @@ -43,16 +44,6 @@ const FormContainer = styled.div` flex-wrap: nowrap; `; -const ApplyButton = 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; -`; - const Headline = styled.h3` font-family: Roboto, sans-serif; font-weight: 900; @@ -150,9 +141,9 @@ const SettingsModal = () => { }} styles={SelectorStyle} /> - setTheme(JSON.stringify(newTheme))}> + diff --git a/src/index.js b/src/index.js index 4146d1d..2b64cb7 100644 --- a/src/index.js +++ b/src/index.js @@ -4,10 +4,10 @@ import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( - - - , - document.getElementById('root') + + + , + document.getElementById('root') ); // If you want your app to work offline and load faster, you can change diff --git a/src/selectedTheme.js b/src/selectedTheme.js new file mode 100644 index 0000000..3655f75 --- /dev/null +++ b/src/selectedTheme.js @@ -0,0 +1,5 @@ +import themeData from './components/data/themes.json'; + +export const selectedTheme = localStorage.getItem('theme') + ? JSON.parse(localStorage.getItem('theme')) + : themeData.themes[0]; diff --git a/src/serviceWorker.js b/src/serviceWorker.js index b04b771..3aa8a6a 100644 --- a/src/serviceWorker.js +++ b/src/serviceWorker.js @@ -11,131 +11,132 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) ); export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } + } } function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); + navigator.serviceWorker + .register(swUrl) + .then((registration) => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch((error) => { + console.error('Error during service worker registration:', error); + }); } function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, - }) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); + .then((response) => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && + contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then((registration) => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); } export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready - .then(registration => { - registration.unregister(); - }) - .catch(error => { - console.error(error.message); - }); - } + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then((registration) => { + registration.unregister(); + }) + .catch((error) => { + console.error(error.message); + }); + } } diff --git a/yarn.lock b/yarn.lock index e2c16ee..caf5887 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2433,6 +2433,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +basic-auth@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" + integrity sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ= + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -3048,6 +3053,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3231,6 +3241,11 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +corser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" + integrity sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c= + cosmiconfig@^5.0.0, cosmiconfig@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -3919,6 +3934,16 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecstatic@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-3.3.2.tgz#6d1dd49814d00594682c652adb66076a69d46c48" + integrity sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog== + dependencies: + he "^1.1.1" + mime "^1.6.0" + minimist "^1.1.0" + url-join "^2.0.5" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -5079,7 +5104,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@^1.2.0: +he@^1.1.1, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -5250,6 +5275,31 @@ http-proxy@^1.17.0: follow-redirects "^1.0.0" requires-port "^1.0.0" +http-proxy@^1.18.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-server@^0.12.3: + version "0.12.3" + resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.12.3.tgz#ba0471d0ecc425886616cb35c4faf279140a0d37" + integrity sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA== + dependencies: + basic-auth "^1.0.3" + colors "^1.4.0" + corser "^2.0.1" + ecstatic "^3.3.2" + http-proxy "^1.18.0" + minimist "^1.2.5" + opener "^1.5.1" + portfinder "^1.0.25" + secure-compare "3.0.1" + union "~0.5.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -6770,6 +6820,11 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + merge-deep@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" @@ -6843,7 +6898,7 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: dependencies: mime-db "1.43.0" -mime@1.6.0: +mime@1.6.0, mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -6895,7 +6950,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -7186,6 +7241,21 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -7360,6 +7430,11 @@ open@^7.0.2: is-docker "^2.0.0" is-wsl "^2.1.1" +opener@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" + integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -7695,6 +7770,11 @@ picomatch@^2.0.4, picomatch@^2.0.7: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8630,6 +8710,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.4.0: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -9386,6 +9471,11 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6 ajv "^6.12.0" ajv-keywords "^3.4.1" +secure-compare@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" + integrity sha1-8aAymzCLIh+uN7mXTz1XjQypmeM= + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -9544,7 +9634,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@1.7.2: +shell-quote@1.7.2, shell-quote@^1.6.1: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== @@ -9918,6 +10008,14 @@ string.prototype.matchall@^4.0.2: regexp.prototype.flags "^1.3.0" side-channel "^1.0.2" +string.prototype.padend@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" + integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -10393,6 +10491,13 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +union@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" + integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA== + dependencies: + qs "^6.4.0" + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -10457,6 +10562,11 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-join@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" + integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg= + url-loader@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b"