A lot of improvements
- Removed "refresh" buttons, please just refresh the entire site (less cluttered appearance) - Refined README on installation - Added more themes - Improved Dockerfile, now everything runs with production settings
This commit is contained in:
parent
19bbc337bf
commit
22ee2ca08d
12 changed files with 242 additions and 91 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,7 +8,7 @@
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# production
|
# building
|
||||||
/build
|
/build
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
|
16
Dockerfile
16
Dockerfile
|
@ -1,6 +1,18 @@
|
||||||
|
# Get current image of node.js
|
||||||
FROM node:current-slim
|
FROM node:current-slim
|
||||||
|
|
||||||
|
# Set /app as directory where the app should be at
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy all of the relevant files over to the container
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Download dependencies, build container
|
||||||
RUN yarn
|
RUN yarn
|
||||||
EXPOSE 3000
|
RUN [ "yarn", "build" ]
|
||||||
CMD [ "yarn", "start" ]
|
|
||||||
|
# Expose the two relevant ports
|
||||||
|
EXPOSE 3000 8080
|
||||||
|
|
||||||
|
# Serve the app
|
||||||
|
CMD [ "yarn", "serve:production" ]
|
40
README.md
40
README.md
|
@ -18,41 +18,27 @@ Getting Dashboard to run is fairly simple and can be accomplished with two techn
|
||||||
|
|
||||||
1. Locally
|
1. Locally
|
||||||
|
|
||||||
**Prerequisites: node, npm, yarn**
|
|
||||||
|
|
||||||
To get Dashboard to run, just clone the repository, download the dependencies using yarn, then start using `yarn start`.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/phntxx/dashboard.git
|
$ git clone https://github.com/phntxx/dashboard.git
|
||||||
cd dashboard
|
$ cd dashboard/
|
||||||
yarn
|
$ yarn
|
||||||
yarn build
|
$ yarn build
|
||||||
yarn serve:production
|
$ yarn serve:production
|
||||||
```
|
```
|
||||||
|
|
||||||
alternatively, if you want to work using static files (requires a rebuild for
|
|
||||||
every change in the JSON-files), just replace `yarn start` with `yarn build`.
|
|
||||||
Then you can copy the files inside the `build` directory onto the webroot of
|
|
||||||
your webserver of choice.
|
|
||||||
|
|
||||||
2. Using Docker
|
2. Using Docker
|
||||||
|
|
||||||
Using Docker requires building the container manually. Fortunately, this can be accomplished fairly easily:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/phntxx/dashboard.git
|
$ git clone https://github.com/phntxx/dashboard.git
|
||||||
cd dashboard
|
$ cd dashboard/
|
||||||
docker build -t dashboard:1.0
|
$ docker build -t dashboard:1.0 .
|
||||||
|
$ docker run -d \
|
||||||
docker run -d \
|
-v $(pwd)/data:/app/data
|
||||||
-t \
|
-p 3000:3000 \
|
||||||
-p 3000:3000 \
|
--name dashboard \
|
||||||
-v ./src/components/data:/app/src/components/data \
|
dashboard:1.0
|
||||||
dashboard:1.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**NOTE: The `-t` flag is very important, as the Dockerfile requires standard TTY.**
|
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
Dashboard is designed to be customizable. Everything is handled using four .json-files, which can be found at /src/components/data
|
Dashboard is designed to be customizable. Everything is handled using four .json-files, which can be found at /src/components/data
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"apps": [
|
"apps": [
|
||||||
{
|
{
|
||||||
"name": "my pi hole",
|
"name": "Pihole",
|
||||||
"displayURL": "example.com",
|
"displayURL": "example.com",
|
||||||
"URL": "https://example.com",
|
"URL": "https://example.com",
|
||||||
"icon": "vpn_lock"
|
"icon": "vpn_lock"
|
||||||
|
|
|
@ -20,6 +20,90 @@
|
||||||
"mainColor": "",
|
"mainColor": "",
|
||||||
"accentColor": "",
|
"accentColor": "",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Blackboard",
|
||||||
|
"value": 3,
|
||||||
|
"mainColor": "#fffdea",
|
||||||
|
"accentColor": "5c5c5c",
|
||||||
|
"backgroundColor": "#1a1a1a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Gazette",
|
||||||
|
"value": 4,
|
||||||
|
"mainColor": "#000000",
|
||||||
|
"accentColor": "#5c5c5c",
|
||||||
|
"backgroundColor": "#F2F7FF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Espresso",
|
||||||
|
"value": 5,
|
||||||
|
"mainColor": "#d1b59a",
|
||||||
|
"accentColor": "#4e4e4e",
|
||||||
|
"backgroundColor": "#21211f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Cab",
|
||||||
|
"value": 6,
|
||||||
|
"mainColor": "#1f1f1f",
|
||||||
|
"accentColor": "#424242",
|
||||||
|
"backgroundColor": "#f6d305"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Cloud",
|
||||||
|
"value": 7,
|
||||||
|
"mainColor": "#35342f",
|
||||||
|
"accentColor": "#37bbe4",
|
||||||
|
"backgroundColor": "#f1f2f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Lime",
|
||||||
|
"value": 8,
|
||||||
|
"mainColor": "#aabbc3",
|
||||||
|
"accentColor": "#aeea00",
|
||||||
|
"backgroundColor": "#263238"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "White",
|
||||||
|
"value": 9,
|
||||||
|
"mainColor": "#222222",
|
||||||
|
"accentColor": "#dddddd",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Tron",
|
||||||
|
"value": 10,
|
||||||
|
"mainColor": "#effbff",
|
||||||
|
"accentColor": "#6ee2ff",
|
||||||
|
"backgroundColor": "#242b33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Blues",
|
||||||
|
"value": 11,
|
||||||
|
"mainColor": "#eff1fc",
|
||||||
|
"accentColor": "#6677eb",
|
||||||
|
"backgroundColor": "#2b2c56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Passion",
|
||||||
|
"value": 12,
|
||||||
|
"mainColor": "#12005e",
|
||||||
|
"accentColor": "#8e24aa",
|
||||||
|
"backgroundColor": "#f5f5f5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Chalk",
|
||||||
|
"value": 13,
|
||||||
|
"mainColor": "#aabbc3",
|
||||||
|
"accentColor": "#ff869a",
|
||||||
|
"backgroundColor": "#263238"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Paper",
|
||||||
|
"value": 14,
|
||||||
|
"mainColor": "#4c432e",
|
||||||
|
"accentColor": "#aa9a73",
|
||||||
|
"backgroundColor": "#f8f6f1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ import styled from 'styled-components';
|
||||||
import selectedTheme from './themeManager';
|
import selectedTheme from './themeManager';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
handleResponse,
|
||||||
Headline,
|
Headline,
|
||||||
ListContainer,
|
ListContainer,
|
||||||
ItemList,
|
ItemList,
|
||||||
Item,
|
Item,
|
||||||
RefreshButton,
|
|
||||||
ErrorMessage
|
ErrorMessage
|
||||||
} from './elements';
|
} from './elements';
|
||||||
|
|
||||||
|
@ -48,13 +48,6 @@ const App = styled.div`
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const handleResponse = response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
throw new Error('Failed to load app data.');
|
|
||||||
};
|
|
||||||
|
|
||||||
const useAppData = () => {
|
const useAppData = () => {
|
||||||
const [appData, setAppData] = useState({ apps: [], error: false });
|
const [appData, setAppData] = useState({ apps: [], error: false });
|
||||||
const fetchAppData = useCallback(() => {
|
const fetchAppData = useCallback(() => {
|
||||||
|
@ -84,7 +77,6 @@ const AppList = () => {
|
||||||
return (
|
return (
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
<Headline>Applications</Headline>
|
<Headline>Applications</Headline>
|
||||||
<RefreshButton onClick={fetchAppData}>refresh</RefreshButton>
|
|
||||||
<ItemList>
|
<ItemList>
|
||||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||||
{apps.map((app, idx) => {
|
{apps.map((app, idx) => {
|
||||||
|
|
|
@ -3,11 +3,11 @@ import styled from 'styled-components';
|
||||||
|
|
||||||
import selectedTheme from './themeManager';
|
import selectedTheme from './themeManager';
|
||||||
import {
|
import {
|
||||||
|
handleResponse,
|
||||||
Headline,
|
Headline,
|
||||||
ListContainer,
|
ListContainer,
|
||||||
ItemList,
|
ItemList,
|
||||||
Item,
|
Item,
|
||||||
RefreshButton,
|
|
||||||
ErrorMessage
|
ErrorMessage
|
||||||
} from './elements';
|
} from './elements';
|
||||||
|
|
||||||
|
@ -35,18 +35,12 @@ const Bookmark = styled.a`
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const handleResponse = response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
throw new Error('Failed to load app data.');
|
|
||||||
};
|
|
||||||
|
|
||||||
const useBookmarkData = () => {
|
const useBookmarkData = () => {
|
||||||
const [bookmarkData, setBookmarkData] = useState({
|
const [bookmarkData, setBookmarkData] = useState({
|
||||||
groups: [],
|
groups: [],
|
||||||
error: false
|
error: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchBookmarkData = useCallback(() => {
|
const fetchBookmarkData = useCallback(() => {
|
||||||
(process.env.NODE_ENV === 'production'
|
(process.env.NODE_ENV === 'production'
|
||||||
? fetch('/bookmarks.json').then(handleResponse)
|
? fetch('/bookmarks.json').then(handleResponse)
|
||||||
|
@ -74,7 +68,6 @@ const BookmarkList = () => {
|
||||||
return (
|
return (
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
<Headline>Bookmarks</Headline>
|
<Headline>Bookmarks</Headline>
|
||||||
<RefreshButton onClick={fetchBookmarkData}>refresh</RefreshButton>
|
|
||||||
<ItemList>
|
<ItemList>
|
||||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||||
{groups.map((group, idx) => {
|
{groups.map((group, idx) => {
|
||||||
|
|
|
@ -20,6 +20,90 @@
|
||||||
"mainColor": "",
|
"mainColor": "",
|
||||||
"accentColor": "",
|
"accentColor": "",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Blackboard",
|
||||||
|
"value": 3,
|
||||||
|
"mainColor": "#fffdea",
|
||||||
|
"accentColor": "5c5c5c",
|
||||||
|
"backgroundColor": "#1a1a1a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Gazette",
|
||||||
|
"value": 4,
|
||||||
|
"mainColor": "#000000",
|
||||||
|
"accentColor": "#5c5c5c",
|
||||||
|
"backgroundColor": "#F2F7FF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Espresso",
|
||||||
|
"value": 5,
|
||||||
|
"mainColor": "#d1b59a",
|
||||||
|
"accentColor": "#4e4e4e",
|
||||||
|
"backgroundColor": "#21211f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Cab",
|
||||||
|
"value": 6,
|
||||||
|
"mainColor": "#1f1f1f",
|
||||||
|
"accentColor": "#424242",
|
||||||
|
"backgroundColor": "#f6d305"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Cloud",
|
||||||
|
"value": 7,
|
||||||
|
"mainColor": "#35342f",
|
||||||
|
"accentColor": "#37bbe4",
|
||||||
|
"backgroundColor": "#f1f2f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Lime",
|
||||||
|
"value": 8,
|
||||||
|
"mainColor": "#aabbc3",
|
||||||
|
"accentColor": "#aeea00",
|
||||||
|
"backgroundColor": "#263238"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "White",
|
||||||
|
"value": 9,
|
||||||
|
"mainColor": "#222222",
|
||||||
|
"accentColor": "#dddddd",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Tron",
|
||||||
|
"value": 10,
|
||||||
|
"mainColor": "#effbff",
|
||||||
|
"accentColor": "#6ee2ff",
|
||||||
|
"backgroundColor": "#242b33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Blues",
|
||||||
|
"value": 11,
|
||||||
|
"mainColor": "#eff1fc",
|
||||||
|
"accentColor": "#6677eb",
|
||||||
|
"backgroundColor": "#2b2c56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Passion",
|
||||||
|
"value": 12,
|
||||||
|
"mainColor": "#12005e",
|
||||||
|
"accentColor": "#8e24aa",
|
||||||
|
"backgroundColor": "#f5f5f5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Chalk",
|
||||||
|
"value": 13,
|
||||||
|
"mainColor": "#aabbc3",
|
||||||
|
"accentColor": "#ff869a",
|
||||||
|
"backgroundColor": "#263238"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Paper",
|
||||||
|
"value": 14,
|
||||||
|
"mainColor": "#4c432e",
|
||||||
|
"accentColor": "#aa9a73",
|
||||||
|
"backgroundColor": "#f8f6f1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,13 @@ import MaterialIcon from 'material-icons-react';
|
||||||
|
|
||||||
// File for elements that are/can be reused across the entire site.
|
// File for elements that are/can be reused across the entire site.
|
||||||
|
|
||||||
|
export const handleResponse = response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
throw new Error('Failed to load data.');
|
||||||
|
};
|
||||||
|
|
||||||
export const ListContainer = styled.div`
|
export const ListContainer = styled.div`
|
||||||
padding: 2rem 0 2rem 0;
|
padding: 2rem 0 2rem 0;
|
||||||
`;
|
`;
|
||||||
|
@ -40,8 +47,8 @@ export const Button = styled.button`
|
||||||
border: 1px solid ${selectedTheme.mainColor};
|
border: 1px solid ${selectedTheme.mainColor};
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${selectedTheme.mainColor};
|
||||||
background: none;
|
background: none;
|
||||||
margin-left: 1rem;
|
|
||||||
min-height: 3em;
|
min-height: 3em;
|
||||||
|
height: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
const StyledButton = styled.button`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { ErrorMessage } from './elements';
|
import { handleResponse, ErrorMessage } from './elements';
|
||||||
|
|
||||||
import selectedTheme from './themeManager';
|
import selectedTheme from './themeManager';
|
||||||
|
|
||||||
|
@ -15,18 +15,12 @@ const SearchInput = styled.input`
|
||||||
color: ${selectedTheme.mainColor};
|
color: ${selectedTheme.mainColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const handleResponse = response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
throw new Error('Failed to load app data.');
|
|
||||||
};
|
|
||||||
|
|
||||||
const useSearchProviders = () => {
|
const useSearchProviders = () => {
|
||||||
const [searchProviders, setSearchProviders] = useState({
|
const [searchProviders, setSearchProviders] = useState({
|
||||||
providers: [],
|
providers: [],
|
||||||
error: false
|
error: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchSearchProviders = useCallback(() => {
|
const fetchSearchProviders = useCallback(() => {
|
||||||
(process.env.NODE_ENV === 'production'
|
(process.env.NODE_ENV === 'production'
|
||||||
? fetch('/search.json').then(handleResponse)
|
? fetch('/search.json').then(handleResponse)
|
||||||
|
@ -43,11 +37,14 @@ const useSearchProviders = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSearchProviders();
|
fetchSearchProviders();
|
||||||
}, [fetchSearchProviders]);
|
}, [fetchSearchProviders]);
|
||||||
return searchProviders;
|
return { searchProviders, fetchSearchProviders };
|
||||||
};
|
};
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
const searchProviders = useSearchProviders();
|
const {
|
||||||
|
searchProviders: { providers, error },
|
||||||
|
fetchSearchProviders
|
||||||
|
} = useSearchProviders();
|
||||||
|
|
||||||
let [input, setInput] = useState();
|
let [input, setInput] = useState();
|
||||||
|
|
||||||
|
@ -70,24 +67,17 @@ const SearchBar = () => {
|
||||||
|
|
||||||
let searchQuery = queryArray.join(' ');
|
let searchQuery = queryArray.join(' ');
|
||||||
|
|
||||||
var foundProvider = false;
|
providers.forEach(provider => {
|
||||||
searchProviders.providers.forEach(provider => {
|
if (provider.prefix === prefix)
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={e => handleSearchQuery(e)}>
|
<form onSubmit={e => handleSearchQuery(e)}>
|
||||||
{searchProviders.error && (
|
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||||
<ErrorMessage>{searchProviders.error}</ErrorMessage>
|
|
||||||
)}
|
|
||||||
<SearchInput
|
<SearchInput
|
||||||
type="text"
|
type="text"
|
||||||
onChange={e => setInput(e.target.value)}
|
onChange={e => setInput(e.target.value)}
|
||||||
|
|
|
@ -6,7 +6,17 @@ import Select from 'react-select';
|
||||||
import searchData from './data/search.json';
|
import searchData from './data/search.json';
|
||||||
|
|
||||||
import selectedTheme, { setTheme } from './themeManager';
|
import selectedTheme, { setTheme } from './themeManager';
|
||||||
import { Button, IconButton, ErrorMessage } from './elements';
|
import {
|
||||||
|
handleResponse,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
ErrorMessage,
|
||||||
|
Headline as hl
|
||||||
|
} from './elements';
|
||||||
|
|
||||||
|
const Headline = styled(hl)`
|
||||||
|
padding: 0.5rem 0 0.5rem 0;
|
||||||
|
`;
|
||||||
|
|
||||||
const Modal = styled.div`
|
const Modal = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -24,16 +34,8 @@ const SelectContainer = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FormContainer = styled.div`
|
const FormContainer = styled.div`
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-template-columns: auto auto auto;
|
||||||
flex-wrap: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Headline = styled.h3`
|
|
||||||
font-family: Roboto, sans-serif;
|
|
||||||
font-weight: 900;
|
|
||||||
color: ${selectedTheme.mainColor};
|
|
||||||
margin-top: 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Table = styled.table`
|
const Table = styled.table`
|
||||||
|
@ -57,6 +59,11 @@ const HeadCell = styled.th`
|
||||||
background: none;
|
background: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const InfoText = styled.p`
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0.5rem 0 0.5rem 0;
|
||||||
|
`;
|
||||||
|
|
||||||
const SelectorStyle = {
|
const SelectorStyle = {
|
||||||
control: provided => ({
|
control: provided => ({
|
||||||
...provided,
|
...provided,
|
||||||
|
@ -102,13 +109,6 @@ const SelectorStyle = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResponse = response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
throw new Error('Failed to load app data.');
|
|
||||||
};
|
|
||||||
|
|
||||||
const useThemeData = () => {
|
const useThemeData = () => {
|
||||||
const [themeData, setThemeData] = useState({ themes: [], error: false });
|
const [themeData, setThemeData] = useState({ themes: [], error: false });
|
||||||
const fetchThemeData = useCallback(() => {
|
const fetchThemeData = useCallback(() => {
|
||||||
|
@ -162,12 +162,15 @@ const SettingsModal = () => {
|
||||||
}}
|
}}
|
||||||
styles={SelectorStyle}
|
styles={SelectorStyle}
|
||||||
/>
|
/>
|
||||||
<Button onClick={fetchThemeData}>Refresh</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setTheme(JSON.stringify(newTheme))}
|
onClick={() => setTheme(JSON.stringify(newTheme))}
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => window.location.reload()}>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</SelectContainer>
|
</SelectContainer>
|
||||||
<Table>
|
<Table>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
import App from './app';
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
|
|
Loading…
Reference in a new issue