From 414dcd51c00632ce44de6bacd1d202e5d4fcc7bb Mon Sep 17 00:00:00 2001 From: brecert Date: Mon, 6 Sep 2021 06:02:30 -0400 Subject: [PATCH] Add base theme shop implementation and pane - added theme shop settings pane - added `generateVariables` for themes - added `preview.svg` for previewing themes until a proper solution is made --- src/context/Theme.tsx | 14 +- src/pages/settings/Settings.tsx | 12 +- src/pages/settings/assets/preview.svg | 179 ++++++++++++++++++++++++ src/pages/settings/panes/Appearance.tsx | 22 +-- src/pages/settings/panes/ThemeShop.tsx | 160 +++++++++++++++++++++ 5 files changed, 371 insertions(+), 16 deletions(-) create mode 100644 src/pages/settings/assets/preview.svg create mode 100644 src/pages/settings/panes/ThemeShop.tsx diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx index a2fbd2d1..07228d48 100644 --- a/src/context/Theme.tsx +++ b/src/context/Theme.tsx @@ -278,14 +278,18 @@ export const PRESETS: Record = { 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]};`; - })} + ${(props) => generateVariables(props.theme)} } `; +export const generateVariables = (theme: Theme) => { + const mergedTheme = { ...PRESETS[theme.light ? 'light' : 'dark'], ...theme } + return (Object.keys(mergedTheme) as Variables[]).map((key) => { + if (!keys.includes(key)) return; + return `--${key}: ${mergedTheme[key]};`; + }) +} + // Load the default default them and apply extras later export const ThemeContext = createContext(PRESETS["dark"]); diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 0b44170e..8ef915ab 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -16,6 +16,7 @@ import { User, Megaphone, Speaker, + Store, } from "@styled-icons/boxicons-solid"; import { Route, Switch, useHistory } from "react-router-dom"; import { LIBRARY_VERSION } from "revolt.js"; @@ -48,6 +49,7 @@ import { Notifications } from "./panes/Notifications"; import { Profile } from "./panes/Profile"; import { Sessions } from "./panes/Sessions"; import { Sync } from "./panes/Sync"; +import { ThemeShop } from "./panes/ThemeShop"; export default function Settings() { const history = useHistory(); @@ -123,12 +125,17 @@ export default function Settings() { title: , }, { - divider: true, category: "revolt", id: "bots", icon: , title: , }, + { + divider: true, + id: "theme_shop", + icon: , + title: , + }, { id: "feedback", icon: , @@ -169,6 +176,9 @@ export default function Settings() { + + + diff --git a/src/pages/settings/assets/preview.svg b/src/pages/settings/assets/preview.svg new file mode 100644 index 00000000..d6c922f4 --- /dev/null +++ b/src/pages/settings/assets/preview.svg @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index 478acb1e..e33f5b38 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -1,5 +1,5 @@ import { Reset, Import } from "@styled-icons/boxicons-regular"; -import { Pencil } from "@styled-icons/boxicons-solid"; +import { Pencil, Store } from "@styled-icons/boxicons-solid"; // @ts-expect-error shade-blend-color does not have typings. import pSBC from "shade-blend-color"; @@ -8,12 +8,16 @@ import { Text } from "preact-i18n"; import { useCallback, useContext, useEffect, useState } from "preact/hooks"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; +import CategoryButton from "../../../components/ui/fluent/CategoryButton"; + + import { debounce } from "../../../lib/debounce"; import { dispatch } from "../../../redux"; import { connectState } from "../../../redux/connector"; import { EmojiPacks, Settings } from "../../../redux/reducers/settings"; + import { DEFAULT_FONT, DEFAULT_MONO_FONT, @@ -42,6 +46,7 @@ import mutantSVG from "../assets/mutant_emoji.svg"; import notoSVG from "../assets/noto_emoji.svg"; import openmojiSVG from "../assets/openmoji_emoji.svg"; import twemojiSVG from "../assets/twemoji_emoji.svg"; +import { Link } from "react-router-dom"; interface Props { settings: Settings; @@ -131,15 +136,12 @@ export function Component(props: Props) { - {/* - setTheme({ - ligatures: !props.settings.theme?.ligatures, - }) - }> - Use the system theme - */} + + + } action="chevron" hover> + + +

diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx new file mode 100644 index 00000000..20b7cb57 --- /dev/null +++ b/src/pages/settings/panes/ThemeShop.tsx @@ -0,0 +1,160 @@ +import { useEffect, useState } from "preact/hooks" +import styled from "styled-components" +import { Theme, generateVariables } from '../../../context/Theme' +import { dispatch } from "../../../redux" + +export const fetchManifest = (): Promise => + fetch(`//bree.dev/revolt-themes/manifest.json`).then(res => res.json()) + +export const fetchTheme = (slug: string): Promise => + fetch(`//bree.dev/revolt-themes/theme_${slug}.json`).then(res => res.json()) + +interface ThemeMetadata { + name: string, + creator: string, + description: string +} + +type Manifest = { + generated: string, + themes: Record +} + +// TODO: ability to preview / display the settings set like in the appearance pane +const ThemeInfo = styled.div` + display: grid; + grid: + "preview name creator" min-content + "preview desc desc" 1fr + / 200px 1fr 1fr; + + gap: 0.5rem 1rem; + padding: 0.5rem; + border-radius: var(--border-radius); + background: var(--secondary-background); + + &[data-loaded] { + .preview { + opacity: 1; + } + } + + .preview { + grid-area: preview; + aspect-ratio: 323 / 202; + + display: grid; + grid: 1fr / 1fr; + + align-items: center; + justify-content: center; + text-align: center; + cursor: pointer; + + background-color: var(--secondary-background); + border-radius: var(--border-radius); + + overflow: hidden; + + opacity: 0; + transition: 0.25s opacity; + + > * { + grid-area: 1 / 1; + } + + svg { + height: 100%; + width: 100%; + object-fit: contain; + } + } + + .name { + grid-area: name; + } + + .creator { + grid-area: creator; + justify-self: end; + } + + .description { + grid-area: desc; + } +` + +const ThemeList = styled.div` + display: grid; + gap: 1rem; +` + +import previewPath from '../assets/preview.svg' + +const ThemedSVG = styled.svg<{ theme: Theme }>` + ${props => props.theme && generateVariables(props.theme)} +` + +type ThemePreviewProps = Omit, "as"> & { + slug?: string, + theme?: Theme + onThemeLoaded?: (theme: Theme) => void +}; + +const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => { + return +} + +export function ThemeShop() { + // setThemeList is for adding more / lazy loading in the future + const [themeList, setThemeList] = useState<[string, ThemeMetadata][] | null>(null); + const [themeData, setThemeData] = useState>({}); + + async function fetchThemeList() { + const manifest = await fetchManifest() + setThemeList(Object.entries(manifest.themes)) + } + + async function getTheme(slug: string) { + const theme = await fetchTheme(slug); + setThemeData(data => ({ ...data, [slug]: theme })) + } + + useEffect(() => { + fetchThemeList() + }, []) + + useEffect(() => { + themeList?.forEach(([slug]) => { + getTheme(slug) + }) + }, [themeList]) + + return ( + + {themeList?.map(([slug, theme]) => { + return +
{theme.name}
+ {/* Maybe id's of the users should be included as well / instead? */} +
@{theme.creator}
+
{theme.description}
+
+ dispatch({ + type: "SETTINGS_SET_THEME", + theme: { + custom: themeData[slug], + } + })} + /> +
+
+ })} +
+ ) +} \ No newline at end of file