Replace useFetcher with useFetch.
This commit is contained in:
parent
d4f593c4ce
commit
942cba97da
15 changed files with 292 additions and 740 deletions
32
src/app.tsx
32
src/app.tsx
|
@ -8,7 +8,7 @@ import Settings from "./components/settings";
|
||||||
import Imprint from "./components/imprint";
|
import Imprint from "./components/imprint";
|
||||||
|
|
||||||
import { IThemeProps, getTheme, setScheme } from "./lib/useTheme";
|
import { IThemeProps, getTheme, setScheme } from "./lib/useTheme";
|
||||||
import useFetcher from "./lib/fetcher";
|
import useFetch from "./lib/useFetch";
|
||||||
import useMediaQuery from "./lib/useMediaQuery";
|
import useMediaQuery from "./lib/useMediaQuery";
|
||||||
|
|
||||||
export const GlobalStyle = createGlobalStyle<{ theme: IThemeProps }>`
|
export const GlobalStyle = createGlobalStyle<{ theme: IThemeProps }>`
|
||||||
|
@ -33,37 +33,29 @@ const App = () => {
|
||||||
const {
|
const {
|
||||||
appData,
|
appData,
|
||||||
bookmarkData,
|
bookmarkData,
|
||||||
searchProviderData,
|
searchData,
|
||||||
themeData,
|
themeData,
|
||||||
imprintData,
|
imprintData,
|
||||||
greeterData,
|
greeterData,
|
||||||
} = useFetcher();
|
} = useFetch();
|
||||||
|
|
||||||
const theme = getTheme();
|
const theme = getTheme();
|
||||||
let isDark = useMediaQuery("(prefers-color-scheme: dark)");
|
let isDark = useMediaQuery("(prefers-color-scheme: dark)");
|
||||||
if (isDark) {
|
setScheme(isDark ? "dark-theme" : "light-theme");
|
||||||
setScheme("dark-theme");
|
|
||||||
} else {
|
|
||||||
setScheme("light-theme");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<div>
|
<div>
|
||||||
<SearchBar search={searchProviderData?.search} />
|
<SearchBar search={searchData.response} />
|
||||||
{(!themeData.error || !searchProviderData.error) && (
|
<Settings themes={themeData.response} search={searchData.response} />
|
||||||
<Settings
|
<Greeter greeter={greeterData.response} />
|
||||||
themes={themeData?.themes}
|
<AppList
|
||||||
search={searchProviderData?.search}
|
apps={appData.response?.apps}
|
||||||
|
categories={appData.response?.categories}
|
||||||
/>
|
/>
|
||||||
)}
|
<BookmarkList groups={bookmarkData.response?.groups} />
|
||||||
<Greeter data={greeterData.greeter} />
|
<Imprint imprint={imprintData.response} />
|
||||||
{!appData.error && (
|
|
||||||
<AppList apps={appData.apps} categories={appData.categories} />
|
|
||||||
)}
|
|
||||||
{!bookmarkData.error && <BookmarkList groups={bookmarkData.groups} />}
|
|
||||||
{!imprintData.error && <Imprint imprint={imprintData.imprint} />}
|
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -38,7 +38,7 @@ export interface IBookmarkGroupProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBookmarkListProps {
|
export interface IBookmarkListProps {
|
||||||
groups: Array<IBookmarkGroupProps>;
|
groups?: Array<IBookmarkGroupProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Bookmark = ({ name, url, newTab }: IBookmarkProps) => {
|
export const Bookmark = ({ name, url, newTab }: IBookmarkProps) => {
|
||||||
|
@ -83,15 +83,23 @@ export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
||||||
* @param {IBookmarkListProps} props props of the given bookmark list
|
* @param {IBookmarkListProps} props props of the given bookmark list
|
||||||
* @returns {React.ReactNode} the bookmark list component
|
* @returns {React.ReactNode} the bookmark list component
|
||||||
*/
|
*/
|
||||||
const BookmarkList = ({ groups }: IBookmarkListProps) => (
|
const BookmarkList = ({ groups }: IBookmarkListProps) => {
|
||||||
|
if (groups === undefined || groups.length <= 0) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
<Headline>Bookmarks</Headline>
|
<Headline>Bookmarks</Headline>
|
||||||
<ItemList>
|
<ItemList>
|
||||||
{groups.map(({ name, items }, index) => (
|
{groups.map(({ name, items }, index) => (
|
||||||
<BookmarkGroup key={[name, index].join("")} name={name} items={items} />
|
<BookmarkGroup
|
||||||
|
key={[name, index].join("")}
|
||||||
|
name={name}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ItemList>
|
</ItemList>
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default BookmarkList;
|
export default BookmarkList;
|
||||||
|
|
|
@ -20,7 +20,11 @@ const DateText = styled.h3`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface IGreeterComponentProps {
|
export interface IGreeterComponentProps {
|
||||||
data: IGreeterProps;
|
greeter?: IGreeterDataProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGreeterDataProps {
|
||||||
|
greeter: IGreeterProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGreeterProps {
|
export interface IGreeterProps {
|
||||||
|
@ -114,13 +118,21 @@ export const getDateString = (
|
||||||
* @param {IGreeterComponentProps} data required greeter data
|
* @param {IGreeterComponentProps} data required greeter data
|
||||||
* @returns {React.ReactNode} the greeter
|
* @returns {React.ReactNode} the greeter
|
||||||
*/
|
*/
|
||||||
const Greeter = ({ data }: IGreeterComponentProps) => (
|
const Greeter = ({ greeter }: IGreeterComponentProps) => {
|
||||||
|
if (greeter === undefined) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
<GreeterContainer>
|
<GreeterContainer>
|
||||||
<DateText>
|
<DateText>
|
||||||
{getDateString(data.days, data.months, data.dateformat)}
|
{getDateString(
|
||||||
|
greeter.greeter.days,
|
||||||
|
greeter.greeter.months,
|
||||||
|
greeter.greeter.dateformat,
|
||||||
|
)}
|
||||||
</DateText>
|
</DateText>
|
||||||
<GreetText>{getGreeting(data.greetings)}</GreetText>
|
<GreetText>{getGreeting(greeter.greeter.greetings)}</GreetText>
|
||||||
</GreeterContainer>
|
</GreeterContainer>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Greeter;
|
export default Greeter;
|
||||||
|
|
|
@ -40,7 +40,7 @@ export interface IImprintProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IImprintComponentProps {
|
export interface IImprintComponentProps {
|
||||||
imprint: IImprintProps;
|
imprint?: IImprintProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IImprintFieldComponentProps {
|
interface IImprintFieldComponentProps {
|
||||||
|
@ -72,7 +72,10 @@ export const onClose = () => {
|
||||||
* @param {IImprintProps} props contents of the imprint
|
* @param {IImprintProps} props contents of the imprint
|
||||||
* @returns {React.ReactNode} the imprint node
|
* @returns {React.ReactNode} the imprint node
|
||||||
*/
|
*/
|
||||||
const Imprint = ({ imprint }: IImprintComponentProps) => (
|
const Imprint = ({ imprint }: IImprintComponentProps) => {
|
||||||
|
if (imprint === undefined) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
<Headline>About</Headline>
|
<Headline>About</Headline>
|
||||||
|
@ -107,6 +110,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
||||||
</ItemList>
|
</ItemList>
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Imprint;
|
export default Imprint;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export interface ISearchProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISearchBarProps {
|
interface ISearchBarProps {
|
||||||
search: ISearchProps;
|
search?: ISearchProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleQueryWithProvider = (
|
export const handleQueryWithProvider = (
|
||||||
|
@ -85,6 +85,8 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
||||||
|
|
||||||
useEffect(() => setButtonsHidden(input === ""), [input]);
|
useEffect(() => setButtonsHidden(input === ""), [input]);
|
||||||
|
|
||||||
|
if (search === undefined) return <></>;
|
||||||
|
|
||||||
const handleSearchQuery = (e: React.FormEvent) => {
|
const handleSearchQuery = (e: React.FormEvent) => {
|
||||||
var query: string = input || "";
|
var query: string = input || "";
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,7 @@ const Select = ({
|
||||||
className={className}
|
className={className}
|
||||||
value={selected}
|
value={selected}
|
||||||
>
|
>
|
||||||
{items.map(({ label, value }, index) => {
|
{items.map(({ label, value }, index) => (
|
||||||
if (label === current) {
|
|
||||||
return (
|
|
||||||
<option
|
<option
|
||||||
data-testid={"option-" + (testId ? `${testId}-` : "") + index}
|
data-testid={"option-" + (testId ? `${testId}-` : "") + index}
|
||||||
key={[label, index].join("")}
|
key={[label, index].join("")}
|
||||||
|
@ -56,19 +54,7 @@ const Select = ({
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
);
|
))}
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<option
|
|
||||||
data-testid={"option-" + (testId ? `${testId}-` : "") + index}
|
|
||||||
key={[label, index].join("")}
|
|
||||||
value={value.toString()}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,12 @@ import styled from "styled-components";
|
||||||
import Select from "./select";
|
import Select from "./select";
|
||||||
|
|
||||||
import { ISearchProps } from "./searchBar";
|
import { ISearchProps } from "./searchBar";
|
||||||
import { setTheme, IThemeProps, getTheme } from "../lib/useTheme";
|
import {
|
||||||
|
setTheme,
|
||||||
|
IThemeProps,
|
||||||
|
IThemeDataProps,
|
||||||
|
getTheme,
|
||||||
|
} from "../lib/useTheme";
|
||||||
import { Button, SubHeadline } from "./elements";
|
import { Button, SubHeadline } from "./elements";
|
||||||
|
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
|
@ -88,13 +93,13 @@ const ContentContainer = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface ISettingsProps {
|
interface ISettingsProps {
|
||||||
themes: Array<IThemeProps> | undefined;
|
themes?: IThemeDataProps;
|
||||||
search: ISearchProps | undefined;
|
search?: ISearchProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the settings-modal
|
* Handles the settings-modal
|
||||||
* @param {Array<IThemeProps>} themes - the list of themes a user can select between
|
* @param {IThemeDataProps} themes - the list of themes a user can select between
|
||||||
* @param {ISearchProps} search - the list of search providers
|
* @param {ISearchProps} search - the list of search providers
|
||||||
*/
|
*/
|
||||||
const Settings = ({ themes, search }: ISettingsProps) => {
|
const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
|
@ -104,18 +109,19 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
const currentLightTheme = getTheme("light").value;
|
const currentLightTheme = getTheme("light").value;
|
||||||
const currentDarkTheme = getTheme("dark").value;
|
const currentDarkTheme = getTheme("dark").value;
|
||||||
|
|
||||||
if (themes || search) {
|
if (themes === undefined && search === undefined) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal element="icon" icon="settings" title="Settings">
|
<Modal element="icon" icon="settings" title="Settings">
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
{themes && (
|
{themes !== undefined && (
|
||||||
<Section>
|
<Section>
|
||||||
<SectionHeadline>Theme</SectionHeadline>
|
<SectionHeadline>Theme</SectionHeadline>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<div>
|
<div>
|
||||||
<ThemeHeader>Light</ThemeHeader>
|
<ThemeHeader>Light</ThemeHeader>
|
||||||
<ThemeSelect
|
<ThemeSelect
|
||||||
items={themes}
|
items={themes.themes}
|
||||||
onChange={(theme: IThemeProps) => setNewLightTheme(theme)}
|
onChange={(theme: IThemeProps) => setNewLightTheme(theme)}
|
||||||
current={currentLightTheme}
|
current={currentLightTheme}
|
||||||
testId="light"
|
testId="light"
|
||||||
|
@ -124,7 +130,7 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
<div>
|
<div>
|
||||||
<ThemeHeader>Dark</ThemeHeader>
|
<ThemeHeader>Dark</ThemeHeader>
|
||||||
<ThemeSelect
|
<ThemeSelect
|
||||||
items={themes}
|
items={themes.themes}
|
||||||
onChange={(theme: IThemeProps) => setNewDarkTheme(theme)}
|
onChange={(theme: IThemeProps) => setNewDarkTheme(theme)}
|
||||||
current={currentDarkTheme}
|
current={currentDarkTheme}
|
||||||
testId="dark"
|
testId="dark"
|
||||||
|
@ -152,7 +158,7 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
{search && (
|
{search !== undefined && (
|
||||||
<Section>
|
<Section>
|
||||||
<SectionHeadline>Search Providers</SectionHeadline>
|
<SectionHeadline>Search Providers</SectionHeadline>
|
||||||
<>
|
<>
|
||||||
|
@ -182,9 +188,6 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|
31
src/lib/fetch.d.ts
vendored
31
src/lib/fetch.d.ts
vendored
|
@ -1,31 +0,0 @@
|
||||||
import { ISearchProps } from "../components/searchBar";
|
|
||||||
import { IBookmarkGroupProps } from "../components/bookmarks";
|
|
||||||
import { IAppProps, IAppCategoryProps } from "../components/apps";
|
|
||||||
import { IThemeProps } from "./theme";
|
|
||||||
import { IImprintProps } from "../components/imprint";
|
|
||||||
import { IGreeterProps } from "../components/greeter";
|
|
||||||
|
|
||||||
declare module "../data/apps.json" {
|
|
||||||
export const categories: IAppCategoryProps[];
|
|
||||||
export const apps: IAppProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/search.json" {
|
|
||||||
export const search: ISearchProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/bookmarks.json" {
|
|
||||||
export const groups: IBookmarkGroupProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/themes.json" {
|
|
||||||
export const themes: IThemeProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/imprint.json" {
|
|
||||||
export const imprint: IImprintProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/greeter.json" {
|
|
||||||
export const greeter: IGreeterProps;
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
|
||||||
import { IAppListProps } from "../components/apps";
|
|
||||||
import { IThemeProps } from "./useTheme";
|
|
||||||
import { IBookmarkListProps } from "../components/bookmarks";
|
|
||||||
import { ISearchProps } from "../components/searchBar";
|
|
||||||
import { IImprintProps } from "../components/imprint";
|
|
||||||
import { IGreeterProps } from "../components/greeter";
|
|
||||||
|
|
||||||
const inProduction = process.env.NODE_ENV === "production";
|
|
||||||
|
|
||||||
interface IFetchItemProps {
|
|
||||||
url: string;
|
|
||||||
setHook?: React.Dispatch<React.SetStateAction<any>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IFetchListProps {
|
|
||||||
app: IFetchItemProps;
|
|
||||||
bookmarks: IFetchItemProps;
|
|
||||||
greeter: IFetchItemProps;
|
|
||||||
imprint: IFetchItemProps;
|
|
||||||
search: IFetchItemProps;
|
|
||||||
themes: IFetchItemProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fetchList: IFetchListProps = {
|
|
||||||
app: { url: "/data/app.json" },
|
|
||||||
bookmarks: { url: "/data/bookmarks.json" },
|
|
||||||
greeter: { url: "/data/greeter.json" },
|
|
||||||
imprint: { url: "/data/imprint.json" },
|
|
||||||
search: { url: "/data/search.json" },
|
|
||||||
themes: { url: "/data/themes.json" },
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleResponse = (response: Response, type: string) => {
|
|
||||||
if (response.ok) return response.json();
|
|
||||||
throw new Error("Error fetching " + type + " data");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = (error: Error) => {
|
|
||||||
console.error(error.message);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchURL = (url: string, type: string) => {
|
|
||||||
const response = inProduction ? fetch(url) : import(".." + url);
|
|
||||||
|
|
||||||
return response
|
|
||||||
.then((response: Response) => handleResponse(response, type))
|
|
||||||
.catch(handleError);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useFetch = () => {
|
|
||||||
const [appData, setAppData] = useState<IAppListProps>();
|
|
||||||
fetchList.app.setHook = setAppData;
|
|
||||||
|
|
||||||
const [bookmarkData, setBookmarkData] = useState<IBookmarkListProps>();
|
|
||||||
fetchList.bookmarks.setHook = setBookmarkData;
|
|
||||||
|
|
||||||
const [greeterData, setGreeterData] = useState<IGreeterProps>();
|
|
||||||
fetchList.greeter.setHook = setGreeterData;
|
|
||||||
|
|
||||||
const [imprintData, setImprintData] = useState<IImprintProps>();
|
|
||||||
fetchList.imprint.setHook = setImprintData;
|
|
||||||
|
|
||||||
const [searchData, setSearchData] = useState<ISearchProps>();
|
|
||||||
fetchList.search.setHook = setSearchData;
|
|
||||||
|
|
||||||
const [themeData, setThemeData] = useState<Array<IThemeProps>>();
|
|
||||||
fetchList.themes.setHook = setThemeData;
|
|
||||||
|
|
||||||
const callback = useCallback(() => {
|
|
||||||
Object.entries(fetchList).forEach(([key, val]) => {
|
|
||||||
fetchURL(val.url, key).then((data) => {
|
|
||||||
val.setHook(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => callback(), [callback]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
appData,
|
|
||||||
bookmarkData,
|
|
||||||
greeterData,
|
|
||||||
imprintData,
|
|
||||||
searchData,
|
|
||||||
themeData,
|
|
||||||
callback,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFetch;
|
|
32
src/lib/fetcher.d.ts
vendored
32
src/lib/fetcher.d.ts
vendored
|
@ -1,32 +0,0 @@
|
||||||
import { ISearchProps } from "../components/searchBar";
|
|
||||||
import { IBookmarkGroupProps } from "../components/bookmarks";
|
|
||||||
import { IAppCategoryProps } from "../components/appCategory";
|
|
||||||
import { IAppProps } from "../components/app";
|
|
||||||
import { IThemeProps } from "./theme";
|
|
||||||
import { IImprintProps } from "../components/imprint";
|
|
||||||
import { IGreeterProps } from "../components/greeter";
|
|
||||||
|
|
||||||
declare module "../data/apps.json" {
|
|
||||||
export const categories: IAppCategoryProps[];
|
|
||||||
export const apps: IAppProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/search.json" {
|
|
||||||
export const search: ISearchProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/bookmarks.json" {
|
|
||||||
export const groups: IBookmarkGroupProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/themes.json" {
|
|
||||||
export const themes: IThemeProps[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/imprint.json" {
|
|
||||||
export const imprint: IImprintProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "../data/greeter.json" {
|
|
||||||
export const greeter: IGreeterProps;
|
|
||||||
}
|
|
|
@ -1,278 +0,0 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import { ISearchProps } from "../components/searchBar";
|
|
||||||
import { IBookmarkGroupProps } from "../components/bookmarks";
|
|
||||||
import { IAppCategoryProps } from "../components/appCategory";
|
|
||||||
import { IAppProps } from "../components/app";
|
|
||||||
import { IThemeProps } from "./useTheme";
|
|
||||||
import { IImprintProps } from "../components/imprint";
|
|
||||||
import { IGreeterProps } from "../components/greeter";
|
|
||||||
|
|
||||||
const errorMessage = "Failed to load data.";
|
|
||||||
const inProduction = process.env.NODE_ENV === "production";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
export const handleResponse = (response: Response) => {
|
|
||||||
if (response.ok) return response.json();
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ISearchDataProps {
|
|
||||||
search: ISearchProps;
|
|
||||||
error: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBookmarkDataProps {
|
|
||||||
groups: Array<IBookmarkGroupProps>;
|
|
||||||
error: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppDataProps {
|
|
||||||
categories: Array<IAppCategoryProps>;
|
|
||||||
apps: Array<IAppProps>;
|
|
||||||
error: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IThemeDataProps {
|
|
||||||
themes: Array<IThemeProps>;
|
|
||||||
error: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IImprintDataProps {
|
|
||||||
imprint: IImprintProps;
|
|
||||||
error: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGreeterDataProps {
|
|
||||||
greeter: IGreeterProps;
|
|
||||||
error: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default values for the respective state variables
|
|
||||||
*/
|
|
||||||
export const defaults = {
|
|
||||||
app: {
|
|
||||||
categories: [],
|
|
||||||
apps: [],
|
|
||||||
error: false,
|
|
||||||
},
|
|
||||||
bookmark: {
|
|
||||||
groups: [],
|
|
||||||
error: false,
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
search: {
|
|
||||||
placeholder: "",
|
|
||||||
defaultProvider: "https://google.com/search?q=",
|
|
||||||
providers: [],
|
|
||||||
},
|
|
||||||
error: false,
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
themes: [],
|
|
||||||
error: false,
|
|
||||||
},
|
|
||||||
imprint: {
|
|
||||||
imprint: {
|
|
||||||
name: { text: "", link: "" },
|
|
||||||
address: { text: "", link: "" },
|
|
||||||
phone: { text: "", link: "" },
|
|
||||||
email: { text: "", link: "" },
|
|
||||||
url: { text: "", link: "" },
|
|
||||||
text: "",
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles fetch errors by returning the error message.
|
|
||||||
* @param {string} type - The type of fetch request that threw an error
|
|
||||||
* @param {Error} error - The error itself
|
|
||||||
*/
|
|
||||||
export const handleError = (status: string, error: Error) => {
|
|
||||||
switch (status) {
|
|
||||||
case "apps":
|
|
||||||
return { ...defaults.app, error: error.message };
|
|
||||||
case "bookmark":
|
|
||||||
return { ...defaults.bookmark, error: error.message };
|
|
||||||
case "searchProvider":
|
|
||||||
return { ...defaults.search, error: error.message };
|
|
||||||
case "theme":
|
|
||||||
return { ...defaults.theme, error: error.message };
|
|
||||||
case "imprint":
|
|
||||||
return { ...defaults.imprint, error: error.message };
|
|
||||||
case "greeter":
|
|
||||||
return { ...defaults.greeter, error: error.message };
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all of the data by doing fetch requests (only available in production)
|
|
||||||
*/
|
|
||||||
export const fetchProduction = Promise.all([
|
|
||||||
fetch("/data/apps.json")
|
|
||||||
.then(handleResponse)
|
|
||||||
.catch((error: Error) => handleError("apps", error)),
|
|
||||||
fetch("/data/bookmarks.json")
|
|
||||||
.then(handleResponse)
|
|
||||||
.catch((error: Error) => handleError("bookmark", 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/imprint.json")
|
|
||||||
.then(handleResponse)
|
|
||||||
.catch((error: Error) => handleError("imprint", error)),
|
|
||||||
fetch("/data/greeter.json")
|
|
||||||
.then(handleResponse)
|
|
||||||
.catch((error: Error) => handleError("greeter", error)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all of the data by importing it (only available in development)
|
|
||||||
*/
|
|
||||||
export const fetchDevelopment = Promise.all([
|
|
||||||
import("../data/apps.json"),
|
|
||||||
import("../data/bookmarks.json"),
|
|
||||||
import("../data/search.json"),
|
|
||||||
import("../data/themes.json"),
|
|
||||||
import("../data/imprint.json"),
|
|
||||||
import("../data/greeter.json"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches app, bookmark, search, theme and imprint data and returns it.
|
|
||||||
*/
|
|
||||||
export const useFetcher = () => {
|
|
||||||
const [appData, setAppData] = useState<IAppDataProps>(defaults.app);
|
|
||||||
|
|
||||||
const [bookmarkData, setBookmarkData] = useState<IBookmarkDataProps>(
|
|
||||||
defaults.bookmark,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [searchProviderData, setSearchProviderData] =
|
|
||||||
useState<ISearchDataProps>(defaults.search);
|
|
||||||
|
|
||||||
const [themeData, setThemeData] = useState<IThemeDataProps>(defaults.theme);
|
|
||||||
|
|
||||||
const [imprintData, setImprintData] = useState<IImprintDataProps>(
|
|
||||||
defaults.imprint,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [greeterData, setGreeterData] = useState<IGreeterDataProps>(
|
|
||||||
defaults.greeter,
|
|
||||||
);
|
|
||||||
|
|
||||||
const callback = useCallback(() => {
|
|
||||||
(inProduction ? fetchProduction : fetchDevelopment).then(
|
|
||||||
([
|
|
||||||
appData,
|
|
||||||
bookmarkData,
|
|
||||||
searchData,
|
|
||||||
themeData,
|
|
||||||
imprintData,
|
|
||||||
greeterData,
|
|
||||||
]: [
|
|
||||||
IAppDataProps,
|
|
||||||
IBookmarkDataProps,
|
|
||||||
ISearchDataProps,
|
|
||||||
IThemeDataProps,
|
|
||||||
IImprintDataProps,
|
|
||||||
IGreeterDataProps,
|
|
||||||
]) => {
|
|
||||||
setAppData(appData.error ? appData : { ...appData, error: false });
|
|
||||||
setBookmarkData(
|
|
||||||
bookmarkData.error ? bookmarkData : { ...bookmarkData, error: false },
|
|
||||||
);
|
|
||||||
setSearchProviderData(
|
|
||||||
searchData.error ? searchData : { ...searchData, error: false },
|
|
||||||
);
|
|
||||||
setThemeData(
|
|
||||||
themeData.error ? themeData : { ...themeData, error: false },
|
|
||||||
);
|
|
||||||
setImprintData(
|
|
||||||
imprintData.error ? imprintData : { ...imprintData, error: false },
|
|
||||||
);
|
|
||||||
setGreeterData(
|
|
||||||
greeterData.error ? greeterData : { ...greeterData, error: false },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => callback(), [callback]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
appData,
|
|
||||||
bookmarkData,
|
|
||||||
searchProviderData,
|
|
||||||
themeData,
|
|
||||||
imprintData,
|
|
||||||
greeterData,
|
|
||||||
callback,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFetcher;
|
|
95
src/lib/useFetch.tsx
Normal file
95
src/lib/useFetch.tsx
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { IAppListProps } from "../components/appList";
|
||||||
|
import { IBookmarkListProps } from "../components/bookmarks";
|
||||||
|
import { ISearchProps } from "../components/searchBar";
|
||||||
|
import { IThemeDataProps } from "./useTheme";
|
||||||
|
import { IImprintProps } from "../components/imprint";
|
||||||
|
import { IGreeterDataProps } from "../components/greeter";
|
||||||
|
|
||||||
|
export interface IDataProps<I> {
|
||||||
|
response?: I;
|
||||||
|
error?: string | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
export const handleResponse = async (response: Response) => {
|
||||||
|
if (!response.ok) throw new Error(response.statusText);
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFile = (f: string) => {
|
||||||
|
if (!inProduction) return require(`../data/${f}.json`);
|
||||||
|
|
||||||
|
return fetch(`/data/${f}.json`)
|
||||||
|
.then(handleResponse)
|
||||||
|
.catch((error: Error) => {
|
||||||
|
return { error: error.message };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IFetchProps {
|
||||||
|
appData: IDataProps<IAppListProps>;
|
||||||
|
bookmarkData: IDataProps<IBookmarkListProps>;
|
||||||
|
searchData: IDataProps<ISearchProps>;
|
||||||
|
themeData: IDataProps<IThemeDataProps>;
|
||||||
|
imprintData: IDataProps<IImprintProps>;
|
||||||
|
greeterData: IDataProps<IGreeterDataProps>;
|
||||||
|
callback?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches app, bookmark, search, theme and imprint data and returns it.
|
||||||
|
*/
|
||||||
|
export const useFetcher = (): IFetchProps => {
|
||||||
|
let defaults: IDataProps<any> = { error: true };
|
||||||
|
|
||||||
|
const [appData, setAppData] = useState<IDataProps<IAppListProps>>(defaults);
|
||||||
|
const [bookmarkData, setBookmarkData] =
|
||||||
|
useState<IDataProps<IBookmarkListProps>>(defaults);
|
||||||
|
const [searchData, setSearchData] =
|
||||||
|
useState<IDataProps<ISearchProps>>(defaults);
|
||||||
|
const [themeData, setThemeData] =
|
||||||
|
useState<IDataProps<IThemeDataProps>>(defaults);
|
||||||
|
const [imprintData, setImprintData] =
|
||||||
|
useState<IDataProps<IImprintProps>>(defaults);
|
||||||
|
const [greeterData, setGreeterData] =
|
||||||
|
useState<IDataProps<IGreeterDataProps>>(defaults);
|
||||||
|
|
||||||
|
const callback = useCallback(() => {
|
||||||
|
let files = ["apps", "bookmarks", "search", "themes", "imprint", "greeter"];
|
||||||
|
|
||||||
|
Promise.all(files.map((f) => fetchFile(f))).then(
|
||||||
|
([apps, bookmarks, search, themes, imprint, greeter]: any) => {
|
||||||
|
setAppData({ response: apps });
|
||||||
|
setBookmarkData({
|
||||||
|
response: bookmarks,
|
||||||
|
});
|
||||||
|
setSearchData({ response: search });
|
||||||
|
setThemeData({ response: themes });
|
||||||
|
setImprintData({ response: imprint });
|
||||||
|
setGreeterData({ response: greeter });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => callback(), [callback]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
appData,
|
||||||
|
bookmarkData,
|
||||||
|
searchData,
|
||||||
|
themeData,
|
||||||
|
imprintData,
|
||||||
|
greeterData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFetcher;
|
|
@ -6,6 +6,10 @@ export interface IThemeProps extends IItemProps {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IThemeDataProps {
|
||||||
|
themes: IThemeProps[];
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultTheme: IThemeProps = {
|
export const defaultTheme: IThemeProps = {
|
||||||
label: "Classic",
|
label: "Classic",
|
||||||
value: 0,
|
value: 0,
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
import { ok } from "assert";
|
|
||||||
import useFetcher, {
|
|
||||||
defaults,
|
|
||||||
handleResponse,
|
|
||||||
handleError,
|
|
||||||
fetchProduction,
|
|
||||||
fetchDevelopment,
|
|
||||||
} from "../../lib/fetcher";
|
|
||||||
|
|
||||||
describe("fetcher.tsx", () => {
|
|
||||||
it("Tests handleResponse", () => {});
|
|
||||||
|
|
||||||
it("Tests handleError", () => {
|
|
||||||
expect(handleError("apps", Error("Test!"))).toEqual({
|
|
||||||
...defaults.app,
|
|
||||||
error: "Test!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleError("bookmark", Error("Test!"))).toEqual({
|
|
||||||
...defaults.bookmark,
|
|
||||||
error: "Test!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleError("searchProvider", Error("Test!"))).toEqual({
|
|
||||||
...defaults.search,
|
|
||||||
error: "Test!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleError("theme", Error("Test!"))).toEqual({
|
|
||||||
...defaults.theme,
|
|
||||||
error: "Test!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleError("imprint", Error("Test!"))).toEqual({
|
|
||||||
...defaults.imprint,
|
|
||||||
error: "Test!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleError("greeter", Error("Test!"))).toEqual({
|
|
||||||
...defaults.greeter,
|
|
||||||
error: "Test!",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(handleError("", Error("Test!"))).toEqual(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { getTheme, IThemeProps, setScheme, setTheme } from "../../lib/useTheme";
|
|
||||||
|
|
||||||
const props: IThemeProps = {
|
|
||||||
label: "Classic",
|
|
||||||
value: 0,
|
|
||||||
mainColor: "#000000",
|
|
||||||
accentColor: "#1e272e",
|
|
||||||
backgroundColor: "#ffffff",
|
|
||||||
};
|
|
||||||
|
|
||||||
const location: Location = window.location;
|
|
||||||
const setup = () => {
|
|
||||||
Object.defineProperty(window, "localStorage", {
|
|
||||||
value: {
|
|
||||||
getItem: jest.fn(() => JSON.stringify(props)),
|
|
||||||
setItem: jest.fn(() => null),
|
|
||||||
},
|
|
||||||
writable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
delete window.location;
|
|
||||||
|
|
||||||
window.location = {
|
|
||||||
...location,
|
|
||||||
reload: jest.fn(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("theme.tsx", () => {
|
|
||||||
it("Tests setScheme", () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let value = "dark";
|
|
||||||
|
|
||||||
setScheme(value);
|
|
||||||
expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
|
|
||||||
expect(window.localStorage.setItem).toHaveBeenCalledWith("theme", value);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("setTheme light test", () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
setTheme("light", props);
|
|
||||||
expect(window.localStorage.setItem).toHaveBeenCalledTimes(2);
|
|
||||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
|
||||||
"light-theme",
|
|
||||||
JSON.stringify(props),
|
|
||||||
);
|
|
||||||
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("setTheme dark test", () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
setTheme("dark", props);
|
|
||||||
expect(window.localStorage.setItem).toHaveBeenCalledTimes(2);
|
|
||||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
|
||||||
"dark-theme",
|
|
||||||
JSON.stringify(props),
|
|
||||||
);
|
|
||||||
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Tests getTheme", () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let themeTest = getTheme();
|
|
||||||
expect(themeTest).toEqual(props);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Tests getTheme with empty parameters", () => {
|
|
||||||
localStorage.setItem("theme", "");
|
|
||||||
expect(getTheme()).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in a new issue