mirror of
https://github.com/pawelmalak/flame.git
synced 2024-12-21 09:11:29 +03:00
Merge pull request #9 from pawelmalak/qol-improvements
Added favicon and changed page title. Changed message when there are …
This commit is contained in:
commit
cf44f45fde
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
@ -10,36 +10,15 @@
|
|||||||
content="Web site created using create-react-app"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900" rel="stylesheet">
|
||||||
<title>React App</title>
|
<title>Flame</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"short_name": "React App",
|
|
||||||
"name": "Create React App Sample",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
@ -18,6 +18,10 @@ if (localStorage.theme) {
|
|||||||
store.dispatch<any>(setTheme(localStorage.theme));
|
store.dispatch<any>(setTheme(localStorage.theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (localStorage.customTitle) {
|
||||||
|
document.title = localStorage.customTitle;
|
||||||
|
}
|
||||||
|
|
||||||
const App = (): JSX.Element => {
|
const App = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
@ -98,7 +98,7 @@ const AppForm = (props: ComponentProps): JSX.Element => {
|
|||||||
value={formData.url}
|
value={formData.url}
|
||||||
onChange={(e) => inputChangeHandler(e)}
|
onChange={(e) => inputChangeHandler(e)}
|
||||||
/>
|
/>
|
||||||
<span>Use URL without protocol</span>
|
<span>Only urls without http[s]:// are supported</span>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<label htmlFor='icon'>App Icon</label>
|
<label htmlFor='icon'>App Icon</label>
|
||||||
|
@ -6,6 +6,7 @@ import AppCard from '../AppCard/AppCard';
|
|||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
apps: App[];
|
apps: App[];
|
||||||
|
totalApps?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppGrid = (props: ComponentProps): JSX.Element => {
|
const AppGrid = (props: ComponentProps): JSX.Element => {
|
||||||
@ -23,9 +24,15 @@ const AppGrid = (props: ComponentProps): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
apps = (
|
if (props.totalApps) {
|
||||||
<p className={classes.AppsMessage}>You don't have any applications. You can add a new one from <Link to='/applications'>/application</Link> menu</p>
|
apps = (
|
||||||
);
|
<p className={classes.AppsMessage}>There are no pinned applications. You can pin them from the <Link to='/applications'>/applications</Link> menu</p>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
apps = (
|
||||||
|
<p className={classes.AppsMessage}>You don't have any applications. You can add a new one from <Link to='/applications'>/applications</Link> menu</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
@ -177,6 +177,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
|
|||||||
value={formData.url}
|
value={formData.url}
|
||||||
onChange={(e) => inputChangeHandler(e)}
|
onChange={(e) => inputChangeHandler(e)}
|
||||||
/>
|
/>
|
||||||
|
<span>Only urls without http[s]:// are supported</span>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<label htmlFor='categoryId'>Bookmark Category</label>
|
<label htmlFor='categoryId'>Bookmark Category</label>
|
||||||
|
@ -8,6 +8,7 @@ import BookmarkCard from '../BookmarkCard/BookmarkCard';
|
|||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
|
totalCategories?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookmarkGrid = (props: ComponentProps): JSX.Element => {
|
const BookmarkGrid = (props: ComponentProps): JSX.Element => {
|
||||||
@ -20,9 +21,15 @@ const BookmarkGrid = (props: ComponentProps): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
bookmarks = (
|
if (props.totalCategories) {
|
||||||
<p className={classes.BookmarksMessage}>You don't have any bookmarks. You can add a new one from <Link to='/bookmarks'>/bookmarks</Link> menu</p>
|
bookmarks = (
|
||||||
);
|
<p className={classes.BookmarksMessage}>There are no pinned categories. You can pin them from the <Link to='/bookmarks'>/bookmarks</Link> menu</p>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
bookmarks = (
|
||||||
|
<p className={classes.BookmarksMessage}>You don't have any bookmarks. You can add a new one from <Link to='/bookmarks'>/bookmarks</Link> menu</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bookmarks;
|
return bookmarks;
|
||||||
|
@ -81,7 +81,10 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||||||
<SectionHeadline title='Applications' link='/applications' />
|
<SectionHeadline title='Applications' link='/applications' />
|
||||||
{props.appsLoading
|
{props.appsLoading
|
||||||
? <Spinner />
|
? <Spinner />
|
||||||
: <AppGrid apps={props.apps.filter((app: App) => app.isPinned)} />
|
: <AppGrid
|
||||||
|
apps={props.apps.filter((app: App) => app.isPinned)}
|
||||||
|
totalApps={props.apps.length}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className={classes.HomeSpace}></div>
|
<div className={classes.HomeSpace}></div>
|
||||||
@ -89,7 +92,10 @@ const Home = (props: ComponentProps): JSX.Element => {
|
|||||||
<SectionHeadline title='Bookmarks' link='/bookmarks' />
|
<SectionHeadline title='Bookmarks' link='/bookmarks' />
|
||||||
{props.categoriesLoading
|
{props.categoriesLoading
|
||||||
? <Spinner />
|
? <Spinner />
|
||||||
: <BookmarkGrid categories={props.categories.filter((category: Category) => category.isPinned)} />
|
: <BookmarkGrid
|
||||||
|
categories={props.categories.filter((category: Category) => category.isPinned)}
|
||||||
|
totalCategories={props.categories.length}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Link to='/settings' className={classes.SettingsButton}>
|
<Link to='/settings' className={classes.SettingsButton}>
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
|
||||||
|
import Button from '../../UI/Buttons/Button/Button';
|
||||||
|
import { createNotification } from '../../../store/actions';
|
||||||
|
import { ApiResponse, Config, NewNotification } from '../../../interfaces';
|
||||||
|
|
||||||
|
interface FormState {
|
||||||
|
customTitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComponentProps {
|
||||||
|
createNotification: (notification: NewNotification) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OtherSettings = (props: ComponentProps): JSX.Element => {
|
||||||
|
const [formData, setFormData] = useState<FormState>({
|
||||||
|
customTitle: document.title
|
||||||
|
})
|
||||||
|
|
||||||
|
// get initial config
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get<ApiResponse<Config[]>>('/api/config?keys=customTitle')
|
||||||
|
.then(data => {
|
||||||
|
let tmpFormData = { ...formData };
|
||||||
|
|
||||||
|
data.data.data.forEach((config: Config) => {
|
||||||
|
let value: string | number = config.value;
|
||||||
|
if (config.valueType === 'number') {
|
||||||
|
value = parseFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFormData = {
|
||||||
|
...tmpFormData,
|
||||||
|
[config.key]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setFormData(tmpFormData);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const formSubmitHandler = (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
axios.put<ApiResponse<{}>>('/api/config', formData)
|
||||||
|
.then(() => {
|
||||||
|
props.createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Settings updated'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err));
|
||||||
|
|
||||||
|
// update local page title
|
||||||
|
localStorage.setItem('customTitle', formData.customTitle);
|
||||||
|
document.title = formData.customTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={(e) => formSubmitHandler(e)}>
|
||||||
|
<InputGroup>
|
||||||
|
<label htmlFor='customTitle'>Custom Page Title</label>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
id='customTitle'
|
||||||
|
name='customTitle'
|
||||||
|
placeholder='Flame'
|
||||||
|
value={formData.customTitle}
|
||||||
|
onChange={(e) => inputChangeHandler(e)}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
<Button>Save changes</Button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, { createNotification })(OtherSettings);
|
@ -6,6 +6,7 @@ import { Container } from '../UI/Layout/Layout';
|
|||||||
import Headline from '../UI/Headlines/Headline/Headline';
|
import Headline from '../UI/Headlines/Headline/Headline';
|
||||||
import Themer from '../Themer/Themer';
|
import Themer from '../Themer/Themer';
|
||||||
import WeatherSettings from './WeatherSettings/WeatherSettings';
|
import WeatherSettings from './WeatherSettings/WeatherSettings';
|
||||||
|
import OtherSettings from './OtherSettings/OtherSettings';
|
||||||
|
|
||||||
const Settings = (): JSX.Element => {
|
const Settings = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
@ -30,11 +31,19 @@ const Settings = (): JSX.Element => {
|
|||||||
to='/settings/weather'>
|
to='/settings/weather'>
|
||||||
Weather
|
Weather
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
className={classes.SettingsNavLink}
|
||||||
|
activeClassName={classes.SettingsNavLinkActive}
|
||||||
|
exact
|
||||||
|
to='/settings/other'>
|
||||||
|
Other
|
||||||
|
</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
<section className={classes.SettingsContent}>
|
<section className={classes.SettingsContent}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path='/settings' component={Themer} />
|
<Route exact path='/settings' component={Themer} />
|
||||||
<Route path='/settings/weather' component={WeatherSettings} />
|
<Route path='/settings/weather' component={WeatherSettings} />
|
||||||
|
<Route path='/settings/other' component={OtherSettings} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,8 +3,8 @@ const Config = require('../models/Config');
|
|||||||
|
|
||||||
const initConfig = async () => {
|
const initConfig = async () => {
|
||||||
// Config keys
|
// Config keys
|
||||||
const keys = ['WEATHER_API_KEY', 'lat', 'long', 'isCelsius'];
|
const keys = ['WEATHER_API_KEY', 'lat', 'long', 'isCelsius', 'customTitle'];
|
||||||
const values = ['', 0, 0, true];
|
const values = ['', 0, 0, true, 'Flame'];
|
||||||
|
|
||||||
// Get config values
|
// Get config values
|
||||||
const configPairs = await Config.findAll({
|
const configPairs = await Config.findAll({
|
||||||
|
Loading…
Reference in New Issue
Block a user