import { isTouchscreenDevice } from "../lib/isTouchscreenDevice"; import { createGlobalStyle } from "styled-components"; import { connectState } from "../redux/connector"; import { Children } from "../types/Preact"; import { useEffect } from "preact/hooks"; import { createContext } from "preact"; import { Helmet } from "react-helmet"; export type Variables = | "accent" | "background" | "foreground" | "block" | "message-box" | "mention" | "success" | "warning" | "error" | "hover" | "scrollbar-thumb" | "scrollbar-track" | "primary-background" | "primary-header" | "secondary-background" | "secondary-foreground" | "secondary-header" | "tertiary-background" | "tertiary-foreground" | "status-online" | "status-away" | "status-busy" | "status-streaming" | "status-invisible"; export type Fonts = 'Open Sans' | 'Inter' | 'Atkinson Hyperlegible' | 'Roboto' | 'Noto Sans' | 'Lato' | 'Bree Serif' | 'Montserrat' | 'Poppins' | 'Raleway' | 'Ubuntu' | 'Comic Neue'; export type MonoscapeFonts = 'Fira Code' | 'Roboto Mono' | 'Source Code Pro' | 'Space Mono' | 'Ubuntu Mono'; export type Theme = { [variable in Variables]: string; } & { light?: boolean; font?: Fonts; css?: string; monoscapeFont?: MonoscapeFonts; }; export interface ThemeOptions { preset?: string; ligatures?: boolean; custom?: Partial; } export const FONTS: Record void }> = { "Open Sans": { name: "Open Sans", load: async () => { await import("@fontsource/open-sans/300.css"); await import("@fontsource/open-sans/400.css"); await import("@fontsource/open-sans/600.css"); await import("@fontsource/open-sans/700.css"); await import("@fontsource/open-sans/400-italic.css"); } }, Inter: { name: "Inter", load: async () => { await import("@fontsource/inter/300.css"); await import("@fontsource/inter/400.css"); await import("@fontsource/inter/600.css"); await import("@fontsource/inter/700.css"); } }, "Atkinson Hyperlegible": { name: "Atkinson Hyperlegible", load: async () => { await import("@fontsource/atkinson-hyperlegible/400.css"); await import("@fontsource/atkinson-hyperlegible/700.css"); await import("@fontsource/atkinson-hyperlegible/400-italic.css"); } }, "Roboto": { name: "Roboto", load: async () => { await import("@fontsource/roboto/400.css"); await import("@fontsource/roboto/700.css"); await import("@fontsource/roboto/400-italic.css"); } }, "Noto Sans": { name: "Noto Sans", load: async () => { await import("@fontsource/noto-sans/400.css"); await import("@fontsource/noto-sans/700.css"); await import("@fontsource/noto-sans/400-italic.css"); } }, "Bree Serif": { name: "Bree Serif", load: () => import("@fontsource/bree-serif/400.css") }, "Lato": { name: "Lato", load: async () => { await import("@fontsource/lato/300.css"); await import("@fontsource/lato/400.css"); await import("@fontsource/lato/700.css"); await import("@fontsource/lato/400-italic.css"); } }, "Montserrat": { name: "Montserrat", load: async () => { await import("@fontsource/montserrat/300.css"); await import("@fontsource/montserrat/400.css"); await import("@fontsource/montserrat/600.css"); await import("@fontsource/montserrat/700.css"); await import("@fontsource/montserrat/400-italic.css"); } }, "Poppins": { name: "Poppins", load: async () => { await import("@fontsource/poppins/300.css"); await import("@fontsource/poppins/400.css"); await import("@fontsource/poppins/600.css"); await import("@fontsource/poppins/700.css"); await import("@fontsource/poppins/400-italic.css"); } }, "Raleway": { name: "Raleway", load: async () => { await import("@fontsource/raleway/300.css"); await import("@fontsource/raleway/400.css"); await import("@fontsource/raleway/600.css"); await import("@fontsource/raleway/700.css"); await import("@fontsource/raleway/400-italic.css"); } }, "Ubuntu": { name: "Ubuntu", load: async () => { await import("@fontsource/ubuntu/300.css"); await import("@fontsource/ubuntu/400.css"); await import("@fontsource/ubuntu/500.css"); await import("@fontsource/ubuntu/700.css"); await import("@fontsource/ubuntu/400-italic.css"); } }, "Comic Neue": { name: "Comic Neue", load: async () => { await import("@fontsource/comic-neue/300.css"); await import("@fontsource/comic-neue/400.css"); await import("@fontsource/comic-neue/700.css"); await import("@fontsource/comic-neue/400-italic.css"); } } }; export const MONOSCAPE_FONTS: Record void }> = { "Fira Code": { name: "Fira Code", load: () => import("@fontsource/fira-code/400.css") }, "Roboto Mono": { name: "Roboto Mono", load: () => import("@fontsource/roboto-mono/400.css") }, "Source Code Pro": { name: "Source Code Pro", load: () => import("@fontsource/source-code-pro/400.css") }, "Space Mono": { name: "Space Mono", load: () => import("@fontsource/space-mono/400.css") }, "Ubuntu Mono": { name: "Ubuntu Mono", load: () => import("@fontsource/ubuntu-mono/400.css") } }; export const FONT_KEYS = Object.keys(FONTS).sort(); export const MONOSCAPE_FONT_KEYS = Object.keys(MONOSCAPE_FONTS).sort(); // Generated from https://gitlab.insrt.uk/revolt/community/themes export const PRESETS: { [key: string]: Theme } = { light: { light: true, accent: "#FD6671", background: "#F6F6F6", foreground: "#101010", block: "#414141", "message-box": "#F1F1F1", mention: "rgba(251, 255, 0, 0.40)", success: "#65E572", warning: "#FAA352", error: "#F06464", hover: "rgba(0, 0, 0, 0.2)", "scrollbar-thumb": "#CA525A", "scrollbar-track": "transparent", "primary-background": "#FFFFFF", "primary-header": "#F1F1F1", "secondary-background": "#F1F1F1", "secondary-foreground": "#888888", "secondary-header": "#F1F1F1", "tertiary-background": "#4D4D4D", "tertiary-foreground": "#646464", "status-online": "#3ABF7E", "status-away": "#F39F00", "status-busy": "#F84848", "status-streaming": "#977EFF", "status-invisible": "#A5A5A5", }, dark: { light: false, accent: "#FD6671", background: "#191919", foreground: "#F6F6F6", block: "#2D2D2D", "message-box": "#363636", mention: "rgba(251, 255, 0, 0.06)", success: "#65E572", warning: "#FAA352", error: "#F06464", hover: "rgba(0, 0, 0, 0.1)", "scrollbar-thumb": "#CA525A", "scrollbar-track": "transparent", "primary-background": "#242424", "primary-header": "#363636", "secondary-background": "#1E1E1E", "secondary-foreground": "#C8C8C8", "secondary-header": "#2D2D2D", "tertiary-background": "#4D4D4D", "tertiary-foreground": "#848484", "status-online": "#3ABF7E", "status-away": "#F39F00", "status-busy": "#F84848", "status-streaming": "#977EFF", "status-invisible": "#A5A5A5", }, }; const keys = Object.keys(PRESETS.dark); const GlobalTheme = createGlobalStyle<{ theme: Theme }>` :root { ${(props) => (Object.keys(props.theme) as Variables[]).map((key) => { if (!keys.includes(key)) return; return `--${key}: ${props.theme[key]};`; })} } `; export const ThemeContext = createContext({} as any); interface Props { children: Children; options?: ThemeOptions; } function Theme({ children, options }: Props) { const theme: Theme = { ...PRESETS["dark"], ...(PRESETS as any)[options?.preset as any], ...options?.custom }; const root = document.documentElement.style; useEffect(() => { const font = theme.font ?? 'Inter'; root.setProperty('--font', `"${font}"`); FONTS[font].load(); }, [ theme.font ]); useEffect(() => { const font = theme.monoscapeFont ?? 'Fira Code'; root.setProperty('--monoscape-font', `"${font}"`); MONOSCAPE_FONTS[font].load(); }, [ theme.monoscapeFont ]); useEffect(() => { root.setProperty('--ligatures', options?.ligatures ? 'normal' : 'none'); }, [ options?.ligatures ]); useEffect(() => { const resize = () => root.setProperty('--app-height', `${window.innerHeight}px`); resize(); window.addEventListener('resize', resize); return () => window.removeEventListener('resize', resize); }, []); return ( {theme.css && (