Refactor
This commit is contained in:
parent
9fef36eae3
commit
8e3dd6e77d
19 changed files with 327 additions and 335 deletions
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Bastian Meissner
|
||||
Copyright (c) 2021 Bastian Meissner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
339
README.md
339
README.md
|
@ -1,33 +1,61 @@
|
|||
# Dashboard
|
||||
|
||||
## IMPORTANT: UPDATE
|
||||
|
||||
Yesterday, an update has been released that changed a couple of things:
|
||||
|
||||
- The serving port has been changed from `3000` to `8080`.
|
||||
- The structure of `imprint.json` has been changed. Make sure that the format of your `imprint.json`-file matches the format of the ones within this repository.
|
||||
|
||||
![Alt text](/screenshot.png?raw=true "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.
|
||||
|
||||
## Features
|
||||
|
||||
So what makes this thing better than SUI?
|
||||
So what makes this project different from (or even 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 (The URL displayed for apps can differ from the actual URL)
|
||||
- Categorization for apps
|
||||
- Themes and search providers can be changed using JSON
|
||||
- Imprint functionality
|
||||
|
||||
## Installation
|
||||
|
||||
Getting Dashboard to run is fairly simple and can be accomplished with two techniques:
|
||||
The recommended way of installation is using [Docker](https://docker.com). You could also build your own version from source, but do proceed at your own risk.
|
||||
|
||||
1. Locally
|
||||
### Docker
|
||||
|
||||
Prerequisites: yarn, npm, node
|
||||
The Docker image is built on top of [this image](https://github.com/ratisbona-coding/nginx-cloudflare-cache), as it's based on Nginx and also provides functionality to purge the Cloudflare cache every time the container restarts (though this functionality is entirely optional).
|
||||
|
||||
1. Using the Docker CLI:
|
||||
|
||||
```sh
|
||||
$ docker run -d \
|
||||
-e CLOUDFLARE_ZONE_ID=[OPTIONAL CLOUDFLARE V4 ZONE ID] \
|
||||
-e CLOUDFLARE_PURGE_TOKEN=[OPTIONAL CLOUDFLARE PURGE TOKEN] \
|
||||
-v $(pwd)/data:/app/data
|
||||
-p 8080:8080 \
|
||||
--name dashboard \
|
||||
phntxx/dashboard
|
||||
```
|
||||
|
||||
2. Using Docker-Compose:
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
dashboard:
|
||||
image: phntxx/dashboard:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CLOUDFLARE_ZONE_ID=[OPTIONAL CLOUDFLARE V4 ZONE ID]
|
||||
- CLOUDFLARE_PURGE_TOKEN=[OPTIONAL CLOUDFLARE PURGE TOKEN]
|
||||
volumes:
|
||||
- [path to data directory]:/app/data
|
||||
ports:
|
||||
- 8080:8080
|
||||
```
|
||||
|
||||
### Compile from source
|
||||
|
||||
I really don't anticipate people to use this, so go forth at your own risk.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/phntxx/dashboard.git
|
||||
$ cd dashboard/
|
||||
$ yarn
|
||||
|
@ -35,196 +63,137 @@ $ yarn build
|
|||
$ yarn serve:production
|
||||
```
|
||||
|
||||
2. Using Docker
|
||||
## Configuration
|
||||
|
||||
```
|
||||
$ docker run -d \
|
||||
-v $(pwd)/data:/app/data
|
||||
-p 8080:8080 \
|
||||
--name dashboard \
|
||||
phntxx/dashboard
|
||||
```
|
||||
There's a couple of things you can / need to configure to get Dashboard to look and behave just as you want it to.
|
||||
|
||||
Sample Docker Compose configuration:
|
||||
If you don't require a specific component, just remove the file from your `data`-directory. Dashboard won't render the components whose files are not present. With no files present, only the greeter will be shown.
|
||||
|
||||
```
|
||||
version: "3"
|
||||
### Apps
|
||||
|
||||
services:
|
||||
dashboard:
|
||||
image: phntxx/dashboard:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- [path to data directory]:/app/data
|
||||
ports:
|
||||
- 8080:8080
|
||||
```
|
||||
To show the apps you want to show, change `apps.json` to resemble the following:
|
||||
|
||||
**Note: You might still need to clone the repository in order to get the JSON-files which are required for the
|
||||
app to run**
|
||||
|
||||
## Customization
|
||||
|
||||
Dashboard is designed to be customizable. Everything is handled using four .json-files, which can be found at /src/components/data
|
||||
|
||||
### Applications
|
||||
|
||||
To add an application, append the following to apps.json or simply edit one of the examples given.
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
"categories": [
|
||||
{
|
||||
"name": "[Name of the category]",
|
||||
"items": [
|
||||
{
|
||||
"name": "[Name of the app]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
],
|
||||
"apps": [
|
||||
{
|
||||
"name": "[Name of the app]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Wherein either `apps` or `categories` can be omitted as needed.
|
||||
|
||||
To find icons, simply go to the [Material Design Icon Library](https://material.io/icons/) and copy one of the codes for an icon there.
|
||||
|
||||
**NEW FEATURE: CATEGORIES**
|
||||
|
||||
To add a category to your dashboard, change apps.json to resemble the following:
|
||||
|
||||
```
|
||||
{
|
||||
"categories": [
|
||||
...
|
||||
],
|
||||
"apps": [
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Then, a category can be added by entering the following within the "categories" field:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "[Name of the category]",
|
||||
"items": [
|
||||
[Application goes here]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In the end, your apps.json file should look something like this:
|
||||
|
||||
1. Without categories
|
||||
|
||||
```
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. With apps and categories
|
||||
|
||||
```
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"name": "[Name of the category]",
|
||||
"items": [
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
],
|
||||
"apps": [
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
{
|
||||
"name": "[Name of the Application]",
|
||||
"displayURL": "[URL you want to show]",
|
||||
"URL": "[URL to redirect to]",
|
||||
"icon": "[Icon code]"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Bookmarks
|
||||
|
||||
To add a bookmark, append the following to bookmarks.json or simply edit one of the examples given.
|
||||
To show bookmarks, `bookmarks.json` needs to resemble the following:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "[Category name]",
|
||||
"items": [
|
||||
{
|
||||
"name": "[Bookmark name]",
|
||||
"url": "[URL to redirect to]"
|
||||
},
|
||||
{
|
||||
"name": "[Bookmark name]",
|
||||
"url": "[URL to redirect to]"
|
||||
},
|
||||
{
|
||||
"name": "[Bookmark name]",
|
||||
"url": "[URL to redirect to]"
|
||||
}
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
### Theming:
|
||||
|
||||
Dashboard also supports themes with the help of a simple JSON-file: themes.json. To add a theme, append the following to themes.json:
|
||||
|
||||
```
|
||||
{
|
||||
"label": "[Theme Name]",
|
||||
"value": [Number of the theme],
|
||||
"mainColor": "[Main Color as 6-character hex code]",
|
||||
"accentColor": "[Accent Color as 6-character hex code]",
|
||||
"backgroundColor": "[Background Color as 6-character hex code]"
|
||||
"groups": [
|
||||
{
|
||||
"name": "[Group Name]",
|
||||
"items": [
|
||||
{
|
||||
"name": "[Bookmark Name]",
|
||||
"url": "[Bookmark URL]"
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Search Providers:
|
||||
### Themes
|
||||
|
||||
The searchbar on the top supports shortcuts like "/so", just as SUI does. To add one of your own, simply append the following to search.json
|
||||
In order to customize theming, `themes.json` needs to resemble this:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name":"[Name of the website]",
|
||||
"url":"[Link that processes searches on that website]",
|
||||
"prefix":"[a custom prefix]"
|
||||
},
|
||||
"themes": [
|
||||
{
|
||||
"label": "[Theme Name]",
|
||||
"value": "[Number of the theme]",
|
||||
"mainColor": "[Main Color as 6-character hex code]",
|
||||
"accentColor": "[Accent Color as 6-character hex code]",
|
||||
"backgroundColor": "[Background Color as 6-character hex code]"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Search Providers
|
||||
|
||||
For search providers to work, make sure your `search.json` resembles the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": [
|
||||
{
|
||||
"name": "[Name of the website]",
|
||||
"url": "[Link that processes searches on that website]",
|
||||
"prefix": "[A custom prefix (e.g. '/test')]"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Imprint
|
||||
|
||||
In order for the imprint-modal to show up, make sure your `imprint.json` resembles the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"imprint": {
|
||||
"name": {
|
||||
"text": "[Name]",
|
||||
"link": "[Link to the name (to e.g. a portfolio)]"
|
||||
},
|
||||
"address": {
|
||||
"text": "[Address]",
|
||||
"link": "[Link for the address (to e.g. Google Maps)]"
|
||||
},
|
||||
"phone": {
|
||||
"text": "[Phone number]",
|
||||
"link": "[Link for the phone number]"
|
||||
},
|
||||
"email": {
|
||||
"text": "[Email address]",
|
||||
"link": "[Link for the email address (e.g. for 'mailto')]"
|
||||
},
|
||||
"url": {
|
||||
"text": "[URL]",
|
||||
"link": "[Link for the URL]"
|
||||
},
|
||||
"text": "[Text for the imprint]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> :exclamation: I haven't quite tested this. I'm not a lawyer and I'm not responsible if you're sued for using this incorrectly.
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"url": {
|
||||
"text": "example.com",
|
||||
"link": "#"
|
||||
}
|
||||
},
|
||||
"text": "This is the place where you should put whatever you want it to say on the imprint page."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ const GlobalStyle = createGlobalStyle`
|
|||
|
||||
/**
|
||||
* Renders the entire app by calling individual components
|
||||
* @returns
|
||||
*/
|
||||
const App = () => {
|
||||
|
||||
|
|
|
@ -46,22 +46,21 @@ const AppDescription = styled.p`
|
|||
export interface IAppProps {
|
||||
name: string;
|
||||
icon: string;
|
||||
URL: string;
|
||||
url: string;
|
||||
displayURL: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders one app in the list
|
||||
* @param
|
||||
* @returns
|
||||
* Renders a single app shortcut
|
||||
* @param {IAppProps} props - The props of the given app
|
||||
*/
|
||||
export const App = ({ name, icon, URL, displayURL }: IAppProps) => (
|
||||
export const App = ({ name, icon, url, displayURL }: IAppProps) => (
|
||||
<AppContainer>
|
||||
<IconContainer>
|
||||
<Icon name={icon} />
|
||||
</IconContainer>
|
||||
<DetailsContainer>
|
||||
<AppLink href={URL}>{name}</AppLink>
|
||||
<AppLink href={url}>{name}</AppLink>
|
||||
<AppDescription>{displayURL}</AppDescription>
|
||||
</DetailsContainer>
|
||||
</AppContainer>
|
||||
|
|
|
@ -16,6 +16,10 @@ export interface IAppCategoryProps {
|
|||
items: Array<IAppProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders one app category
|
||||
* @param {IAppCategoryProps} props - The props of the given category
|
||||
*/
|
||||
export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
||||
<CategoryContainer>
|
||||
{name && <CategoryHeadline>{name}</CategoryHeadline>}
|
||||
|
|
|
@ -9,22 +9,24 @@ export interface IAppListProps {
|
|||
apps: Array<IAppProps>;
|
||||
}
|
||||
|
||||
const AppList = ({ categories, apps }: IAppListProps) => {
|
||||
return (
|
||||
<ListContainer>
|
||||
<Headline>Applications</Headline>
|
||||
{categories &&
|
||||
categories.map(({ name, items }, idx) => (
|
||||
<AppCategory key={[name, idx].join("")} name={name} items={items} />
|
||||
))}
|
||||
{apps && (
|
||||
<AppCategory
|
||||
name={categories ? "Uncategorized apps" : ""}
|
||||
items={apps}
|
||||
/>
|
||||
)}
|
||||
</ListContainer>
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Renders one list containing all app categories and uncategorized apps
|
||||
* @param {IAppListProps} props - The props of the given list of apps
|
||||
*/
|
||||
const AppList = ({ categories, apps }: IAppListProps) => (
|
||||
<ListContainer>
|
||||
<Headline>Applications</Headline>
|
||||
{categories &&
|
||||
categories.map(({ name, items }, idx) => (
|
||||
<AppCategory key={[name, idx].join("")} name={name} items={items} />
|
||||
))}
|
||||
{apps && (
|
||||
<AppCategory
|
||||
name={categories ? "Uncategorized apps" : ""}
|
||||
items={apps}
|
||||
/>
|
||||
)}
|
||||
</ListContainer>
|
||||
);
|
||||
|
||||
export default AppList;
|
||||
|
|
|
@ -28,14 +28,15 @@ export interface IBookmarkProps {
|
|||
}
|
||||
|
||||
export interface IBookmarkGroupProps {
|
||||
name: string;
|
||||
groupName: string;
|
||||
items: Array<IBookmarkProps>;
|
||||
}
|
||||
|
||||
export const BookmarkGroup = ({
|
||||
name: groupName,
|
||||
items,
|
||||
}: IBookmarkGroupProps) => (
|
||||
/**
|
||||
* Renders a given bookmark group
|
||||
* @param {IBookmarkGroupProps} props - The given props of the bookmark group
|
||||
*/
|
||||
export const BookmarkGroup = ({ groupName, items }: IBookmarkGroupProps) => (
|
||||
<Item>
|
||||
<GroupContainer>
|
||||
<SubHeadline>{groupName}</SubHeadline>
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
import React from "react";
|
||||
|
||||
import { Headline, ListContainer, ItemList } from "./elements";
|
||||
|
||||
import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup";
|
||||
|
||||
interface IBookmarkListProps {
|
||||
groups: Array<IBookmarkGroupProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a given list of categorized bookmarks
|
||||
* @param {IBookmarkListProps} props - The props of the given bookmark list
|
||||
*/
|
||||
const BookmarkList = ({ groups }: IBookmarkListProps) => {
|
||||
return (
|
||||
<ListContainer>
|
||||
<Headline>Bookmarks</Headline>
|
||||
<ItemList>
|
||||
{groups.map(({ name, items }, idx) => (
|
||||
<BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
|
||||
{groups.map(({ groupName, items }, idx) => (
|
||||
<BookmarkGroup key={[groupName, idx].join("")} groupName={groupName} items={items} />
|
||||
))}
|
||||
</ItemList>
|
||||
</ListContainer>
|
||||
|
|
|
@ -3,8 +3,6 @@ import styled from "styled-components";
|
|||
import selectedTheme from "../lib/theme";
|
||||
import Icon from "./icon";
|
||||
|
||||
// File for elements that are/can be reused across the entire site.
|
||||
|
||||
export const ListContainer = styled.div`
|
||||
padding: 2rem 0;
|
||||
`;
|
||||
|
@ -75,6 +73,10 @@ interface IIconButtonProps {
|
|||
onClick: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button with an icon
|
||||
* @param {IIconProps} props - The props of the given IconButton
|
||||
*/
|
||||
export const IconButton = ({ icon, onClick }: IIconButtonProps) => (
|
||||
<StyledButton onClick={onClick}>
|
||||
<Icon name={icon} />
|
||||
|
|
|
@ -22,37 +22,6 @@ const DateText = styled.h3`
|
|||
color: ${selectedTheme.accentColor};
|
||||
`;
|
||||
|
||||
const getGreeting = () => {
|
||||
switch (Math.floor(new Date().getHours() / 6)) {
|
||||
case 0:
|
||||
return "Good night!";
|
||||
case 1:
|
||||
return "Good morning!";
|
||||
case 2:
|
||||
return "Good afternoon!";
|
||||
case 3:
|
||||
return "Good evening!";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const getExtension = (day: number) => {
|
||||
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";
|
||||
}
|
||||
|
||||
return extension;
|
||||
};
|
||||
|
||||
const monthNames = [
|
||||
"January",
|
||||
"February",
|
||||
|
@ -78,6 +47,50 @@ const weekDayNames = [
|
|||
"Saturday",
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns a greeting based on the current time
|
||||
* @returns {string} - A greeting
|
||||
*/
|
||||
const getGreeting = () => {
|
||||
switch (Math.floor(new Date().getHours() / 6)) {
|
||||
case 0:
|
||||
return "Good night!";
|
||||
case 1:
|
||||
return "Good morning!";
|
||||
case 2:
|
||||
return "Good afternoon!";
|
||||
case 3:
|
||||
return "Good evening!";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the appropriate extension for a number (eg. 'rd' for '3' to make '3rd')
|
||||
* @param {number} day - The number of a day within a month
|
||||
* @returns {string} - The extension for that number
|
||||
*/
|
||||
const getExtension = (day: number) => {
|
||||
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";
|
||||
}
|
||||
|
||||
return extension;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the current date
|
||||
* @returns {string} - The current date as a string
|
||||
*/
|
||||
const getDateString = () => {
|
||||
let currentDate = new Date();
|
||||
|
||||
|
@ -93,6 +106,9 @@ const getDateString = () => {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the Greeter
|
||||
*/
|
||||
const Greeter = () => {
|
||||
let date = getDateString();
|
||||
let greeting = getGreeting();
|
||||
|
|
|
@ -7,26 +7,30 @@ interface IIconProps {
|
|||
size?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an Icon
|
||||
* @param {IIconProps} props - The props needed for the given icon
|
||||
*/
|
||||
export const Icon = ({ name, size }: IIconProps) => {
|
||||
|
||||
let IconContainer = styled.i`
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: ${size ? size : "24px"};
|
||||
color: ${selectedTheme.mainColor};
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: "liga";
|
||||
`;
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: ${size ? size : "24px"};
|
||||
color: ${selectedTheme.mainColor};
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: "liga";
|
||||
`;
|
||||
|
||||
return <IconContainer>{name}</IconContainer>;
|
||||
};
|
||||
|
|
|
@ -5,16 +5,11 @@ import selectedTheme from "../lib/theme";
|
|||
import {
|
||||
ListContainer,
|
||||
ItemList,
|
||||
Headline as Hl,
|
||||
SubHeadline as SHl,
|
||||
Headline,
|
||||
SubHeadline,
|
||||
} from "./elements";
|
||||
|
||||
const Headline = styled(Hl)`
|
||||
display: block;
|
||||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
const ModalSubHeadline = styled(SHl)`
|
||||
const ModalSubHeadline = styled(SubHeadline)`
|
||||
display: block;
|
||||
padding: 0.5rem 0;
|
||||
`;
|
||||
|
@ -53,27 +48,36 @@ export interface IImprintProps {
|
|||
phone: IImprintFieldProps;
|
||||
email: IImprintFieldProps;
|
||||
url: IImprintFieldProps;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface IImprintFieldComponentProps {
|
||||
field: IImprintFieldProps;
|
||||
}
|
||||
|
||||
const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
||||
<Link href={field.link}>{field.text}</Link>
|
||||
);
|
||||
|
||||
interface IImprintComponentProps {
|
||||
imprint: IImprintProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an imprint field
|
||||
* @param {IImprintFieldComponentProps} props - The data for the field
|
||||
*/
|
||||
const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
||||
<Link href={field.link}>{field.text}</Link>
|
||||
);
|
||||
|
||||
/**
|
||||
* Renders the imprint component
|
||||
* @param {IImprintProps} props - The contents of the imprint
|
||||
*/
|
||||
const Imprint = ({ imprint }: IImprintComponentProps) => (
|
||||
<>
|
||||
<ListContainer>
|
||||
<Hl>About</Hl>
|
||||
<Headline>About</Headline>
|
||||
<ItemList>
|
||||
<ItemContainer>
|
||||
<SHl>Imprint</SHl>
|
||||
<SubHeadline>Imprint</SubHeadline>
|
||||
<Modal
|
||||
element="text"
|
||||
text="View Imprint"
|
||||
|
@ -96,37 +100,8 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
|||
{imprint.phone && <ImprintField field={imprint.phone} />}
|
||||
{imprint.url && <ImprintField field={imprint.url} />}
|
||||
</>
|
||||
<Headline>Disclaimer</Headline>
|
||||
<ModalSubHeadline>Accountability for content</ModalSubHeadline>
|
||||
<Text>
|
||||
The contents of our pages have been created with the utmost care.
|
||||
However, we cannot guarantee the contents' accuracy, completeness
|
||||
or topicality. According to statutory provisions, we are
|
||||
furthermore responsible for our own content on these web pages. In
|
||||
this matter, please note that we are not obliged to monitor the
|
||||
transmitted or saved information of third parties, or investigate
|
||||
circumstances pointing to illegal activity. Our obligations to
|
||||
remove or block the use of information under generally applicable
|
||||
laws remain unaffected by this as per §§ 8 to 10 of the Telemedia
|
||||
Act (TMG).
|
||||
</Text>
|
||||
<ModalSubHeadline>Accountability for links</ModalSubHeadline>
|
||||
<Text>
|
||||
Responsibility for the content of external links (to web pages of
|
||||
third parties) lies solely with the operators of the linked pages.
|
||||
No violations were evident to us at the time of linking. Should
|
||||
any legal infringement become known to us, we will remove the
|
||||
respective link immediately.
|
||||
</Text>
|
||||
<ModalSubHeadline>Copyright</ModalSubHeadline>
|
||||
<Text>
|
||||
Our web pages and their contents are subject to German copyright
|
||||
law. Unless expressly permitted by law, every form of utilizing,
|
||||
reproducing or processing works subject to copyright protection on
|
||||
our web pages requires the prior consent of the respective owner
|
||||
of the rights. Individual reproductions of a work are only allowed
|
||||
for private use. The materials from these pages are copyrighted
|
||||
and any unauthorized use may violate copyright laws.
|
||||
{imprint.text}
|
||||
</Text>
|
||||
</Modal>
|
||||
</ItemContainer>
|
||||
|
|
|
@ -37,7 +37,7 @@ const TitleContainer = styled.div`
|
|||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
interface IModalInterface {
|
||||
interface IModalProps {
|
||||
element: string;
|
||||
icon?: string;
|
||||
text?: string;
|
||||
|
@ -47,7 +47,11 @@ interface IModalInterface {
|
|||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Modal = ({ element, icon, text, condition, title, onClose, children }: IModalInterface) => {
|
||||
/**
|
||||
* Renders a modal with button to hide and un-hide
|
||||
* @param {IModalProps} props - The needed props for the modal
|
||||
*/
|
||||
const Modal = ({ element, icon, text, condition, title, onClose, children }: IModalProps) => {
|
||||
const [modalHidden, setModalHidden] = useState(condition ?? true);
|
||||
|
||||
const closeModal = () => {
|
||||
|
|
|
@ -48,6 +48,10 @@ interface ISearchBarProps {
|
|||
providers: Array<ISearchProviderProps> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a search bar
|
||||
* @param {ISearchBarProps} props - The search providers for the search bar to use
|
||||
*/
|
||||
const SearchBar = ({ providers }: ISearchBarProps) => {
|
||||
let [input, setInput] = useState<string>("");
|
||||
let [buttonsHidden, setButtonsHidden] = useState<boolean>(true);
|
||||
|
|
|
@ -9,13 +9,6 @@ import { Button, SubHeadline } from "./elements";
|
|||
|
||||
import Modal from "./modal";
|
||||
|
||||
/**
|
||||
* Complementary code to get hover pseudo-classes working in React
|
||||
* @param color the color of the element on hover
|
||||
* @param backgroundColor the background color of the element on hover
|
||||
* @param border the border of the element on hover
|
||||
* @param borderColor the border color of the element on hover
|
||||
*/
|
||||
interface IHoverProps {
|
||||
color?: string;
|
||||
backgroundColor?: string;
|
||||
|
@ -125,6 +118,12 @@ interface ISettingsProps {
|
|||
providers: Array<ISearchProviderProps> | undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the settings-modal
|
||||
* @param {Array<IThemeProps>} themes - the list of themes a user can select between
|
||||
* @param {Array<ISearchProviderProps>} providers - the list of search providers
|
||||
*/
|
||||
const Settings = ({ themes, providers }: ISettingsProps) => {
|
||||
const [newTheme, setNewTheme] = useState();
|
||||
|
||||
|
@ -132,9 +131,6 @@ const Settings = ({ themes, providers }: ISettingsProps) => {
|
|||
return (
|
||||
<Modal element="icon" icon="settings" title="Settings">
|
||||
{themes && (
|
||||
|
||||
|
||||
|
||||
<Section>
|
||||
<SectionHeadline>Theme:</SectionHeadline>
|
||||
<FormContainer>
|
||||
|
@ -154,7 +150,6 @@ const Settings = ({ themes, providers }: ISettingsProps) => {
|
|||
</Section>
|
||||
)}
|
||||
{providers && (
|
||||
|
||||
<Section>
|
||||
<SectionHeadline>Search Providers</SectionHeadline>
|
||||
<Table>
|
||||
|
@ -172,7 +167,6 @@ const Settings = ({ themes, providers }: ISettingsProps) => {
|
|||
</tbody>
|
||||
</Table>
|
||||
</Section>
|
||||
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"url": {
|
||||
"text": "example.com",
|
||||
"link": "#"
|
||||
}
|
||||
},
|
||||
"text": "This is the place where you should put whatever you want it to say on the imprint page."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,13 @@ import { IImprintProps } from "../components/imprint";
|
|||
const errorMessage = "Failed to load data.";
|
||||
const inProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
const handleResponse = (response: any) => {
|
||||
/**
|
||||
* Handles the response from the fetch requests
|
||||
* @param {Response} response - The response given by the fetch request
|
||||
* @returns - The response in JSON
|
||||
* @throws - Error with given error message if request failed
|
||||
*/
|
||||
const handleResponse = (response: Response) => {
|
||||
if (response.ok) return response.json();
|
||||
throw new Error(errorMessage);
|
||||
};
|
||||
|
@ -69,6 +75,7 @@ const defaults = {
|
|||
phone: { text: "", link: "" },
|
||||
email: { text: "", link: "" },
|
||||
url: { text: "", link: "" },
|
||||
text: "",
|
||||
},
|
||||
error: false,
|
||||
},
|
||||
|
@ -97,7 +104,7 @@ const handleError = (status: string, error: Error) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches all of the data by doing fetch requests
|
||||
* Fetches all of the data by doing fetch requests (only available in production)
|
||||
*/
|
||||
const fetchProduction = Promise.all([
|
||||
fetch("/data/apps.json").then(handleResponse).catch((error: Error) => handleError("apps", error)),
|
||||
|
@ -120,7 +127,6 @@ const fetchDevelopment = Promise.all([
|
|||
|
||||
/**
|
||||
* Fetches app, bookmark, search, theme and imprint data and returns it.
|
||||
* @returns all of the data the function was able to fetch and the callback function to refresh the data
|
||||
*/
|
||||
export const useFetcher = () => {
|
||||
const [appData, setAppData] = useState<IAppDataProps>(defaults.app);
|
||||
|
@ -159,7 +165,8 @@ export const useFetcher = () => {
|
|||
bookmarkData,
|
||||
searchProviderData,
|
||||
themeData,
|
||||
imprintData, callback
|
||||
imprintData,
|
||||
callback
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -14,15 +14,23 @@ const defaultTheme: IThemeProps = {
|
|||
backgroundColor: "#ffffff",
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a given theme into localStorage
|
||||
* @param {string} theme - the theme that shall be saved (in stringified JSON)
|
||||
*/
|
||||
export const setTheme = (theme: string) => {
|
||||
if (theme !== undefined) localStorage.setItem("theme", theme);
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const getTheme = () => {
|
||||
/**
|
||||
* Function that gets the saved theme from localStorage or returns the default
|
||||
* @returns {IThemeProps} the saved theme or the default theme
|
||||
*/
|
||||
const getTheme = (): IThemeProps => {
|
||||
let selectedTheme = defaultTheme;
|
||||
|
||||
if (localStorage.getItem("theme") != null) {
|
||||
if (localStorage.getItem("theme") !== null) {
|
||||
selectedTheme = JSON.parse(localStorage.getItem("theme") || "{}");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue