Merge branch 'adjust/themes' into 'main'

Merge Theme Adjustments

See merge request neshura-websites/www!10
This commit is contained in:
Neshura 2022-12-18 22:20:42 +00:00
commit 7c7d60bab7
19 changed files with 280 additions and 185 deletions

12
.gitignore vendored
View file

@ -1,9 +1,9 @@
# do not track installed modules
node_modules/
.vscode/
/node_modules/
/.vscode/
# do not track built files
.next/
/.next/
*.tsbuildinfo
next-env.d.ts
@ -14,6 +14,6 @@ yarn-error.log*
.pnpm-debug.log*
# production
build/
data/
confs/
/build/
/data/
/confs/

1
components/fetcher.tsx Normal file
View file

@ -0,0 +1 @@
export const fetcher = (url:string) => fetch(url).then((res) => res.json());

View file

@ -1,8 +1,7 @@
import PageFooter from './footer'
import PageNavbar from './navbar'
import Script from 'next/script'
import { Page, Main } from './styles/generic'
import PageFooter from './footer';
import PageNavbar from './navbar';
import Script from 'next/script';
import { Page, Main } from './styles/generic';
const Layout = ({ children }: { children: React.ReactNode }) => {
return (

View file

@ -1,13 +1,7 @@
import { usePathname } from 'next/navigation'
import { NavBar, NavLink, NavWrap } from './styles/navbar';
import { StyleSelector, StyleSelectorPlaceholder } from './themeselector';
const navLinks = [
{ name: "Home", href: "/" },
{ name: "About", href: "/about" },
{ name: "Games", href: "/games" },
{ name: "Services", href: "/services" }
]
import Links from '../public/data/navbar.json';
const PageNavbar = () => {
const path = usePathname();
@ -16,8 +10,8 @@ const PageNavbar = () => {
<NavWrap>
<StyleSelector></StyleSelector>
<NavBar>
{navLinks.map((item) => (
<NavLink active={path == item.href ? true : false} key={item.name} href={item.href}>
{Links.links.map((item) => (
<NavLink active={path === item.href ? +true : +false} key={item.name} href={item.href}>
{item.name}
</NavLink>
))}

View file

@ -12,17 +12,23 @@ interface OnlinePropType {
// replaces .title
export const PageTitle = styled.h1`
margin: 0;
padding: 0.25rem 1rem;
line-height: 1.15;
font-size: 4rem;
text-align: center;
background-color: ${({ theme }) => theme.colors.background ? theme.colors.background : ""};
border-radius: 15px;
`;
// replaces .description
export const PageDescription = styled.p`
background-color: ${({ theme }) => theme.colors.background ? theme.colors.background : ""};
padding: 0.25rem 0.5rem;
margin: 4rem 0;
line-height: 1.5;
font-size: 1.5rem;
text-align: center;
border-radius: 10px;
`;
// replaces .grid
@ -43,25 +49,26 @@ const CardStyle = css`
height: 240px;
`;
const CardLink = styled(Link)`
export const CardLink = styled(Link)`
${CardStyle}
`;
const CardStyleWrap = styled.div`
export const CardStyleWrap = styled.div`
${CardStyle}
`;
// replaces .card & .contentcard
export const PageCard = styled.div`
margin: 1rem;
padding: 1.5rem 0.7rem 1.5rem 0.7rem;
padding: 23px 10px;
text-align: center;
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.background};
text-decoration: none;
border: 1px solid;
border: 2px solid;
border-radius: 10px;
border-color: ${({ theme }) => theme.colors.primary};
transition: color 0.15s ease, border-color 0.15s ease;
transition: all 0.1s linear;
width: 300px;
height: 200px;
display: flex;
@ -82,11 +89,15 @@ export const PageCard = styled.div`
${CardStyleWrap}:focus,${CardStyleWrap}:active,${CardStyleWrap}:hover & {
color: ${({ theme }) => theme.colors.secondary};
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.invertButtons ?
theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background : theme.colors.background};
}
${CardLink}:focus,${CardLink}:active,${CardLink}:hover & {
color: ${({ theme }) => theme.colors.secondary};
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.invertButtons ?
theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background : theme.colors.background};
}
`;
@ -110,7 +121,6 @@ const OnlineStatus = styled.p<OnlinePropType>`
return ret;
}};
padding: 0.2rem;
background-color: ${({ theme }) => theme.colors.background};
border: 1px solid;
border-color: ${({ theme }) => theme.colors.primary};
border-radius: 5px;
@ -119,13 +129,25 @@ const OnlineStatus = styled.p<OnlinePropType>`
top: 100; right: 50; bottom: 0; left: 50;
offset-position: bottom 10px;
transition: color 0.15s ease, border-color 0.15s ease;
background-color: ${({ theme }) => theme.colors.background};
background-image: ${({ theme }) => theme.backgroundImage ?
"linear-gradient("
+ theme.colors.background + "," + theme.colors.background +
"), url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
${CardStyleWrap}:focus,${CardStyleWrap}:active,${CardStyleWrap}:hover & {
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.invertButtons ?
theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background : theme.colors.background};
}
${CardLink}:focus,${CardLink}:active,${CardLink}:hover & {
border-color: ${({ theme }) => theme.colors.secondary};
background-color: ${({ theme }) => theme.invertButtons ?
theme.colors.backgroundAlt ? theme.colors.backgroundAlt : theme.colors.background : theme.colors.background};
}
`;
@ -136,16 +158,16 @@ const CardContentWarning = styled.p`
`;
// replaces .contentIcon
const CardContentTitleIcon = styled.div`
const CardContentTitleIcon = styled(Image)`
object-fit: "contain";
margin-right: 0.4rem;
position: relative;
margin-right: 8px;
aspect-ratio: 1;
height: 1.5rem;
height: 28px;
`;
// replaces .contentTitle
const CardContentTitleWrap = styled.div`
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
@ -163,9 +185,7 @@ const CardContentTitle = ({ content }: { content: Service | Game }) => {
<CardContentTitleWrap>
{
content.icon ? (
<CardContentTitleIcon>
<Image alt="icon" src={content.icon} fill></Image>
</CardContentTitleIcon>
<CardContentTitleIcon alt="icon" src={content.icon} width="28" height="28" sizes='10vw'/>
) : (<></>)
}
<h2>{content.name}</h2>

View file

@ -10,6 +10,10 @@ export const StyledBody = styled.body`
export const Page = styled.div`
width: 100%;
background-color: ${({ theme }) => theme.colors.background};
background-image: ${({ theme }) => theme.backgroundImage ? "url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
`
export const Main = styled.main`

View file

@ -2,7 +2,7 @@ import styled from 'styled-components'
import Link from 'next/link';
interface ActivePropType {
active?: false | true;
active?: number;
}
export const NavWrap = styled.div`
@ -24,18 +24,33 @@ export const NavBar = styled.nav`
`
export const NavLink = styled(Link) <ActivePropType>`
color: ${props => props.active ? ({ theme }) => theme.colors.secondary : ({ theme }) => theme.colors.primary};
color: ${props => props.active ?
({ theme }) => theme.invertButtons ?
theme.colors.text ? theme.colors.text : theme.colors.secondary : theme.colors.secondary :
({ theme }) => theme.colors.primary};
background-color: ${props => props.active ?
({ theme }) => theme.invertButtons ?
theme.colors.secondary : theme.colors.background :
({ theme }) => theme.colors.background};
padding: 2px 6px;
border: 2px solid;
margin: 0.2rem;
border: 1px solid;
border-radius: 5px;
padding: 0.2rem 0.5rem;
display: flex;
justify-content: center;
align-items: center;
transition: color 0.15s ease, border-color 0.15s ease;
transition: all 0.1s ease;
&:hover {
color: ${({ theme }) => theme.colors.secondary};
}
color: ${({ theme }) => theme.invertButtons ?
theme.colors.text ? theme.colors.text : theme.colors.primary :
theme.colors.secondary};
background-color: ${({ theme }) => theme.invertButtons ?
theme.colors.secondary :
theme.colors.background};
}
`

View file

@ -1,36 +1,46 @@
import styled from 'styled-components';
interface DisplayPropType {
show?: false | true;
focus?: number,
show?: number;
}
interface ActivePropType {
active?: false | true;
active?: number;
}
export const ThemeDropDown = styled.div`
margin-left: 1%;
min-width: 180px;
color: ${({ theme }) => theme.colors.primary}
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.colors.primary};
`
export const ThemeDropDownButton = styled.button<DisplayPropType>`
width: 160px;
border: 1px solid;
border: 2px solid;
border-radius: 5px;
background-color: ${({ theme }) => theme.colors.background};
padding: 0.2rem 0.5rem;
background-color: ${ props => props.focus ?
({ theme }) => theme.invertButtons ? theme.colors.secondary : theme.colors.background :
({ theme }) => theme.colors.background};
padding: 2px 6px;
cursor: pointer;
color: ${({ theme }) => theme.colors.primary};
color: ${props => props.focus ? ({ theme }) => theme.invertButtons ?
theme.colors.text ? theme.colors.text : theme.colors.primary : theme.colors.secondary :
({ theme }) => theme.colors.primary};
transition-property: color, border-bottom-left-radius, border-bottom-right-radius;
transition-property: color, border-bottom-left-radius, border-bottom-right-radius, background-color;
transition-timing-function: ease;
transition-duration: 0.15s;
transition-delay: 0s, ${ props => props.show ? "0s, 0s" : "0.6s, 0.6s" }, 0s;
&:focus,:hover {
color: ${({ theme }) => theme.colors.secondary};
color: ${({ theme }) => theme.invertButtons ?
theme.colors.text ? theme.colors.text : theme.colors.primary :
theme.colors.secondary};
background-color: ${({ theme }) => theme.invertButtons ?
theme.colors.secondary :
theme.colors.background};
}
border-bottom-left-radius: ${ props => props.show ? "0" : "" };
@ -40,7 +50,14 @@ export const ThemeDropDownButton = styled.button<DisplayPropType>`
export const ThemeDropDownOptions = styled.div<DisplayPropType>`
position: absolute;
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.background};
background-image: ${({ theme }) => theme.backgroundImage ?
"linear-gradient("
+ theme.colors.background + "," + theme.colors.background +
"), url(" + theme.backgroundImage + ")" : ""};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
display: flex;
flex-direction: column;
min-width: 160px;
@ -49,7 +66,7 @@ export const ThemeDropDownOptions = styled.div<DisplayPropType>`
border-radius: 5px;
border-top-left-radius: 0px; border-top-right-radius: 0px;
z-index: 1;
overflow: hidden;
overflow: ${ props => props.show ? "scroll" : "hidden" };
max-height: ${ props => props.show ? "20%" : "0%" };
visibility: ${ props => props.show ? "visible" : "hidden" };
@ -61,7 +78,8 @@ export const ThemeDropDownOptions = styled.div<DisplayPropType>`
export const ThemeDropDownOption = styled.button<ActivePropType>`
color: ${ props => props.active ? ({ theme }) => theme.colors.secondary : ({ theme }) => theme.colors.primary };
background-color: ${({ theme }) => theme.colors.background};
background-color: transparent;
cursor: pointer;
align-self: center;
border: 0px solid;
padding: 0.2rem 0.5rem;

View file

@ -57,16 +57,3 @@ export const amoledTheme: DefaultTheme = {
offline: '#ff0000',
},
}
export const amoled2Theme: DefaultTheme = {
themeName: "AMOLED Theme 2",
themeId: 3,
colors: {
background: '#000000',
primary: '#00ffaa',
secondary:'#aa00ff',
online: '#00ff00',
loading: '#ff5300',
offline: '#ff0000',
},
}

View file

@ -1,17 +1,12 @@
import { useUpdateTheme } from "../pages/_app";
import { Fragment, useContext, useState } from 'react';
import { useContext, useState } from 'react';
import { ThemeContext, DefaultTheme } from "styled-components";
import { darkTheme, amoledTheme, lightTheme, amoled2Theme } from './themes';
import { darkTheme, lightTheme } from './themes';
import { ThemeDropDown, ThemeDropDownButton, ThemeDropDownOption, ThemeDropDownOptions } from "./styles/themedropdown";
const themes = [
lightTheme,
darkTheme,
amoledTheme,
amoled2Theme,
]
import Themes from '../public/data/themes.json';
export const StyleSelector = () => {
const themes: DefaultTheme[] = Themes.themes;
const updateTheme = useUpdateTheme();
const currentTheme = useContext(ThemeContext);
const [selectedTheme, setSelectedTheme] = useState(themes[currentTheme.themeId]);
@ -21,7 +16,6 @@ export const StyleSelector = () => {
}
const updateThemeWithStorage = (newTheme: DefaultTheme) => {
if (newTheme.themeId === lightTheme.themeId) {
updateLightTheme(newTheme);
}
@ -38,27 +32,25 @@ export const StyleSelector = () => {
localStorage.setItem("theme", newTheme.themeId.toString());
updateTheme(newTheme)
}
else {
}
}
const [test, setTest] = useState(false);
const [visible, setVisible] = useState(false);
const [buttonFocus, setButtonFocus] = useState(visible);
function handleBlur(event:any) {
console.log(event)
if (!event.currentTarget.contains(event.relatedTarget)) {
setTest(false);
setButtonFocus(false);
setVisible(false);
}
}
return (
<ThemeDropDown onBlur={(event) => handleBlur(event)}>
<ThemeDropDownButton show={test} onClick={() => setTest(test => !test)}>{selectedTheme.themeName}</ThemeDropDownButton>
<ThemeDropDownOptions id="themesDropdown" show={test}>
<ThemeDropDownButton focus={+buttonFocus} show={+visible} onFocus={() => setButtonFocus(true)} onClick={() => setVisible(visible => !visible)}>{selectedTheme.themeName}
</ThemeDropDownButton>
<ThemeDropDownOptions id="themesDropdown" show={+visible}>
{themes.map((theme) => (
<ThemeDropDownOption active={theme === selectedTheme} key={theme.themeId} onClick={() => updateThemeWithStorage(theme)}>
<ThemeDropDownOption active={theme.themeId === selectedTheme.themeId ? 1 : 0} key={theme.themeId} onClick={() => updateThemeWithStorage(theme)}>
{theme.themeName}
</ThemeDropDownOption>
))}
@ -73,27 +65,14 @@ export const StyleSelectorPlaceholder = () => {
)
}
export function getTheme(themeId: number): DefaultTheme {
let theme: DefaultTheme;
export function getTheme(themeId: number, themes: DefaultTheme[]): DefaultTheme {
let retTheme: DefaultTheme = darkTheme;
switch (themeId) {
case 0:
theme = lightTheme;
break;
case 1:
theme = darkTheme;
break;
case 2:
theme = amoledTheme;
break;
case 3:
theme = amoled2Theme;
break;
default:
theme = darkTheme
}
themes.forEach((theme) => {
if (theme.themeId === themeId) { retTheme = theme};
})
return theme;
return retTheme;
}
export default StyleSelector;

View file

@ -7,6 +7,7 @@ import { DefaultTheme, ThemeProvider } from 'styled-components';
import { createContext, useContext, useEffect, useState } from 'react'
import { darkTheme, GlobalStyle } from '../components/themes';
import { getTheme } from '../components/themeselector';
import Themes from '../public/data/themes.json';
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
@ -16,8 +17,7 @@ export type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
const ThemeUpdateContext = createContext(
export const ThemeUpdateContext = createContext(
(theme: DefaultTheme) => console.error("attempted to set theme outside of a ThemeUpdateContext.Provider")
)
@ -25,7 +25,9 @@ export const useUpdateTheme = () => useContext(ThemeUpdateContext);
export default function Website({ Component, pageProps }: AppPropsWithLayout) {
const loadedThemes = Themes.themes;
const [selectedTheme, setselectedTheme] = useState(darkTheme);
const [themes, setThemes] = useState(loadedThemes);
useEffect(() => {
@ -34,10 +36,9 @@ export default function Website({ Component, pageProps }: AppPropsWithLayout) {
// if theme data differs set it
// if not just exit
if (storedThemeIdTemp && parseInt(storedThemeIdTemp) !== selectedTheme.themeId) {
setselectedTheme(getTheme(parseInt(storedThemeIdTemp)))
setselectedTheme(getTheme(parseInt(storedThemeIdTemp), themes))
}
}, [selectedTheme])
}, [selectedTheme, themes])
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => (

View file

@ -1,21 +0,0 @@
import fsPromises from 'fs/promises'
import path from 'path'
import { Service, Status } from '../../interfaces/CardTypes';
export default async function ServicesAPI(req: any, res: any) {
try {
const filePath = path.join(process.cwd(), '/public/pages.json')
const data = await fsPromises.readFile(filePath)
.then((file) => JSON.parse(file.toString()));
data.services.forEach((service: Service) => {
service.status = Status.loading;
});
res.status(200).json(data.services);
}
catch (error) {
console.log(error);
res.status(500).json({ error: 'Error reading data' });
}
}

View file

@ -1,12 +1,11 @@
import Head from 'next/head'
import fsPromises from 'fs/promises'
import path from 'path'
import { EntryList, Game } from '../interfaces/CardTypes';
import { Game } from '../interfaces/CardTypes';
import { PageContentBox, PageDescription, PageTitle, CardContentGame } from '../components/styles/content'
import Link from 'next/link'
import GameList from '../public/pages.json';
function Servers(props: EntryList) {
const serverList = props.games
function Servers() {
// TODO: unuggly this shit
const serverList: Game[] = JSON.parse(JSON.stringify(GameList.games));
return (
<>
<Head>
@ -32,12 +31,4 @@ function Servers(props: EntryList) {
)
}
export async function getServerSideProps() {
const filePath = path.join(process.cwd(), '/public/pages.json')
const jsonData = await fsPromises.readFile(filePath)
const list = JSON.parse(jsonData.toString())
return { props: list }
}
export default Servers

View file

@ -1,6 +1,6 @@
import Head from 'next/head'
import Link from 'next/link';
import { PageTitle, PageDescription, PageContentBox, PageCard } from '../components/styles/content';
import { PageTitle, PageDescription, PageContentBox, PageCard, CardLink } from '../components/styles/content';
export default function Home() {
return (
@ -18,26 +18,26 @@ export default function Home() {
Feel free to look around
</PageDescription>
<PageContentBox>
<PageCard key="about">
<Link href="/about">
<CardLink key="about" href="/about">
<PageCard>
<h2>About &rarr;</h2>
<p>Useless Info, don&apos;t bother</p>
</Link>
</PageCard>
</CardLink>
<PageCard key="servers">
<Link href="/games">
<CardLink key="servers" href="/games">
<PageCard>
<h2>Games &rarr;</h2>
<p>List of all available Servers</p>
</Link>
</PageCard>
</CardLink>
<PageCard key="services">
<Link href="/services">
<CardLink key="services" href="/services">
<PageCard>
<h2>Services &rarr;</h2>
<p>List of available Services</p>
</Link>
</PageCard>
</CardLink>
</PageContentBox>
</>
)

View file

@ -1,20 +1,19 @@
import Head from 'next/head'
import Link from 'next/link'
import { Service, Status, ServiceType, ServiceLocation } from '../interfaces/CardTypes';
import Dockerode from 'dockerode';
import { ReactElement } from 'react'
import useSWR from 'swr';
import { PageCard, CardContentService, PageContentBox, PageDescription, PageTitle } from '../components/styles/content';
import { CardContentService, PageContentBox, PageDescription, PageTitle } from '../components/styles/content';
import ServiceList from '../public/pages.json';
const fetcher = (url: string) => fetch(url).then((res) => res.json())
function Services() {
const { initialData, fullData, loadingInitial, loadingFull, error } = useServices();
const { initialData, fullData, loadingFull, error } = useServices();
let content: ReactElement = <></>;
if (error) { content = <div>Error loading data</div> }
else if (loadingInitial) { content = <div>Loading</div> }
else if (loadingFull) {
content =
<PageContentBox>
@ -22,7 +21,6 @@ function Services() {
<CardContentService key={item.name} content={item} />
))}
</PageContentBox>
}
else if (fullData) {
content =
@ -39,7 +37,7 @@ function Services() {
return (
<>
<Head>
<title>Neshura Servers</title>
<title>Neshweb - Services</title>
<meta charSet='utf-8' />
<link rel="icon" href="/favicon.ico" />
<meta name="description" content="Lists all available Services, most likely up-to-date" />
@ -171,17 +169,18 @@ const fetchFullDataArray = (containerData: Dockerode.ContainerInfo[], dataSet: S
function useServices() {
const { data: containerData, error: containerError } = useSWR('/api/containers', fetcher);
const { data: initialData, error: initialError } = useSWR('/api/services', fetcher);
const loadingInitial = !initialData && !initialError
const { data: fullData, error: fullError } = useSWR((initialData && containerData) ? [containerData, initialData] : null, fetchFullDataArray)
// TODO: unfuck this
const initialData: Service[] = JSON.parse(JSON.stringify(ServiceList.services));
initialData.forEach((service) => {
if (service.status === undefined) service.status = Status.loading;
})
const { data: fullData, error: fullError } = useSWR((containerData) ? [containerData, initialData] : null, fetchFullDataArray)
const loadingFull = !fullData && !fullError
return {
initialData,
fullData,
loadingInitial,
loadingFull,
error: initialError || fullError || containerError,
error: fullError || containerError,
};
}

20
public/data/navbar.json Normal file
View file

@ -0,0 +1,20 @@
{
"links": [
{
"name": "Home",
"href": "/"
},
{
"name": "About",
"href": "/about"
},
{
"name": "Games",
"href": "/games"
},
{
"name": "Services",
"href": "/services"
}
]
}

84
public/data/themes.json Normal file
View file

@ -0,0 +1,84 @@
{
"themes": [
{
"themeName": "Cancer - Blue",
"themeId": 0,
"colors": {
"background": "#ffffff",
"primary": "#00AAFF",
"secondary": "#FF5500",
"online": "#2BFF00",
"loading": "#D400FF",
"offline": "#FF002B"
}
},
{
"themeName": "Dark - Blue",
"themeId": 1,
"colors": {
"background": "#161616",
"primary": "#00AAFF",
"secondary": "#FF5500",
"online": "#2BFF00",
"loading": "#D400FF",
"offline": "#FF002B"
}
},
{
"themeName": "AMOLED - Blue",
"themeId": 2,
"colors": {
"background": "#000000",
"primary": "#00AAFF",
"secondary": "#FF5500",
"online": "#2BFF00",
"loading": "#D400FF",
"offline": "#FF002B"
}
},
{
"themeName": "AMOLED - Purple",
"themeId": 3,
"colors": {
"background": "#000000",
"primary": "#886aff",
"secondary": "#E1FF6A",
"online": "#00ff00",
"loading": "#FF6A97",
"offline": "#ff0000"
}
},
{
"themeName": "Nordlys",
"themeId": 4,
"backgroundImage": "https://images4.alphacoders.com/112/1123390.jpg",
"invertButtons": true,
"colors": {
"background": "#0008",
"backgroundAlt": "#000d",
"primary": "#ccc",
"secondary": "#00C7C7",
"text": "#000",
"online": "#00ff00",
"loading": "#0063C7",
"offline": "#ff0000"
}
},
{
"themeName": "dev",
"themeId": 5,
"backgroundImage": "https://images4.alphacoders.com/112/1123390.jpg",
"invertButtons": true,
"colors": {
"background": "#0000",
"backgroundAlt": "#0000",
"primary": "#ccc",
"secondary": "#00C7C7",
"text": "#000",
"online": "#00ff00",
"loading": "#0063C7",
"offline": "#ff0000"
}
}
]
}

View file

@ -3,7 +3,7 @@
{
"name": "Nextcloud",
"icon": "/icons/nextcloud-logo.svg",
"href": "https://nextcloud.neshweb.net",
"href": "https://nextcloud.neshweb.net/",
"desc": "Self-hosted Cloud Storage Service",
"warn": "Note: Registration requires approval",
"type": "docker",
@ -13,7 +13,7 @@
{
"name": "Komga",
"icon": "/icons/komga-logo.png",
"href": "https://komga.neshweb.net",
"href": "https://komga.neshweb.net/",
"desc": "Self-hosted Comic Library",
"warn": "Note: Registration only via Admin",
"type": "docker",
@ -32,7 +32,7 @@
{
"name": "PeerTube",
"icon": "/icons/peertube-logo.svg",
"href": "https://tube.neshweb.net",
"href": "https://tube.neshweb.net/",
"desc": "Self-hosted PeerTube Instance",
"warn": "Note: Registration only via Admin",
"type": "docker",
@ -42,7 +42,7 @@
{
"name": "Mastodon",
"icon": "/icons/mastodon-logo.svg",
"href": "https://mastodon.neshweb.net",
"href": "https://mastodon.neshweb.net/",
"desc": "Self-hosted Mastodon Instance",
"warn": "Note: Registration requires approval",
"type": "docker",

4
styled.d.ts vendored
View file

@ -4,10 +4,14 @@ declare module 'styled-components' {
export interface DefaultTheme {
themeName: string,
themeId: number,
backgroundImage?: string,
invertButtons?: boolean,
colors: {
background: string,
backgroundAlt?: string,
primary: string,
secondary: string,
text?: string,
online: string,
loading: string,
offline: string,