Feature Requests

This commit is contained in:
phntxx 2021-04-03 16:54:44 +02:00
parent a94f31ddd2
commit 746505294b
11 changed files with 272 additions and 104 deletions

50
data/greeter.json Normal file
View file

@ -0,0 +1,50 @@
{
"greeter": {
"months": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"days": [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
"greetings": [
{
"greeting": "Good night!",
"start": 0,
"end": 6
},
{
"greeting": "Good morning!",
"start": 6,
"end": 12
},
{
"greeting": "Good afternoon!",
"start": 12,
"end": 18
},
{
"greeting": "Good evening!",
"start": 18,
"end": 0
}
],
"dateformat": "%wd, %m %d%e %y"
}
}

View file

@ -30,5 +30,17 @@
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
} }
} }

View file

@ -1,4 +1,3 @@
import React from "react";
import { createGlobalStyle } from "styled-components"; import { createGlobalStyle } from "styled-components";
import SearchBar from "./components/searchBar"; import SearchBar from "./components/searchBar";
@ -31,7 +30,7 @@ const GlobalStyle = createGlobalStyle`
*/ */
const App = () => { const App = () => {
const { appData, bookmarkData, searchProviderData, themeData, imprintData } = useFetcher(); const { appData, bookmarkData, searchProviderData, themeData, imprintData, greeterData } = useFetcher();
return ( return (
<> <>
@ -44,8 +43,7 @@ const App = () => {
providers={searchProviderData?.providers} providers={searchProviderData?.providers}
/> />
)} )}
<Greeter data={greeterData.greeter} />
<Greeter />
{!appData.error && ( {!appData.error && (
<AppList apps={appData.apps} categories={appData.categories} /> <AppList apps={appData.apps} categories={appData.categories} />
)} )}

View file

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } from "react";
import Icon from "./icon"; import Icon from "./icon";
import styled from "styled-components"; import styled from "styled-components";
import selectedTheme from "../lib/theme"; import selectedTheme from "../lib/theme";
@ -48,20 +48,31 @@ export interface IAppProps {
icon: string; icon: string;
url: string; url: string;
displayURL: string; displayURL: string;
newTab?: boolean;
} }
/** /**
* Renders a single app shortcut * Renders a single app shortcut
* @param {IAppProps} props - The props of the given app * @param {IAppProps} props - The props of the given app
*/ */
export const App = ({ name, icon, url, displayURL }: IAppProps) => ( export const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
useEffect(() => { console.log(newTab) }, [newTab])
return (
<AppContainer> <AppContainer>
<IconContainer> <IconContainer>
<Icon name={icon} /> <Icon name={icon} />
</IconContainer> </IconContainer>
<DetailsContainer> <DetailsContainer>
<AppLink href={url}>{name}</AppLink>
{
(newTab !== undefined && newTab) ?
<AppLink href={url} target="_blank" rel="noopener noreferrer">{name}</AppLink> : <AppLink href={url}>{name}</AppLink>
}
<AppDescription>{displayURL}</AppDescription> <AppDescription>{displayURL}</AppDescription>
</DetailsContainer> </DetailsContainer>
</AppContainer> </AppContainer>
); );
}

View file

@ -31,6 +31,7 @@ export const AppCategory = ({ name, items }: IAppCategoryProps) => (
icon={app.icon} icon={app.icon}
url={app.url} url={app.url}
displayURL={app.displayURL} displayURL={app.displayURL}
newTab={app.newTab}
/> />
</Item> </Item>
))} ))}

View file

@ -1,4 +1,3 @@
import React from "react";
import { AppCategory, IAppCategoryProps } from "./appCategory"; import { AppCategory, IAppCategoryProps } from "./appCategory";
import { IAppProps } from "./app"; import { IAppProps } from "./app";

View file

@ -1,6 +1,4 @@
import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import selectedTheme from "../lib/theme"; import selectedTheme from "../lib/theme";
const GreeterContainer = styled.div` const GreeterContainer = styled.div`
@ -22,48 +20,45 @@ const DateText = styled.h3`
color: ${selectedTheme.accentColor}; color: ${selectedTheme.accentColor};
`; `;
const monthNames = [ export interface IGreeterProps {
"January", months: Array<string>;
"February", days: Array<string>;
"March", greetings: Array<IGreetingProps>;
"April", dateformat: string;
"May", }
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const weekDayNames = [ interface IGreetingProps {
"Sunday", greeting: string;
"Monday", start: number;
"Tuesday", end: number;
"Wednesday", }
"Thursday",
"Friday", interface IGreeterComponentProps {
"Saturday", data: IGreeterProps;
]; }
/**
*
* @param a the number that's supposed to be checked
* @param b the minimum
* @param c the maximum
*/
const isBetween = (a: number, b: number, c: number): boolean => (a > b && a < c)
/** /**
* Returns a greeting based on the current time * Returns a greeting based on the current time
* @returns {string} - A greeting * @returns {string} - A greeting
*/ */
const getGreeting = () => { const getGreeting = (greetings: Array<IGreetingProps>): string => {
switch (Math.floor(new Date().getHours() / 6)) {
case 0: let hours = Math.floor(new Date().getHours())
return "Good night!"; let result = "";
case 1:
return "Good morning!"; greetings.forEach(greeting => {
case 2: if (isBetween(hours, greeting.start, greeting.end)) result = greeting.greeting;
return "Good afternoon!"; })
case 3:
return "Good evening!"; return result;
default:
break;
}
}; };
/** /**
@ -89,30 +84,28 @@ const getExtension = (day: number) => {
/** /**
* Generates the current date * Generates the current date
* @param {string} format - The format of the date string
* @returns {string} - The current date as a string * @returns {string} - The current date as a string
*/ */
const getDateString = () => { const getDateString = (weekdays: Array<string>, months: Array<string>, format: string) => {
let currentDate = new Date(); let currentDate = new Date();
return ( let weekday = weekdays[currentDate.getUTCDay()];
weekDayNames[currentDate.getUTCDay()] + let day = currentDate.getDate();
", " + let month = months[currentDate.getUTCMonth()];
monthNames[currentDate.getUTCMonth()] + let extension = getExtension(day);
" " + let year = currentDate.getFullYear();
currentDate.getDate() +
getExtension(currentDate.getDate()) + return format.replace("%wd", weekday).replace("%d", day.toString()).replace("%e", extension).replace("%m", month).replace("%y", year.toString());
" " +
currentDate.getFullYear()
);
}; };
/** /**
* Renders the Greeter * Renders the Greeter
*/ */
const Greeter = () => ( const Greeter = ({ data }: IGreeterComponentProps) => (
<GreeterContainer> <GreeterContainer>
<DateText>{getDateString()}</DateText> <DateText>{getDateString(data.days, data.months, data.dateformat)}</DateText>
<GreetText>{getGreeting()}</GreetText> <GreetText>{getGreeting(data.greetings)}</GreetText>
</GreeterContainer> </GreeterContainer>
); );

View file

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import Select, { Styles, ValueType } from "react-select"; import Select, { ValueType } from "react-select";
import { ISearchProviderProps } from "./searchBar"; import { ISearchProviderProps } from "./searchBar";
import selectedTheme, { setTheme, IThemeProps } from "../lib/theme"; import selectedTheme, { setTheme, IThemeProps } from "../lib/theme";
@ -9,17 +9,6 @@ import { Button, SubHeadline } from "./elements";
import Modal from "./modal"; import Modal from "./modal";
interface IHoverProps {
color?: string;
backgroundColor?: string;
border?: string;
borderColor?: string;
}
interface IPseudoProps extends React.CSSProperties {
"&:hover": IHoverProps
}
const FormContainer = styled.div` const FormContainer = styled.div`
display: grid; display: grid;
grid-template-columns: auto auto auto; grid-template-columns: auto auto auto;
@ -55,22 +44,12 @@ const SectionHeadline = styled(SubHeadline)`
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
`; `;
const SelectorStyle: Partial<Styles<IThemeProps, false>> = { const SelectorStyle: any = {
indicatorSeparator: () => ({ container: (base: any): any => ({
display: "none",
}),
container: (base: React.CSSProperties): React.CSSProperties => ({
...base, ...base,
margin: "0 2px", margin: "0 2px",
}), }),
dropdownIndicator: (base: React.CSSProperties): IPseudoProps => ({ control: (base: any): any => ({
...base,
color: selectedTheme.mainColor,
"&:hover": {
color: selectedTheme.mainColor
}
}),
control: (base: React.CSSProperties): IPseudoProps => ({
...base, ...base,
fontWeight: 500, fontWeight: 500,
color: selectedTheme.mainColor, color: selectedTheme.mainColor,
@ -86,7 +65,17 @@ const SelectorStyle: Partial<Styles<IThemeProps, false>> = {
borderColor: selectedTheme.mainColor borderColor: selectedTheme.mainColor
}, },
}), }),
menu: (base: React.CSSProperties): React.CSSProperties => ({ dropdownIndicator: (base: any): any => ({
...base,
color: selectedTheme.mainColor,
"&:hover": {
color: selectedTheme.mainColor
}
}),
indicatorSeparator: () => ({
display: "none",
}),
menu: (base: any): any => ({
...base, ...base,
backgroundColor: selectedTheme.backgroundColor, backgroundColor: selectedTheme.backgroundColor,
border: "1px solid " + selectedTheme.mainColor, border: "1px solid " + selectedTheme.mainColor,
@ -94,7 +83,7 @@ const SelectorStyle: Partial<Styles<IThemeProps, false>> = {
boxShadow: "none", boxShadow: "none",
margin: "4px 0" margin: "4px 0"
}), }),
option: (base: React.CSSProperties): IPseudoProps => ({ option: (base: any): any => ({
...base, ...base,
fontWeight: 500, fontWeight: 500,
color: selectedTheme.mainColor, color: selectedTheme.mainColor,
@ -107,7 +96,7 @@ const SelectorStyle: Partial<Styles<IThemeProps, false>> = {
color: selectedTheme.backgroundColor, color: selectedTheme.backgroundColor,
}, },
}), }),
singleValue: (base: React.CSSProperties): React.CSSProperties => ({ singleValue: (base: any): any => ({
...base, ...base,
color: selectedTheme.mainColor, color: selectedTheme.mainColor,
}), }),

50
src/data/greeter.json Normal file
View file

@ -0,0 +1,50 @@
{
"greeter": {
"months": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"days": [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
"greetings": [
{
"greeting": "Good night!",
"start": 0,
"end": 6
},
{
"greeting": "Good morning!",
"start": 6,
"end": 12
},
{
"greeting": "Good afternoon!",
"start": 12,
"end": 18
},
{
"greeting": "Good evening!",
"start": 18,
"end": 0
}
],
"dateformat": "%wd, %m %d%e %y"
}
}

View file

@ -6,6 +6,7 @@ import { IAppCategoryProps } from "../components/appCategory";
import { IAppProps } from "../components/app"; import { IAppProps } from "../components/app";
import { IThemeProps } from "./theme"; import { IThemeProps } from "./theme";
import { IImprintProps } from "../components/imprint"; import { IImprintProps } from "../components/imprint";
import { IGreeterProps } from "../components/greeter";
const errorMessage = "Failed to load data."; const errorMessage = "Failed to load data.";
const inProduction = process.env.NODE_ENV === "production"; const inProduction = process.env.NODE_ENV === "production";
@ -47,6 +48,11 @@ export interface IImprintDataProps {
error: string | boolean; error: string | boolean;
} }
export interface IGreeterDataProps {
greeter: IGreeterProps;
error: string | boolean;
}
/** /**
* Default values for the respective state variables * Default values for the respective state variables
*/ */
@ -79,6 +85,57 @@ const defaults = {
}, },
error: false, error: false,
}, },
greeter: {
greeter: {
months: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
days: [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
greetings: [
{
greeting: "Good night!",
start: 0,
end: 6
},
{
greeting: "Good morning!",
start: 6,
end: 12
},
{
greeting: "Good afternoon!",
start: 12,
end: 18
},
{
greeting: "Good evening!",
start: 18,
end: 0
}
],
dateformat: "%wd, %m %d%e %y"
},
error: false,
}
}; };
/** /**
@ -98,6 +155,8 @@ const handleError = (status: string, error: Error) => {
return { ...defaults.theme, error: error.message } return { ...defaults.theme, error: error.message }
case "imprint": case "imprint":
return { ...defaults.imprint, error: error.message } return { ...defaults.imprint, error: error.message }
case "greeter":
return { ...defaults.greeter, error: error.message }
default: default:
break; break;
} }
@ -112,6 +171,7 @@ const fetchProduction = Promise.all([
fetch("/data/search.json").then(handleResponse).catch((error: Error) => handleError("searchProvider", error)), fetch("/data/search.json").then(handleResponse).catch((error: Error) => handleError("searchProvider", error)),
fetch("/data/themes.json").then(handleResponse).catch((error: Error) => handleError("theme", error)), fetch("/data/themes.json").then(handleResponse).catch((error: Error) => handleError("theme", error)),
fetch("/data/imprint.json").then(handleResponse).catch((error: Error) => handleError("imprint", error)), fetch("/data/imprint.json").then(handleResponse).catch((error: Error) => handleError("imprint", error)),
fetch("/data/greeter.json").then(handleResponse).catch((error: Error) => handleError("greeter", error))
]); ]);
/** /**
@ -123,6 +183,7 @@ const fetchDevelopment = Promise.all([
import("../data/search.json"), import("../data/search.json"),
import("../data/themes.json"), import("../data/themes.json"),
import("../data/imprint.json"), import("../data/imprint.json"),
import("../data/greeter.json")
]); ]);
/** /**
@ -146,14 +207,17 @@ export const useFetcher = () => {
defaults.imprint defaults.imprint
); );
const [greeterData, setGreeterData] = useState<IGreeterDataProps>(defaults.greeter);
const callback = useCallback(() => { const callback = useCallback(() => {
(inProduction ? fetchProduction : fetchDevelopment).then( (inProduction ? fetchProduction : fetchDevelopment).then(
([appData, bookmarkData, searchData, themeData, imprintData]: [IAppDataProps, IBookmarkDataProps, ISearchProviderDataProps, IThemeDataProps, IImprintDataProps]) => { ([appData, bookmarkData, searchData, themeData, imprintData, greeterData]: [IAppDataProps, IBookmarkDataProps, ISearchProviderDataProps, IThemeDataProps, IImprintDataProps, IGreeterDataProps]) => {
(appData.error) ? setAppData(appData) : setAppData({ ...appData, error: false }); setAppData((appData.error) ? appData : { ...appData, error: false });
(bookmarkData.error) ? setBookmarkData(bookmarkData) : setBookmarkData({ ...bookmarkData, error: false }); setBookmarkData((bookmarkData.error) ? bookmarkData : { ...bookmarkData, error: false });
(searchData.error) ? setSearchProviderData(searchData) : setSearchProviderData({ ...searchData, error: false }); setSearchProviderData((searchData.error) ? searchData : { ...searchData, error: false });
(themeData.error) ? setThemeData(themeData) : setThemeData({ ...themeData, error: false }); setThemeData((themeData.error) ? themeData : { ...themeData, error: false });
(imprintData.error) ? setImprintData(imprintData) : setImprintData({ ...imprintData, error: false }); setImprintData((imprintData.error) ? imprintData : { ...imprintData, error: false });
setGreeterData((greeterData.error) ? greeterData : { ...greeterData, error: false });
} }
); );
}, []); }, []);
@ -166,6 +230,7 @@ export const useFetcher = () => {
searchProviderData, searchProviderData,
themeData, themeData,
imprintData, imprintData,
greeterData,
callback callback
}; };
}; };

View file

@ -17,7 +17,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react", "jsx": "react-jsx",
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": [ "include": [