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
This commit is contained in:
brecert
2021-09-06 06:02:30 -04:00
parent 7fc830eacf
commit 4d14390b22
5 changed files with 371 additions and 16 deletions

View File

@@ -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) {
</h4>
</div>
</div>
{/*<Checkbox
checked={props.settings.theme?.ligatures === true}
onChange={() =>
setTheme({
ligatures: !props.settings.theme?.ligatures,
})
}>
Use the system theme
</Checkbox>*/}
<Link to="/settings/theme_shop">
<CategoryButton icon={<Store size={24} />} action="chevron" hover>
<Text id="app.settings.pages.theme_shop.title" />
</CategoryButton>
</Link>
<h3>
<Text id="app.settings.pages.appearance.accent_selector" />

View File

@@ -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<Manifest> =>
fetch(`//bree.dev/revolt-themes/manifest.json`).then(res => res.json())
export const fetchTheme = (slug: string): Promise<Theme> =>
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<string, ThemeMetadata>
}
// 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<JSX.HTMLAttributes<SVGSVGElement>, "as"> & {
slug?: string,
theme?: Theme
onThemeLoaded?: (theme: Theme) => void
};
const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => {
return <ThemedSVG {...props} theme={theme} width="24" height="24" aria-hidden="true" data-loaded={!!theme}>
<use href={`${previewPath}#preview`} width="100%" height="100%" />
</ThemedSVG >
}
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<Record<string, Theme>>({});
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>
{themeList?.map(([slug, theme]) => {
return <ThemeInfo key={slug} data-loaded={Reflect.has(themeData, slug)}>
<div class="name">{theme.name}</div>
{/* Maybe id's of the users should be included as well / instead? */}
<div class="creator">@{theme.creator}</div>
<div class="description">{theme.description}</div>
<div class="preview">
<ThemePreview
slug={slug}
theme={themeData[slug]}
// todo: add option to set or override the current theme
onClick={() => dispatch({
type: "SETTINGS_SET_THEME",
theme: {
custom: themeData[slug],
}
})}
/>
</div>
</ThemeInfo>
})}
</ThemeList>
)
}