This commit is contained in:
phntxx 2021-03-21 19:59:18 +01:00
parent 9fef36eae3
commit 8e3dd6e77d
19 changed files with 327 additions and 335 deletions

View file

@ -28,7 +28,6 @@ const GlobalStyle = createGlobalStyle`
/**
* Renders the entire app by calling individual components
* @returns
*/
const App = () => {

View file

@ -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>

View file

@ -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>}

View file

@ -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;

View file

@ -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>

View file

@ -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>

View file

@ -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} />

View file

@ -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();

View file

@ -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>;
};

View file

@ -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>

View file

@ -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 = () => {

View file

@ -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);

View file

@ -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>
);

View file

@ -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."
}
}

View file

@ -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
};
};

View file

@ -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") || "{}");
}