diff --git a/LICENSE b/LICENSE
index 0a79592..a5616c1 100644
--- a/LICENSE
+++ b/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
diff --git a/README.md b/README.md
index ad57616..20f92f8 100644
--- a/README.md
+++ b/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.
diff --git a/data/imprint.json b/data/imprint.json
index 38355be..c143aed 100644
--- a/data/imprint.json
+++ b/data/imprint.json
@@ -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."
}
}
diff --git a/src/app.tsx b/src/app.tsx
index e0787c6..96c9ec8 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -28,7 +28,6 @@ const GlobalStyle = createGlobalStyle`
/**
* Renders the entire app by calling individual components
- * @returns
*/
const App = () => {
diff --git a/src/components/app.tsx b/src/components/app.tsx
index fb61e13..8987287 100644
--- a/src/components/app.tsx
+++ b/src/components/app.tsx
@@ -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) => (
- {name}
+ {name}{displayURL}
diff --git a/src/components/appCategory.tsx b/src/components/appCategory.tsx
index 84edcd0..0e2e2e9 100644
--- a/src/components/appCategory.tsx
+++ b/src/components/appCategory.tsx
@@ -16,6 +16,10 @@ export interface IAppCategoryProps {
items: Array;
}
+/**
+ * Renders one app category
+ * @param {IAppCategoryProps} props - The props of the given category
+ */
export const AppCategory = ({ name, items }: IAppCategoryProps) => (
{name && {name}}
diff --git a/src/components/appList.tsx b/src/components/appList.tsx
index 770d1e6..b09529c 100644
--- a/src/components/appList.tsx
+++ b/src/components/appList.tsx
@@ -9,22 +9,24 @@ export interface IAppListProps {
apps: Array;
}
-const AppList = ({ categories, apps }: IAppListProps) => {
- return (
-
- Applications
- {categories &&
- categories.map(({ name, items }, idx) => (
-
- ))}
- {apps && (
-
- )}
-
- );
-};
+/**
+ * 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) => (
+
+ Applications
+ {categories &&
+ categories.map(({ name, items }, idx) => (
+
+ ))}
+ {apps && (
+
+ )}
+
+);
export default AppList;
diff --git a/src/components/bookmarkGroup.tsx b/src/components/bookmarkGroup.tsx
index 70a69c8..4491390 100644
--- a/src/components/bookmarkGroup.tsx
+++ b/src/components/bookmarkGroup.tsx
@@ -28,14 +28,15 @@ export interface IBookmarkProps {
}
export interface IBookmarkGroupProps {
- name: string;
+ groupName: string;
items: Array;
}
-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) => (
{groupName}
diff --git a/src/components/bookmarkList.tsx b/src/components/bookmarkList.tsx
index a69886d..3fea78d 100644
--- a/src/components/bookmarkList.tsx
+++ b/src/components/bookmarkList.tsx
@@ -1,20 +1,22 @@
import React from "react";
-
import { Headline, ListContainer, ItemList } from "./elements";
-
import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup";
interface IBookmarkListProps {
groups: Array;
}
+/**
+ * Renders a given list of categorized bookmarks
+ * @param {IBookmarkListProps} props - The props of the given bookmark list
+ */
const BookmarkList = ({ groups }: IBookmarkListProps) => {
return (
Bookmarks
- {groups.map(({ name, items }, idx) => (
-
+ {groups.map(({ groupName, items }, idx) => (
+
))}
diff --git a/src/components/elements.tsx b/src/components/elements.tsx
index 8cce902..060b217 100644
--- a/src/components/elements.tsx
+++ b/src/components/elements.tsx
@@ -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) => (
diff --git a/src/components/greeter.tsx b/src/components/greeter.tsx
index 49bce77..42eb593 100644
--- a/src/components/greeter.tsx
+++ b/src/components/greeter.tsx
@@ -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();
diff --git a/src/components/icon.tsx b/src/components/icon.tsx
index 9a1aff3..8c98bc6 100644
--- a/src/components/icon.tsx
+++ b/src/components/icon.tsx
@@ -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 {name};
};
diff --git a/src/components/imprint.tsx b/src/components/imprint.tsx
index 324696f..c942b3b 100644
--- a/src/components/imprint.tsx
+++ b/src/components/imprint.tsx
@@ -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) => (
- {field.text}
-);
-
interface IImprintComponentProps {
imprint: IImprintProps;
}
+/**
+ * Renders an imprint field
+ * @param {IImprintFieldComponentProps} props - The data for the field
+ */
+const ImprintField = ({ field }: IImprintFieldComponentProps) => (
+ {field.text}
+);
+
+/**
+ * Renders the imprint component
+ * @param {IImprintProps} props - The contents of the imprint
+ */
const Imprint = ({ imprint }: IImprintComponentProps) => (
<>
- About
+ About
- Imprint
+ Imprint (
{imprint.phone && }
{imprint.url && }
>
- Disclaimer
- Accountability for content
- 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).
-
- Accountability for links
-
- 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.
-
- Copyright
-
- 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}
diff --git a/src/components/modal.tsx b/src/components/modal.tsx
index 214965f..566dee3 100644
--- a/src/components/modal.tsx
+++ b/src/components/modal.tsx
@@ -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 = () => {
diff --git a/src/components/searchBar.tsx b/src/components/searchBar.tsx
index 82910d3..3e730e1 100644
--- a/src/components/searchBar.tsx
+++ b/src/components/searchBar.tsx
@@ -48,6 +48,10 @@ interface ISearchBarProps {
providers: Array | 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("");
let [buttonsHidden, setButtonsHidden] = useState(true);
diff --git a/src/components/settings.tsx b/src/components/settings.tsx
index 1917917..1160c16 100644
--- a/src/components/settings.tsx
+++ b/src/components/settings.tsx
@@ -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 | undefined;
}
+
+/**
+ * Handles the settings-modal
+ * @param {Array} themes - the list of themes a user can select between
+ * @param {Array} 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 (
{themes && (
-
-
-
Theme:
@@ -154,7 +150,6 @@ const Settings = ({ themes, providers }: ISettingsProps) => {
)}
{providers && (
-
Search Providers