This commit addresses the two currently outstanding issues and also introduces some changes:

- The port has been changed from 3000 to 8080
- The format of the imprint.json file has changed. See the current example file.
This commit is contained in:
phntxx 2021-03-05 22:00:32 +01:00
parent 15d4a60958
commit 7de67cbbd8
57 changed files with 3168 additions and 19341 deletions

View file

@ -1,18 +1,12 @@
FROM node:current-alpine FROM node:lts AS build
WORKDIR /app WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY yarn.lock . COPY . ./
COPY package.json . RUN yarn build
RUN [ "yarn", "install" ] FROM ratisbonacoding/nginx-cloudflare-cache
COPY --from=build /app/build /app
COPY data /app/data COPY nginx.conf /etc/nginx/nginx.conf
COPY src /app/src
COPY public /app/public
RUN [ "yarn", "build" ]
EXPOSE 3000 8080
CMD [ "yarn", "serve:production" ]

View file

@ -1,71 +1,71 @@
{ {
"categories": [ "categories": [
{
"name": "Networking",
"items": [
{ {
"name": "Networking", "name": "pfSense",
"items": [ "displayURL": "example.com",
{ "URL": "https://example.com",
"name": "pfSense", "icon": "security"
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "security"
},
{
"name": "Pihole",
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "vpn_lock"
}
]
}, },
{ {
"name": "Monitoring", "name": "Pihole",
"items": [ "displayURL": "example.com",
{ "URL": "https://example.com",
"name": "Tautulli", "icon": "vpn_lock"
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "bar_chart"
},
{
"name": "Grafana",
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "show_chart"
}
]
} }
], ]
"apps": [ },
{
"name": "Monitoring",
"items": [
{ {
"name": "Plex", "name": "Tautulli",
"displayURL": "example.com", "displayURL": "example.com",
"URL": "https://example.com", "URL": "https://example.com",
"icon": "tv" "icon": "bar_chart"
}, },
{ {
"name": "NextCloud", "name": "Grafana",
"displayURL": "example.com", "displayURL": "example.com",
"URL": "https://example.com", "URL": "https://example.com",
"icon": "filter_drama" "icon": "show_chart"
}, }
{ ]
"name": "Ghost", }
"displayURL": "example.com", ],
"URL": "https://example.com", "apps": [
"icon": "rss_feed" {
}, "name": "Plex",
{ "displayURL": "example.com",
"name": "Minecraft", "URL": "https://example.com",
"displayURL": "example.com", "icon": "tv"
"URL": "https://example.com", },
"icon": "games" {
}, "name": "NextCloud",
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "filter_drama"
},
{
"name": "Ghost",
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "rss_feed"
},
{
"name": "Minecraft",
"displayURL": "example.com",
"URL": "https://example.com",
"icon": "games"
},
{ {
"name": "ESXi", "name": "ESXi",
"displayURL": "example.com", "displayURL": "example.com",
"URL": "https://example.com", "URL": "https://example.com",
"icon": "dns" "icon": "dns"
} }
] ]
} }

View file

@ -1,98 +1,98 @@
{ {
"groups": [ "groups": [
{
"name": "Wikis",
"items": [
{ {
"name": "Wikis", "name": "Wikipedia",
"items": [ "url": "https://en.wikipedia.org/"
{
"name": "Wikipedia",
"url": "https://en.wikipedia.org/"
},
{
"name": "Arch Linux Wiki",
"url": "https://archlinux.org/"
}
]
}, },
{ {
"name": "Dev", "name": "Arch Linux Wiki",
"items": [ "url": "https://archlinux.org/"
{
"name": "Codepen",
"url": "https://codepen.io/"
},
{
"name": "JSFiddle",
"url": "https://jsfiddle.net/"
},
{
"name": "Pastebin",
"url": "https://pastebin.com/"
}
]
},
{
"name": "Media",
"items": [
{
"name": "Soundcloud",
"url": "https://soundcloud.com"
},
{
"name": "YouTube",
"url": "https://youtube.com"
},
{
"name": "Twitch",
"url": "https://twitch.tv"
}
]
},
{
"name": "Social Networks",
"items": [
{
"name": "Facebook",
"url": "https://facebook.com"
},
{
"name": "Twitter",
"url": "https://twitter.com"
},
{
"name": "Instagram",
"url": "https://instagram.com"
}
]
},
{
"name": "Imageboards",
"items": [
{
"name": "Reddit",
"url": "https://reddit.com"
},
{
"name": "4chan",
"url": "https://4chan.org"
}
]
},
{
"name": "Tech",
"items": [
{
"name": "Hackernoon",
"url": "https://hackernoon.com"
},
{
"name": "The Verge",
"url": "https://theverge.com"
},
{
"name": "Hackernews",
"url": "https://news.ycombinator.com"
}
]
} }
] ]
},
{
"name": "Dev",
"items": [
{
"name": "Codepen",
"url": "https://codepen.io/"
},
{
"name": "JSFiddle",
"url": "https://jsfiddle.net/"
},
{
"name": "Pastebin",
"url": "https://pastebin.com/"
}
]
},
{
"name": "Media",
"items": [
{
"name": "Soundcloud",
"url": "https://soundcloud.com"
},
{
"name": "YouTube",
"url": "https://youtube.com"
},
{
"name": "Twitch",
"url": "https://twitch.tv"
}
]
},
{
"name": "Social Networks",
"items": [
{
"name": "Facebook",
"url": "https://facebook.com"
},
{
"name": "Twitter",
"url": "https://twitter.com"
},
{
"name": "Instagram",
"url": "https://instagram.com"
}
]
},
{
"name": "Imageboards",
"items": [
{
"name": "Reddit",
"url": "https://reddit.com"
},
{
"name": "4chan",
"url": "https://4chan.org"
}
]
},
{
"name": "Tech",
"items": [
{
"name": "Hackernoon",
"url": "https://hackernoon.com"
},
{
"name": "The Verge",
"url": "https://theverge.com"
},
{
"name": "Hackernews",
"url": "https://news.ycombinator.com"
}
]
}
]
} }

View file

@ -1,22 +1,24 @@
{ {
"name": { "imprint": {
"text": "John Doe", "name": {
"link": "#" "text": "John Doe",
}, "link": "#"
"address": { },
"text": "Null Ave. 1234, 90008 Los Angeles", "address": {
"link": "#" "text": "Null Ave. 1234, 90008 Los Angeles",
}, "link": "#"
"phone": { },
"text": "+1-202-555-0167", "phone": {
"link": "#" "text": "+1-202-555-0167",
}, "link": "#"
"email": { },
"text": "doe.john@example.com", "email": {
"link": "#" "text": "doe.john@example.com",
}, "link": "#"
"url": { },
"text": "example.com", "url": {
"link": "#" "text": "example.com",
"link": "#"
}
} }
} }

View file

@ -1,64 +1,64 @@
{ {
"providers": [ "providers": [
{ {
"name": "Allmusic", "name": "Allmusic",
"url": "https://www.allmusic.com/search/all/", "url": "https://www.allmusic.com/search/all/",
"prefix": "/a" "prefix": "/a"
}, },
{ {
"name": "Discogs", "name": "Discogs",
"url": "https://www.discogs.com/search/?q=", "url": "https://www.discogs.com/search/?q=",
"prefix": "/di" "prefix": "/di"
}, },
{ {
"name": "Duck Duck Go", "name": "Duck Duck Go",
"url": "https://duckduckgo.com/?q=", "url": "https://duckduckgo.com/?q=",
"prefix": "/d" "prefix": "/d"
}, },
{ {
"name": "iMDB", "name": "iMDB",
"url": "https://www.imdb.com/find?q=", "url": "https://www.imdb.com/find?q=",
"prefix": "/i" "prefix": "/i"
}, },
{ {
"name": "TheMovieDB", "name": "TheMovieDB",
"url": "https://www.themoviedb.org/search?query=", "url": "https://www.themoviedb.org/search?query=",
"prefix": "/m" "prefix": "/m"
}, },
{ {
"name": "Reddit", "name": "Reddit",
"url": "https://www.reddit.com/search?q=", "url": "https://www.reddit.com/search?q=",
"prefix": "/r" "prefix": "/r"
}, },
{ {
"name": "Qwant", "name": "Qwant",
"url": "https://www.qwant.com/?q=", "url": "https://www.qwant.com/?q=",
"prefix": "/q" "prefix": "/q"
}, },
{ {
"name": "Soundcloud", "name": "Soundcloud",
"url": "https://soundcloud.com/search?q=", "url": "https://soundcloud.com/search?q=",
"prefix": "/so" "prefix": "/so"
}, },
{ {
"name": "Spotify", "name": "Spotify",
"url": "https://open.spotify.com/search/results/", "url": "https://open.spotify.com/search/results/",
"prefix": "/s" "prefix": "/s"
}, },
{ {
"name": "TheTVDB", "name": "TheTVDB",
"url": "https://www.thetvdb.com/search?q=", "url": "https://www.thetvdb.com/search?q=",
"prefix": "/tv" "prefix": "/tv"
}, },
{ {
"name": "Trakt", "name": "Trakt",
"url": "https://trakt.tv/search?query=", "url": "https://trakt.tv/search?query=",
"prefix": "/t" "prefix": "/t"
}, },
{ {
"name": "YouTube", "name": "YouTube",
"url": "https://youtube.com/results?search_query=", "url": "https://youtube.com/results?search_query=",
"prefix": "/yt" "prefix": "/yt"
} }
] ]
} }

View file

@ -1,109 +1,109 @@
{ {
"themes": [ "themes": [
{ {
"label": "Classic", "label": "Classic",
"value": 0, "value": 0,
"mainColor": "#000000", "mainColor": "#000000",
"accentColor": "#1e272e", "accentColor": "#1e272e",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
{ {
"label": "Dark", "label": "Dark",
"value": 1, "value": 1,
"mainColor": "#ffffff", "mainColor": "#ffffff",
"accentColor": "#999999", "accentColor": "#999999",
"backgroundColor": "#000000" "backgroundColor": "#000000"
}, },
{ {
"label": "Raw", "label": "Raw",
"value": 2, "value": 2,
"mainColor": "", "mainColor": "",
"accentColor": "", "accentColor": "",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
{ {
"label": "Blackboard", "label": "Blackboard",
"value": 3, "value": 3,
"mainColor": "#fffdea", "mainColor": "#fffdea",
"accentColor": "#5c5c5c", "accentColor": "#5c5c5c",
"backgroundColor": "#1a1a1a" "backgroundColor": "#1a1a1a"
}, },
{ {
"label": "Gazette", "label": "Gazette",
"value": 4, "value": 4,
"mainColor": "#000000", "mainColor": "#000000",
"accentColor": "#5c5c5c", "accentColor": "#5c5c5c",
"backgroundColor": "#F2F7FF" "backgroundColor": "#F2F7FF"
}, },
{ {
"label": "Espresso", "label": "Espresso",
"value": 5, "value": 5,
"mainColor": "#d1b59a", "mainColor": "#d1b59a",
"accentColor": "#4e4e4e", "accentColor": "#4e4e4e",
"backgroundColor": "#21211f" "backgroundColor": "#21211f"
}, },
{ {
"label": "Cab", "label": "Cab",
"value": 6, "value": 6,
"mainColor": "#1f1f1f", "mainColor": "#1f1f1f",
"accentColor": "#424242", "accentColor": "#424242",
"backgroundColor": "#f6d305" "backgroundColor": "#f6d305"
}, },
{ {
"label": "Cloud", "label": "Cloud",
"value": 7, "value": 7,
"mainColor": "#35342f", "mainColor": "#35342f",
"accentColor": "#37bbe4", "accentColor": "#37bbe4",
"backgroundColor": "#f1f2f0" "backgroundColor": "#f1f2f0"
}, },
{ {
"label": "Lime", "label": "Lime",
"value": 8, "value": 8,
"mainColor": "#aabbc3", "mainColor": "#aabbc3",
"accentColor": "#aeea00", "accentColor": "#aeea00",
"backgroundColor": "#263238" "backgroundColor": "#263238"
}, },
{ {
"label": "White", "label": "White",
"value": 9, "value": 9,
"mainColor": "#222222", "mainColor": "#222222",
"accentColor": "#dddddd", "accentColor": "#dddddd",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
{ {
"label": "Tron", "label": "Tron",
"value": 10, "value": 10,
"mainColor": "#effbff", "mainColor": "#effbff",
"accentColor": "#6ee2ff", "accentColor": "#6ee2ff",
"backgroundColor": "#242b33" "backgroundColor": "#242b33"
}, },
{ {
"label": "Blues", "label": "Blues",
"value": 11, "value": 11,
"mainColor": "#eff1fc", "mainColor": "#eff1fc",
"accentColor": "#6677eb", "accentColor": "#6677eb",
"backgroundColor": "#2b2c56" "backgroundColor": "#2b2c56"
}, },
{ {
"label": "Passion", "label": "Passion",
"value": 12, "value": 12,
"mainColor": "#12005e", "mainColor": "#12005e",
"accentColor": "#8e24aa", "accentColor": "#8e24aa",
"backgroundColor": "#f5f5f5" "backgroundColor": "#f5f5f5"
}, },
{ {
"label": "Chalk", "label": "Chalk",
"value": 13, "value": 13,
"mainColor": "#aabbc3", "mainColor": "#aabbc3",
"accentColor": "#ff869a", "accentColor": "#ff869a",
"backgroundColor": "#263238" "backgroundColor": "#263238"
}, },
{ {
"label": "Paper", "label": "Paper",
"value": 14, "value": 14,
"mainColor": "#4c432e", "mainColor": "#4c432e",
"accentColor": "#aa9a73", "accentColor": "#aa9a73",
"backgroundColor": "#f8f6f1" "backgroundColor": "#f8f6f1"
} }
] ]
} }

View file

@ -7,4 +7,4 @@ services:
volumes: volumes:
- ./data:/app/data - ./data:/app/data
ports: ports:
- 3000:3000 - 8080:8080

30
nginx.conf Normal file
View file

@ -0,0 +1,30 @@
worker_processes auto;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '[$time_iso8601] $request_method $uri - $status';
server_tokens off;
sendfile on;
keepalive_timeout 65;
access_log /var/log/nginx/access.log main;
error_log /dev/null;
server {
server_name localhost;
listen 8080;
location / {
root /app;
index index.html index.htm;
}
}
}

16756
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
/* fallback */
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: url(icons.woff2) format("woff2");
}
.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,272 @@
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(cyrillic-ext-400.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(cyrillic-400.woff2) format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(greek-ext-400.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(greek-400.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(vietnamese-400.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(latin-ext-400.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(latin-400.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(cyrillic-ext-500.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(cyrillic-500.woff2) format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(greek-ext-500.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(greek-500.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(vietnamese-500.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(latin-ext-500.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(latin-500.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(cyrillic-ext-700.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2)
format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(greek-ext-700.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(greek-700.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(vietnamese-700.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(latin-ext-700.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(latin-700.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(cyrillic-ext-900.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(cyrillic-900.woff2) format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(greek-ext-900.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(greek-900.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(vietnamese-900.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(latin-ext-900.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(latin-900.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -25,18 +25,8 @@
href="%PUBLIC_URL%/icon/safari-pinned-tab.svg" href="%PUBLIC_URL%/icon/safari-pinned-tab.svg"
color="#5bbad5" color="#5bbad5"
/> />
<link <link href="%PUBLIC_URL%/fonts/roboto/roboto.css" rel="stylesheet" />
href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;700&display=swap" <link href="%PUBLIC_URL%/fonts/icons/icons.css" rel="stylesheet" />
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
<title>Dashboard</title> <title>Dashboard</title>
</head> </head>
<body> <body>

View file

@ -1,5 +1,5 @@
import React from "react"; import React, { useEffect } from "react";
import styled, { createGlobalStyle } from "styled-components"; import { createGlobalStyle } from "styled-components";
import SearchBar from "./components/searchBar"; import SearchBar from "./components/searchBar";
import Greeter from "./components/greeter"; import Greeter from "./components/greeter";
@ -9,6 +9,13 @@ import Settings from "./components/settings";
import Imprint from "./components/imprint"; import Imprint from "./components/imprint";
import selectedTheme from "./components/themeManager"; import selectedTheme from "./components/themeManager";
import {
useAppData,
useSearchProviderData,
useBookmarkData,
useThemeData,
useImprintData,
} from "./components/fetch";
const GlobalStyle = createGlobalStyle` const GlobalStyle = createGlobalStyle`
body { body {
@ -22,24 +29,37 @@ const GlobalStyle = createGlobalStyle`
@media (min-width: 1366px) { @media (min-width: 1366px) {
max-width: 70%; max-width: 70%;
} }
} }
`; `;
const AppContainer = styled.div``; const App = () => {
const { appData } = useAppData();
const { searchProviderData } = useSearchProviderData();
const { bookmarkData } = useBookmarkData();
const { themeData } = useThemeData();
const { imprintData } = useImprintData();
const App = () => ( return (
<> <>
<GlobalStyle /> <GlobalStyle />
<AppContainer> <div>
<SearchBar /> <SearchBar providers={searchProviderData?.providers} />
<Settings /> {!themeData.error && !searchProviderData.error && (
<Greeter /> <Settings
<AppList /> themes={themeData?.themes}
<BookmarkList /> providers={searchProviderData?.providers}
<Imprint /> />
</AppContainer> )}
</>
); <Greeter />
{!appData.error && (
<AppList apps={appData.apps} categories={appData.categories} />
)}
{!bookmarkData.error && <BookmarkList groups={bookmarkData.groups} />}
{!imprintData.error && <Imprint imprint={imprintData.imprint} />}
</div>
</>
);
};
export default App; export default App;

View file

@ -11,7 +11,7 @@ const CategoryContainer = styled.div`
width: 100%; width: 100%;
`; `;
interface IAppCategoryProps { export interface IAppCategoryProps {
name: string; name: string;
items: Array<IAppProps>; items: Array<IAppProps>;
} }

View file

@ -1,47 +1,18 @@
import React, { useCallback, useEffect, useState } from "react"; import React from "react";
import { AppCategory } from "./appCategory"; import { AppCategory, IAppCategoryProps } from "./appCategory";
import { IAppProps } from "./app";
import { import { Headline, ListContainer } from "./elements";
handleResponse,
Headline,
ListContainer,
ErrorMessage,
} from "./elements";
const useAppData = () => { export interface IAppListProps {
const [appData, setAppData] = useState({ categories: Array<IAppCategoryProps>;
categories: [], apps: Array<IAppProps>;
apps: [], }
error: false,
});
const fetchAppData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/apps.json").then(handleResponse)
: import("./data/apps.json")
)
.then((jsonResponse) => {
setAppData({ ...jsonResponse, error: false });
})
.catch((error) => {
setAppData({ categories: [], apps: [], error: error.message });
});
}, []);
useEffect(() => {
fetchAppData();
}, [fetchAppData]);
return { appData, fetchAppData };
};
const AppList = () => {
const {
appData: { categories, apps, error },
} = useAppData();
const AppList = ({ categories, apps }: IAppListProps) => {
return ( return (
<ListContainer> <ListContainer>
<Headline>Applications</Headline> <Headline>Applications</Headline>
{error && <ErrorMessage>{error}</ErrorMessage>}
{categories && {categories &&
categories.map(({ name, items }, idx) => ( categories.map(({ name, items }, idx) => (
<AppCategory key={[name, idx].join("")} name={name} items={items} /> <AppCategory key={[name, idx].join("")} name={name} items={items} />

View file

@ -22,19 +22,19 @@ const Bookmark = styled.a`
} }
`; `;
interface IBookmarkProps { export interface IBookmarkProps {
name: string; name: string;
url: string; url: string;
} }
interface IBookmarkGroupProps { export interface IBookmarkGroupProps {
name: string; name: string;
items: [IBookmarkProps]; items: Array<IBookmarkProps>;
} }
export const BookmarkGroup = ({ export const BookmarkGroup = ({
name: groupName, name: groupName,
items items,
}: IBookmarkGroupProps) => ( }: IBookmarkGroupProps) => (
<Item> <Item>
<GroupContainer> <GroupContainer>

View file

@ -1,59 +1,24 @@
import React, { useCallback, useEffect, useState } from 'react'; import React from "react";
import { import { Headline, ListContainer, ItemList } from "./elements";
handleResponse,
Headline,
ListContainer,
ItemList,
ErrorMessage,
} from './elements';
import { BookmarkGroup } from './bookmarkGroup'; import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup";
const useBookmarkData = () => { interface IBookmarkListProps {
const [bookmarkData, setBookmarkData] = useState({ groups: Array<IBookmarkGroupProps>;
groups: [], }
error: false,
});
const fetchBookmarkData = useCallback(() => { const BookmarkList = ({ groups }: IBookmarkListProps) => {
(process.env.NODE_ENV === 'production' return (
? fetch('/data/bookmarks.json').then(handleResponse) <ListContainer>
: import('./data/bookmarks.json') <Headline>Bookmarks</Headline>
) <ItemList>
.then((jsonResponse) => { {groups.map(({ name, items }, idx) => (
setBookmarkData({ ...jsonResponse, error: false }); <BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
}) ))}
.catch((error) => { </ItemList>
setBookmarkData({ groups: [], error: error.message }); </ListContainer>
}); );
}, []);
useEffect(() => {
fetchBookmarkData();
}, [fetchBookmarkData]);
return { bookmarkData, fetchBookmarkData };
};
const BookmarkList = () => {
const {
bookmarkData: { groups, error },
} = useBookmarkData();
return (
<ListContainer>
<Headline>Bookmarks</Headline>
{error && <ErrorMessage>{error}</ErrorMessage>}
<ItemList>
{groups.map(({ name, items }, idx) => (
<BookmarkGroup
key={[name, idx].join('')}
name={name}
items={items}
/>
))}
</ItemList>
</ListContainer>
);
}; };
export default BookmarkList; export default BookmarkList;

View file

@ -1,98 +1,98 @@
{ {
"groups": [ "groups": [
{
"name": "Wikis",
"items": [
{ {
"name": "Wikis", "name": "Wikipedia",
"items": [ "url": "https://en.wikipedia.org/"
{
"name": "Wikipedia",
"url": "https://en.wikipedia.org/"
},
{
"name": "Arch Linux Wiki",
"url": "https://archlinux.org/"
}
]
}, },
{ {
"name": "Dev", "name": "Arch Linux Wiki",
"items": [ "url": "https://archlinux.org/"
{
"name": "Codepen",
"url": "https://codepen.io/"
},
{
"name": "JSFiddle",
"url": "https://jsfiddle.net/"
},
{
"name": "Pastebin",
"url": "https://pastebin.com/"
}
]
},
{
"name": "Media",
"items": [
{
"name": "Soundcloud",
"url": "https://soundcloud.com"
},
{
"name": "YouTube",
"url": "https://youtube.com"
},
{
"name": "Twitch",
"url": "https://twitch.tv"
}
]
},
{
"name": "Social Networks",
"items": [
{
"name": "Facebook",
"url": "https://facebook.com"
},
{
"name": "Twitter",
"url": "https://twitter.com"
},
{
"name": "Instagram",
"url": "https://instagram.com"
}
]
},
{
"name": "Imageboards",
"items": [
{
"name": "Reddit",
"url": "https://reddit.com"
},
{
"name": "4chan",
"url": "https://4chan.org"
}
]
},
{
"name": "Tech",
"items": [
{
"name": "Hackernoon",
"url": "https://hackernoon.com"
},
{
"name": "The Verge",
"url": "https://theverge.com"
},
{
"name": "Hackernews",
"url": "https://news.ycombinator.com"
}
]
} }
] ]
},
{
"name": "Dev",
"items": [
{
"name": "Codepen",
"url": "https://codepen.io/"
},
{
"name": "JSFiddle",
"url": "https://jsfiddle.net/"
},
{
"name": "Pastebin",
"url": "https://pastebin.com/"
}
]
},
{
"name": "Media",
"items": [
{
"name": "Soundcloud",
"url": "https://soundcloud.com"
},
{
"name": "YouTube",
"url": "https://youtube.com"
},
{
"name": "Twitch",
"url": "https://twitch.tv"
}
]
},
{
"name": "Social Networks",
"items": [
{
"name": "Facebook",
"url": "https://facebook.com"
},
{
"name": "Twitter",
"url": "https://twitter.com"
},
{
"name": "Instagram",
"url": "https://instagram.com"
}
]
},
{
"name": "Imageboards",
"items": [
{
"name": "Reddit",
"url": "https://reddit.com"
},
{
"name": "4chan",
"url": "https://4chan.org"
}
]
},
{
"name": "Tech",
"items": [
{
"name": "Hackernoon",
"url": "https://hackernoon.com"
},
{
"name": "The Verge",
"url": "https://theverge.com"
},
{
"name": "Hackernews",
"url": "https://news.ycombinator.com"
}
]
}
]
} }

View file

@ -1,22 +1,24 @@
{ {
"name": { "imprint": {
"text": "John Doe", "name": {
"link": "#" "text": "John Doe",
}, "link": "#"
"address": { },
"text": "Null Ave. 1234, 90008 Los Angeles", "address": {
"link": "#" "text": "Null Ave. 1234, 90008 Los Angeles",
}, "link": "#"
"phone": { },
"text": "+1-202-555-0167", "phone": {
"link": "#" "text": "+1-202-555-0167",
}, "link": "#"
"email": { },
"text": "doe.john@example.com", "email": {
"link": "#" "text": "doe.john@example.com",
}, "link": "#"
"url": { },
"text": "example.com", "url": {
"link": "#" "text": "example.com",
"link": "#"
}
} }
} }

View file

@ -1,64 +1,64 @@
{ {
"providers": [ "providers": [
{ {
"name": "Allmusic", "name": "Allmusic",
"url": "https://www.allmusic.com/search/all/", "url": "https://www.allmusic.com/search/all/",
"prefix": "/a" "prefix": "/a"
}, },
{ {
"name": "Discogs", "name": "Discogs",
"url": "https://www.discogs.com/search/?q=", "url": "https://www.discogs.com/search/?q=",
"prefix": "/di" "prefix": "/di"
}, },
{ {
"name": "Duck Duck Go", "name": "Duck Duck Go",
"url": "https://duckduckgo.com/?q=", "url": "https://duckduckgo.com/?q=",
"prefix": "/d" "prefix": "/d"
}, },
{ {
"name": "iMDB", "name": "iMDB",
"url": "https://www.imdb.com/find?q=", "url": "https://www.imdb.com/find?q=",
"prefix": "/i" "prefix": "/i"
}, },
{ {
"name": "TheMovieDB", "name": "TheMovieDB",
"url": "https://www.themoviedb.org/search?query=", "url": "https://www.themoviedb.org/search?query=",
"prefix": "/m" "prefix": "/m"
}, },
{ {
"name": "Reddit", "name": "Reddit",
"url": "https://www.reddit.com/search?q=", "url": "https://www.reddit.com/search?q=",
"prefix": "/r" "prefix": "/r"
}, },
{ {
"name": "Qwant", "name": "Qwant",
"url": "https://www.qwant.com/?q=", "url": "https://www.qwant.com/?q=",
"prefix": "/q" "prefix": "/q"
}, },
{ {
"name": "Soundcloud", "name": "Soundcloud",
"url": "https://soundcloud.com/search?q=", "url": "https://soundcloud.com/search?q=",
"prefix": "/so" "prefix": "/so"
}, },
{ {
"name": "Spotify", "name": "Spotify",
"url": "https://open.spotify.com/search/results/", "url": "https://open.spotify.com/search/results/",
"prefix": "/s" "prefix": "/s"
}, },
{ {
"name": "TheTVDB", "name": "TheTVDB",
"url": "https://www.thetvdb.com/search?q=", "url": "https://www.thetvdb.com/search?q=",
"prefix": "/tv" "prefix": "/tv"
}, },
{ {
"name": "Trakt", "name": "Trakt",
"url": "https://trakt.tv/search?query=", "url": "https://trakt.tv/search?query=",
"prefix": "/t" "prefix": "/t"
}, },
{ {
"name": "YouTube", "name": "YouTube",
"url": "https://youtube.com/results?search_query=", "url": "https://youtube.com/results?search_query=",
"prefix": "/yt" "prefix": "/yt"
} }
] ]
} }

View file

@ -1,109 +1,109 @@
{ {
"themes": [ "themes": [
{ {
"label": "Classic", "label": "Classic",
"value": 0, "value": 0,
"mainColor": "#000000", "mainColor": "#000000",
"accentColor": "#1e272e", "accentColor": "#1e272e",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
{ {
"label": "Dark", "label": "Dark",
"value": 1, "value": 1,
"mainColor": "#ffffff", "mainColor": "#ffffff",
"accentColor": "#999999", "accentColor": "#999999",
"backgroundColor": "#000000" "backgroundColor": "#000000"
}, },
{ {
"label": "Raw", "label": "Raw",
"value": 2, "value": 2,
"mainColor": "", "mainColor": "",
"accentColor": "", "accentColor": "",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
{ {
"label": "Blackboard", "label": "Blackboard",
"value": 3, "value": 3,
"mainColor": "#fffdea", "mainColor": "#fffdea",
"accentColor": "#5c5c5c", "accentColor": "#5c5c5c",
"backgroundColor": "#1a1a1a" "backgroundColor": "#1a1a1a"
}, },
{ {
"label": "Gazette", "label": "Gazette",
"value": 4, "value": 4,
"mainColor": "#000000", "mainColor": "#000000",
"accentColor": "#5c5c5c", "accentColor": "#5c5c5c",
"backgroundColor": "#F2F7FF" "backgroundColor": "#F2F7FF"
}, },
{ {
"label": "Espresso", "label": "Espresso",
"value": 5, "value": 5,
"mainColor": "#d1b59a", "mainColor": "#d1b59a",
"accentColor": "#4e4e4e", "accentColor": "#4e4e4e",
"backgroundColor": "#21211f" "backgroundColor": "#21211f"
}, },
{ {
"label": "Cab", "label": "Cab",
"value": 6, "value": 6,
"mainColor": "#1f1f1f", "mainColor": "#1f1f1f",
"accentColor": "#424242", "accentColor": "#424242",
"backgroundColor": "#f6d305" "backgroundColor": "#f6d305"
}, },
{ {
"label": "Cloud", "label": "Cloud",
"value": 7, "value": 7,
"mainColor": "#35342f", "mainColor": "#35342f",
"accentColor": "#37bbe4", "accentColor": "#37bbe4",
"backgroundColor": "#f1f2f0" "backgroundColor": "#f1f2f0"
}, },
{ {
"label": "Lime", "label": "Lime",
"value": 8, "value": 8,
"mainColor": "#aabbc3", "mainColor": "#aabbc3",
"accentColor": "#aeea00", "accentColor": "#aeea00",
"backgroundColor": "#263238" "backgroundColor": "#263238"
}, },
{ {
"label": "White", "label": "White",
"value": 9, "value": 9,
"mainColor": "#222222", "mainColor": "#222222",
"accentColor": "#dddddd", "accentColor": "#dddddd",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
{ {
"label": "Tron", "label": "Tron",
"value": 10, "value": 10,
"mainColor": "#effbff", "mainColor": "#effbff",
"accentColor": "#6ee2ff", "accentColor": "#6ee2ff",
"backgroundColor": "#242b33" "backgroundColor": "#242b33"
}, },
{ {
"label": "Blues", "label": "Blues",
"value": 11, "value": 11,
"mainColor": "#eff1fc", "mainColor": "#eff1fc",
"accentColor": "#6677eb", "accentColor": "#6677eb",
"backgroundColor": "#2b2c56" "backgroundColor": "#2b2c56"
}, },
{ {
"label": "Passion", "label": "Passion",
"value": 12, "value": 12,
"mainColor": "#12005e", "mainColor": "#12005e",
"accentColor": "#8e24aa", "accentColor": "#8e24aa",
"backgroundColor": "#f5f5f5" "backgroundColor": "#f5f5f5"
}, },
{ {
"label": "Chalk", "label": "Chalk",
"value": 13, "value": 13,
"mainColor": "#aabbc3", "mainColor": "#aabbc3",
"accentColor": "#ff869a", "accentColor": "#ff869a",
"backgroundColor": "#263238" "backgroundColor": "#263238"
}, },
{ {
"label": "Paper", "label": "Paper",
"value": 14, "value": 14,
"mainColor": "#4c432e", "mainColor": "#4c432e",
"accentColor": "#aa9a73", "accentColor": "#aa9a73",
"backgroundColor": "#f8f6f1" "backgroundColor": "#f8f6f1"
} }
] ]
} }

View file

@ -5,13 +5,6 @@ import Icon from "./icon";
// File for elements that are/can be reused across the entire site. // File for elements that are/can be reused across the entire site.
export const handleResponse = (response: any) => {
if (response.ok) {
return response.json();
}
throw new Error("Failed to load data.");
};
export const ListContainer = styled.div` export const ListContainer = styled.div`
padding: 2rem 0; padding: 2rem 0;
`; `;

193
src/components/fetch.tsx Normal file
View file

@ -0,0 +1,193 @@
import { useCallback, useEffect, useState } from "react";
import { ISearchProviderProps } from "./searchBar";
import { IBookmarkGroupProps } from "./bookmarkGroup";
import { IAppCategoryProps } from "./appCategory";
import { IAppProps } from "./app";
import { IThemeProps } from "./themeManager";
import { IImprintProps } from "./imprint";
const errorMessage = "Failed to load data.";
const handleResponse = (response: any) => {
if (response.ok) return response.json();
throw new Error(errorMessage);
};
// SECTION: Search Provider
export interface ISearchProviderDataProps {
providers: Array<ISearchProviderProps>;
error: string | boolean;
}
export const useSearchProviderData = () => {
const [
searchProviderData,
setSearchProviderData,
] = useState<ISearchProviderDataProps>({ providers: [], error: false });
const fetchSearchProviderData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/search.json").then(handleResponse)
: import("./data/search.json")
)
.then((jsonResponse) => {
setSearchProviderData({ ...jsonResponse, error: false });
})
.catch((error) => {
setSearchProviderData({ providers: [], error: error.message });
});
}, []);
useEffect(() => {
fetchSearchProviderData();
}, [fetchSearchProviderData]);
return { searchProviderData, fetchSearchProviderData };
};
// SECTION: Bookmark data
export interface IBookmarkDataProps {
groups: Array<IBookmarkGroupProps>;
error: string | boolean;
}
export const useBookmarkData = () => {
const [bookmarkData, setBookmarkData] = useState<IBookmarkDataProps>({
groups: [],
error: false,
});
const fetchBookmarkData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/bookmarks.json").then(handleResponse)
: import("./data/bookmarks.json")
)
.then((jsonResponse) => {
setBookmarkData({ ...jsonResponse, error: false });
})
.catch((error) => {
setBookmarkData({ groups: [], error: error.message });
});
}, []);
useEffect(() => {
fetchBookmarkData();
}, [fetchBookmarkData]);
return { bookmarkData, fetchBookmarkData };
};
// SECTION: App data
export interface IAppDataProps {
categories: Array<IAppCategoryProps>;
apps: Array<IAppProps>;
error: string | boolean;
}
export const useAppData = () => {
const [appData, setAppData] = useState({
categories: [],
apps: [],
error: false,
});
const fetchAppData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/apps.json").then(handleResponse)
: import("./data/apps.json")
)
.then((jsonResponse) => {
setAppData({ ...jsonResponse, error: false });
})
.catch((error) => {
setAppData({ categories: [], apps: [], error: error.message });
});
}, []);
useEffect(() => {
fetchAppData();
}, [fetchAppData]);
return { appData, fetchAppData };
};
// Section: Theme Data
export interface IThemeDataProps {
themes: Array<IThemeProps>;
error: string | boolean;
}
export const useThemeData = () => {
const [themeData, setThemeData] = useState<IThemeDataProps>({
themes: [],
error: false,
});
const fetchThemeData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/themes.json").then(handleResponse)
: import("./data/themes.json")
)
.then((jsonResponse) => {
setThemeData({ ...jsonResponse, error: false });
})
.catch((error) => {
setThemeData({ themes: [], error: error.message });
});
}, []);
useEffect(() => {
fetchThemeData();
}, [fetchThemeData]);
return { themeData, fetchThemeData };
};
// SECTION: Imprint Data
export interface IImprintDataProps {
imprint: IImprintProps;
error: string | boolean;
}
export const useImprintData = () => {
const [imprintData, setImprintData] = useState<IImprintDataProps>({
imprint: {
name: { text: "", link: "" },
address: { text: "", link: "" },
phone: { text: "", link: "" },
email: { text: "", link: "" },
url: { text: "", link: "" },
},
error: false,
});
const fetchImprintData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/imprint.json").then(handleResponse)
: import("./data/imprint.json")
)
.then((jsonResponse: any) => {
setImprintData({ ...jsonResponse, error: false });
})
.catch((error: any) => {
setImprintData({
imprint: {
name: { text: "", link: "" },
address: { text: "", link: "" },
phone: { text: "", link: "" },
email: { text: "", link: "" },
url: { text: "", link: "" },
},
error: error.message,
});
});
}, []);
useEffect(() => {
fetchImprintData();
}, [fetchImprintData]);
return { imprintData, fetchImprintData };
};

View file

@ -1,10 +1,8 @@
import React, { useCallback, useEffect, useState } from "react"; import React from "react";
import Modal from "./modal"; import Modal from "./modal";
import styled from "styled-components"; import styled from "styled-components";
import selectedTheme from "./themeManager"; import selectedTheme from "./themeManager";
import { import {
handleResponse,
ErrorMessage,
ListContainer, ListContainer,
ItemList, ItemList,
Headline as Hl, Headline as Hl,
@ -47,127 +45,97 @@ const ItemContainer = styled.div`
padding: 1rem 0; padding: 1rem 0;
`; `;
const useImprintData = () => { interface IImprintFieldProps {
const [imprintData, setImprintData] = useState({ text: string;
name: { text: "", link: "" }, link: string;
address: { text: "", link: "" }, }
phone: { text: "", link: "" },
email: { text: "", link: "" },
url: { text: "", link: "" },
error: false,
});
const fetchImprintData = useCallback(() => {
(process.env.NODE_ENV === "production"
? fetch("/data/imprint.json").then(handleResponse)
: import("./data/imprint.json")
)
.then((jsonResponse: any) => {
setImprintData({ ...jsonResponse, error: false });
})
.catch((error: any) => {
setImprintData({
name: { text: "", link: "" },
address: { text: "", link: "" },
phone: { text: "", link: "" },
email: { text: "", link: "" },
url: { text: "", link: "" },
error: error.message,
});
});
}, []);
useEffect(() => { export interface IImprintProps {
fetchImprintData(); name: IImprintFieldProps;
}, [fetchImprintData]); address: IImprintFieldProps;
return { imprintData, fetchImprintData }; phone: IImprintFieldProps;
}; email: IImprintFieldProps;
url: IImprintFieldProps;
}
const onClose = () => { interface IImprintFieldComponentProps {
if (window.location.href.endsWith("#imprint")) { field: IImprintFieldProps;
let location = window.location.href.replace("#imprint", ""); }
window.location.href = location;
}
};
const Imprint = () => { const ImprintField = ({ field }: IImprintFieldComponentProps) => (
const { <Link href={field.link}>{field.text}</Link>
imprintData: { name, address, phone, email, url, error }, );
} = useImprintData();
useEffect(() => { interface IImprintComponentProps {
console.error(error); imprint: IImprintProps;
}, [error]); }
return ( const Imprint = ({ imprint }: IImprintComponentProps) => (
<> <>
{!error ? ( <ListContainer>
<ListContainer> <Hl>About</Hl>
<Hl>About</Hl> <ItemList>
<ItemList> <ItemContainer>
<ItemContainer> <SHl>Imprint</SHl>
<SHl>Imprint</SHl> <Modal
<Modal element="text"
element="text" text="View Imprint"
text="View Imprint" condition={!window.location.href.endsWith("#imprint")}
condition={!window.location.href.endsWith("#imprint")} onClose={() => {
onClose={() => onClose()} if (window.location.href.endsWith("#imprint")) {
> let location = window.location.href.replace("#imprint", "");
<Headline>Legal Disclosure</Headline> window.location.href = location;
{error && <ErrorMessage>{error}</ErrorMessage>} }
<ModalSubHeadline> }}
Information in accordance with section 5 TMG >
</ModalSubHeadline> <Headline>Legal Disclosure</Headline>
{!error && ( <ModalSubHeadline>
<> Information in accordance with section 5 TMG
<Link href={name.link}>{name.text}</Link> </ModalSubHeadline>
<Link href={address.link}>{address.text}</Link> <>
<Link href={phone.link}>{phone.text}</Link> {imprint.name && <ImprintField field={imprint.name} />}
<Link href={email.link}>{email.text}</Link> {imprint.address && <ImprintField field={imprint.address} />}
<Link href={url.link}>{url.text}</Link> {imprint.email && <ImprintField field={imprint.email} />}
</> {imprint.phone && <ImprintField field={imprint.phone} />}
)} {imprint.url && <ImprintField field={imprint.url} />}
<Headline>Disclaimer</Headline> </>
<ModalSubHeadline>Accountability for content</ModalSubHeadline> <Headline>Disclaimer</Headline>
<Text> <ModalSubHeadline>Accountability for content</ModalSubHeadline>
The contents of our pages have been created with the utmost <Text>
care. However, we cannot guarantee the contents' accuracy, The contents of our pages have been created with the utmost care.
completeness or topicality. According to statutory provisions, However, we cannot guarantee the contents' accuracy, completeness
we are furthermore responsible for our own content on these or topicality. According to statutory provisions, we are
web pages. In this matter, please note that we are not obliged furthermore responsible for our own content on these web pages. In
to monitor the transmitted or saved information of third this matter, please note that we are not obliged to monitor the
parties, or investigate circumstances pointing to illegal transmitted or saved information of third parties, or investigate
activity. Our obligations to remove or block the use of circumstances pointing to illegal activity. Our obligations to
information under generally applicable laws remain unaffected remove or block the use of information under generally applicable
by this as per §§ 8 to 10 of the Telemedia Act (TMG). laws remain unaffected by this as per §§ 8 to 10 of the Telemedia
</Text> Act (TMG).
<ModalSubHeadline>Accountability for links</ModalSubHeadline> </Text>
<Text> <ModalSubHeadline>Accountability for links</ModalSubHeadline>
Responsibility for the content of external links (to web pages <Text>
of third parties) lies solely with the operators of the linked Responsibility for the content of external links (to web pages of
pages. No violations were evident to us at the time of third parties) lies solely with the operators of the linked pages.
linking. Should any legal infringement become known to us, we No violations were evident to us at the time of linking. Should
will remove the respective link immediately. any legal infringement become known to us, we will remove the
</Text> respective link immediately.
<ModalSubHeadline>Copyright</ModalSubHeadline> </Text>
<Text> <ModalSubHeadline>Copyright</ModalSubHeadline>
Our web pages and their contents are subject to German <Text>
copyright law. Unless expressly permitted by law, every form Our web pages and their contents are subject to German copyright
of utilizing, reproducing or processing works subject to law. Unless expressly permitted by law, every form of utilizing,
copyright protection on our web pages requires the prior reproducing or processing works subject to copyright protection on
consent of the respective owner of the rights. Individual our web pages requires the prior consent of the respective owner
reproductions of a work are only allowed for private use. The of the rights. Individual reproductions of a work are only allowed
materials from these pages are copyrighted and any for private use. The materials from these pages are copyrighted
unauthorized use may violate copyright laws. and any unauthorized use may violate copyright laws.
</Text> </Text>
</Modal> </Modal>
</ItemContainer> </ItemContainer>
</ItemList> </ItemList>
</ListContainer> </ListContainer>
) : ( </>
<></> );
)}
</>
);
};
export default Imprint; export default Imprint;

View file

@ -1,8 +1,6 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { handleResponse, ErrorMessage } from "./elements";
import selectedTheme from "./themeManager"; import selectedTheme from "./themeManager";
const SearchInput = styled.input` const SearchInput = styled.input`
@ -15,36 +13,17 @@ const SearchInput = styled.input`
color: ${selectedTheme.mainColor}; color: ${selectedTheme.mainColor};
`; `;
const useSearchProviders = () => { export interface ISearchProviderProps {
const [searchProviders, setSearchProviders] = useState({ name: string;
providers: [{ prefix: "", url: "" }], url: string;
error: false, prefix: string;
}); }
const fetchSearchProviders = useCallback(() => { interface ISearchBarProps {
(process.env.NODE_ENV === "production" providers: Array<ISearchProviderProps> | undefined;
? fetch("/data/search.json").then(handleResponse) }
: import("./data/search.json")
)
.then((jsonResponse) => {
setSearchProviders({ ...jsonResponse, error: false });
})
.catch((error) => {
setSearchProviders({ providers: [], error: error.message });
});
}, []);
useEffect(() => {
fetchSearchProviders();
}, [fetchSearchProviders]);
return { searchProviders, fetchSearchProviders };
};
const SearchBar = () => {
const {
searchProviders: { providers, error },
} = useSearchProviders();
const SearchBar = ({ providers }: ISearchBarProps) => {
let [input, setInput] = useState(""); let [input, setInput] = useState("");
const handleSearchQuery = (e: React.FormEvent) => { const handleSearchQuery = (e: React.FormEvent) => {
@ -67,12 +46,14 @@ const SearchBar = () => {
let searchQuery = queryArray.join(" "); let searchQuery = queryArray.join(" ");
let providerFound = false; let providerFound = false;
providers.forEach((provider) => { if (providers) {
if (provider.prefix === prefix) { providers.forEach((provider: ISearchProviderProps) => {
providerFound = true; if (provider.prefix === prefix) {
window.location.href = provider.url + searchQuery; providerFound = true;
} window.location.href = provider.url + searchQuery;
}); }
});
}
if (!providerFound) if (!providerFound)
window.location.href = "https://google.com/search?q=" + query; window.location.href = "https://google.com/search?q=" + query;
@ -80,7 +61,6 @@ const SearchBar = () => {
return ( return (
<form onSubmit={(e) => handleSearchQuery(e)}> <form onSubmit={(e) => handleSearchQuery(e)}>
{error && <ErrorMessage>{error}</ErrorMessage>}
<SearchInput <SearchInput
type="text" type="text"
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}

View file

@ -1,17 +1,11 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import Select from "react-select"; import Select from "react-select";
import searchData from "./data/search.json"; import { ISearchProviderProps } from "./searchBar";
import selectedTheme, { setTheme, IThemeProps } from "./themeManager";
import selectedTheme, { setTheme } from "./themeManager"; import { Button, Headline as hl } from "./elements";
import {
handleResponse,
Button,
ErrorMessage,
Headline as hl,
} from "./elements";
import Modal from "./modal"; import Modal from "./modal";
@ -91,76 +85,58 @@ const SelectorStyle = {
}, },
}; };
const useThemeData = () => { interface ISettingsProps {
const [themeData, setThemeData] = useState({ themes: [], error: false }); themes: Array<IThemeProps> | undefined;
providers: Array<ISearchProviderProps> | undefined;
}
const fetchThemeData = useCallback(() => { const Settings = ({ themes, providers }: ISettingsProps) => {
(process.env.NODE_ENV === "production"
? fetch("/data/themes.json").then(handleResponse)
: import("./data/themes.json")
)
.then((jsonResponse) => {
setThemeData({ ...jsonResponse, error: false });
})
.catch((error) => {
setThemeData({ themes: [], error: error.message });
});
}, []);
useEffect(() => {
fetchThemeData();
}, [fetchThemeData]);
return { themeData, fetchThemeData };
};
const Settings = () => {
const [newTheme, setNewTheme] = useState(); const [newTheme, setNewTheme] = useState();
const { if (themes && providers) {
themeData: { themes, error }, return (
} = useThemeData(); <Modal element="icon" icon="settings">
{themes && (
<SelectContainer>
<Headline>Theme:</Headline>
<FormContainer>
<Select
options={themes}
defaultValue={selectedTheme}
onChange={(e: any) => {
setNewTheme(e);
}}
styles={SelectorStyle}
/>
useEffect(() => { <Button onClick={() => setTheme(JSON.stringify(newTheme))}>
console.log(newTheme); Apply
}, [newTheme]); </Button>
<Button onClick={() => window.location.reload()}>Refresh</Button>
return ( </FormContainer>
<Modal element="icon" icon="settings"> </SelectContainer>
{error && <ErrorMessage>{error}</ErrorMessage>} )}
<SelectContainer> {providers && (
<Headline>Theme:</Headline> <Table>
<FormContainer> <tbody>
<Select <TableRow>
options={themes} <HeadCell>Search Provider</HeadCell>
defaultValue={selectedTheme} <HeadCell>Prefix</HeadCell>
onChange={(e: any) => { </TableRow>
setNewTheme(e); {providers.map((provider, index) => (
}} <TableRow key={provider.name + index}>
styles={SelectorStyle} <TableCell>{provider.name}</TableCell>
/> <TableCell>{provider.prefix}</TableCell>
</TableRow>
<Button onClick={() => setTheme(JSON.stringify(newTheme))}> ))}
Apply </tbody>
</Button> </Table>
<Button onClick={() => window.location.reload()}>Refresh</Button> )}
</FormContainer> </Modal>
</SelectContainer> );
<Table> } else {
<tbody> return <></>;
<TableRow> }
<HeadCell>Search Provider</HeadCell>
<HeadCell>Prefix</HeadCell>
</TableRow>
{searchData.providers.map((provider, index) => (
<TableRow key={provider.name + index}>
<TableCell>{provider.name}</TableCell>
<TableCell>{provider.prefix}</TableCell>
</TableRow>
))}
</tbody>
</Table>
</Modal>
);
}; };
export default Settings; export default Settings;

View file

@ -1,4 +1,4 @@
interface IThemeProps { export interface IThemeProps {
label: string; label: string;
value: number; value: number;
mainColor: string; mainColor: string;

3370
yarn.lock

File diff suppressed because it is too large Load diff