From 414dcd51c00632ce44de6bacd1d202e5d4fcc7bb Mon Sep 17 00:00:00 2001 From: brecert Date: Mon, 6 Sep 2021 06:02:30 -0400 Subject: [PATCH 1/6] 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 From 6d85c9b7250ed6b357e77e5ac6bf91da92cfd068 Mon Sep 17 00:00:00 2001 From: brecert Date: Mon, 6 Sep 2021 06:24:01 -0400 Subject: [PATCH 2/6] Add under construction warning for the theme shop --- src/pages/settings/panes/ThemeShop.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx index 20b7cb57..6eecb2fe 100644 --- a/src/pages/settings/panes/ThemeShop.tsx +++ b/src/pages/settings/panes/ThemeShop.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "preact/hooks" import styled from "styled-components" +import Tip from "../../../components/ui/Tip" import { Theme, generateVariables } from '../../../context/Theme' import { dispatch } from "../../../redux" @@ -132,7 +133,8 @@ export function ThemeShop() { }) }, [themeList]) - return ( + return (
+ This section is under construction. {themeList?.map(([slug, theme]) => { return @@ -156,5 +158,5 @@ export function ThemeShop() { })} - ) +
) } \ No newline at end of file From 6a32e451ac622f21c5e0c35dee36116925b43e23 Mon Sep 17 00:00:00 2001 From: brecert Date: Mon, 6 Sep 2021 06:38:53 -0400 Subject: [PATCH 3/6] Use `VITE_THEMES_URL` for the default theme manifest - typed `ImportMeta` to have `env` --- src/env.d.ts | 4 ++++ src/pages/settings/panes/ThemeShop.tsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/env.d.ts b/src/env.d.ts index 25be5fe2..bcd82398 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -2,3 +2,7 @@ interface ImportMetaEnv { VITE_API_URL: string; VITE_THEMES_URL: string; } + +interface ImportMeta { + env: ImportMetaEnv +} \ No newline at end of file diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx index 6eecb2fe..87844506 100644 --- a/src/pages/settings/panes/ThemeShop.tsx +++ b/src/pages/settings/panes/ThemeShop.tsx @@ -5,10 +5,11 @@ 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()) + fetch(`${import.meta.env.VITE_THEMES_URL}/manifest.json`).then(res => res.json()) export const fetchTheme = (slug: string): Promise => - fetch(`//bree.dev/revolt-themes/theme_${slug}.json`).then(res => res.json()) + fetch(`${import.meta.env.VITE_THEMES_URL}/theme_${slug}.json`).then(res => res.json()) + interface ThemeMetadata { name: string, From d7f08449cbb7461040abd20d9d73a84504d29324 Mon Sep 17 00:00:00 2001 From: brecert Date: Tue, 7 Sep 2021 02:47:12 -0400 Subject: [PATCH 4/6] Minor style improvements --- src/pages/settings/panes/ThemeShop.tsx | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx index 87844506..23717f29 100644 --- a/src/pages/settings/panes/ThemeShop.tsx +++ b/src/pages/settings/panes/ThemeShop.tsx @@ -23,7 +23,7 @@ type Manifest = { } // TODO: ability to preview / display the settings set like in the appearance pane -const ThemeInfo = styled.div` +const ThemeInfo = styled.article` display: grid; grid: "preview name creator" min-content @@ -31,7 +31,7 @@ const ThemeInfo = styled.div` / 200px 1fr 1fr; gap: 0.5rem 1rem; - padding: 0.5rem; + padding: 1rem; border-radius: var(--border-radius); background: var(--secondary-background); @@ -54,7 +54,8 @@ const ThemeInfo = styled.div` cursor: pointer; background-color: var(--secondary-background); - border-radius: var(--border-radius); + border-radius: calc(var(--border-radius) / 2); + box-shadow: 0 0 0 1px var(--tertiary-foreground); overflow: hidden; @@ -74,11 +75,13 @@ const ThemeInfo = styled.div` .name { grid-area: name; + margin: 0; } .creator { grid-area: creator; justify-self: end; + font-size: 0.75rem; } .description { @@ -109,6 +112,11 @@ const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => { } +const ThemeShopRoot = styled.div` + display: grid; + gap: 1rem; +` + export function ThemeShop() { // setThemeList is for adding more / lazy loading in the future const [themeList, setThemeList] = useState<[string, ThemeMetadata][] | null>(null); @@ -134,14 +142,14 @@ export function ThemeShop() { }) }, [themeList]) - return (
+ return ( This section is under construction. - {themeList?.map(([slug, theme]) => { - return -
{theme.name}
+ {themeList?.map(([slug, theme]) => ( + +

{theme.name}

{/* Maybe id's of the users should be included as well / instead? */} -
@{theme.creator}
+
by {theme.creator}
{theme.description}
- })} + ))}
-
) + ) } \ No newline at end of file From 068540d366d1c6aca423cb4ecbc518bd0806a6aa Mon Sep 17 00:00:00 2001 From: brecert Date: Tue, 7 Sep 2021 05:27:51 -0400 Subject: [PATCH 5/6] Make theme shop hidden an experiment --- src/pages/settings/Settings.tsx | 7 +++++-- src/pages/settings/panes/Appearance.tsx | 5 +++-- src/pages/settings/panes/Experiments.tsx | 3 ++- src/redux/reducers/experiments.ts | 19 +++++++++++++++++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 8ef915ab..bf27eab4 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -50,6 +50,7 @@ import { Profile } from "./panes/Profile"; import { Sessions } from "./panes/Sessions"; import { Sync } from "./panes/Sync"; import { ThemeShop } from "./panes/ThemeShop"; +import { isExperimentEnabled } from "../../redux/reducers/experiments"; export default function Settings() { const history = useHistory(); @@ -125,12 +126,14 @@ export default function Settings() { title: , }, { + divider: !isExperimentEnabled('theme_shop'), category: "revolt", id: "bots", icon: , title: , }, { + hidden: !isExperimentEnabled('theme_shop'), divider: true, id: "theme_shop", icon: , @@ -176,9 +179,9 @@ export default function Settings() { - + {isExperimentEnabled('theme_shop') && - + } diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index e33f5b38..bd61029b 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -47,6 +47,7 @@ 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"; +import { isExperimentEnabled } from "../../../redux/reducers/experiments"; interface Props { settings: Settings; @@ -137,11 +138,11 @@ export function Component(props: Props) { - + {isExperimentEnabled('theme_shop') && } action="chevron" hover> - + }

diff --git a/src/pages/settings/panes/Experiments.tsx b/src/pages/settings/panes/Experiments.tsx index abac7cc3..7e50c892 100644 --- a/src/pages/settings/panes/Experiments.tsx +++ b/src/pages/settings/panes/Experiments.tsx @@ -7,6 +7,7 @@ import { AVAILABLE_EXPERIMENTS, ExperimentOptions, EXPERIMENTS, + isExperimentEnabled, } from "../../../redux/reducers/experiments"; import Checkbox from "../../../components/ui/Checkbox"; @@ -24,7 +25,7 @@ export function Component(props: Props) { {AVAILABLE_EXPERIMENTS.map((key) => ( -1} + checked={isExperimentEnabled(key, props.options)} onChange={(enabled) => dispatch({ type: enabled diff --git a/src/redux/reducers/experiments.ts b/src/redux/reducers/experiments.ts index c9da28d3..7d081f86 100644 --- a/src/redux/reducers/experiments.ts +++ b/src/redux/reducers/experiments.ts @@ -1,5 +1,9 @@ -export type Experiments = "search"; -export const AVAILABLE_EXPERIMENTS: Experiments[] = []; +import { getState } from ".."; + +export type Experiments = "search" | "theme_shop"; + +export const AVAILABLE_EXPERIMENTS: Experiments[] = ["theme_shop"]; + export const EXPERIMENTS: { [key in Experiments]: { title: string; description: string }; } = { @@ -7,6 +11,10 @@ export const EXPERIMENTS: { title: "Search", description: "Allows you to search for messages in channels.", }, + theme_shop: { + title: "Theme Shop", + description: "Allows you to access and set user submitted themes.", + }, }; export interface ExperimentOptions { @@ -50,3 +58,10 @@ export function experiments( return state; } } + +export function isExperimentEnabled( + name: Experiments, + experiments: ExperimentOptions = getState().experiments, +) { + return experiments.enabled?.includes(name) ?? false; +} From 28b2d8dcabb4c0c040be663f059e8ebfb85355d4 Mon Sep 17 00:00:00 2001 From: brecert Date: Tue, 7 Sep 2021 06:31:49 -0400 Subject: [PATCH 6/6] Improve accessibility and styling of theme shop --- src/pages/settings/panes/ThemeShop.tsx | 49 +++++++++++++++----------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx index 23717f29..205f0960 100644 --- a/src/pages/settings/panes/ThemeShop.tsx +++ b/src/pages/settings/panes/ThemeShop.tsx @@ -45,23 +45,25 @@ const ThemeInfo = styled.article` 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: calc(var(--border-radius) / 2); - box-shadow: 0 0 0 1px var(--tertiary-foreground); + + // prep style for later + outline: 3px solid transparent; + // hide random svg parts, crop border on firefox overflow: hidden; + // hide until loaded opacity: 0; - transition: 0.25s opacity; - + + // style button + border: 0; + margin: 0; + padding: 0; + + transition: 0.25s opacity, 0.25s outline; + > * { grid-area: 1 / 1; } @@ -71,6 +73,10 @@ const ThemeInfo = styled.article` width: 100%; object-fit: contain; } + + &:hover, &:active, &:focus-visible { + outline: 3px solid var(--tertiary-background); + } } .name { @@ -107,7 +113,7 @@ type ThemePreviewProps = Omit, "as"> & { }; const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => { - return