mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 08:30:58 -05:00
Merge pull request #188 from brecert/theme_shop
This commit is contained in:
commit
99116981ab
8 changed files with 416 additions and 19 deletions
|
@ -278,14 +278,18 @@ export const PRESETS: Record<string, Theme> = {
|
||||||
const keys = Object.keys(PRESETS.dark);
|
const keys = Object.keys(PRESETS.dark);
|
||||||
const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
||||||
:root {
|
:root {
|
||||||
${(props) =>
|
${(props) => generateVariables(props.theme)}
|
||||||
(Object.keys(props.theme) as Variables[]).map((key) => {
|
|
||||||
if (!keys.includes(key)) return;
|
|
||||||
return `--${key}: ${props.theme[key]};`;
|
|
||||||
})}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
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
|
// Load the default default them and apply extras later
|
||||||
export const ThemeContext = createContext<Theme>(PRESETS["dark"]);
|
export const ThemeContext = createContext<Theme>(PRESETS["dark"]);
|
||||||
|
|
||||||
|
|
4
src/env.d.ts
vendored
4
src/env.d.ts
vendored
|
@ -2,3 +2,7 @@ interface ImportMetaEnv {
|
||||||
VITE_API_URL: string;
|
VITE_API_URL: string;
|
||||||
VITE_THEMES_URL: string;
|
VITE_THEMES_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
env: ImportMetaEnv
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import {
|
||||||
User,
|
User,
|
||||||
Megaphone,
|
Megaphone,
|
||||||
Speaker,
|
Speaker,
|
||||||
|
Store,
|
||||||
} from "@styled-icons/boxicons-solid";
|
} from "@styled-icons/boxicons-solid";
|
||||||
import { Route, Switch, useHistory } from "react-router-dom";
|
import { Route, Switch, useHistory } from "react-router-dom";
|
||||||
import { LIBRARY_VERSION } from "revolt.js";
|
import { LIBRARY_VERSION } from "revolt.js";
|
||||||
|
@ -48,6 +49,8 @@ import { Notifications } from "./panes/Notifications";
|
||||||
import { Profile } from "./panes/Profile";
|
import { Profile } from "./panes/Profile";
|
||||||
import { Sessions } from "./panes/Sessions";
|
import { Sessions } from "./panes/Sessions";
|
||||||
import { Sync } from "./panes/Sync";
|
import { Sync } from "./panes/Sync";
|
||||||
|
import { ThemeShop } from "./panes/ThemeShop";
|
||||||
|
import { isExperimentEnabled } from "../../redux/reducers/experiments";
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -123,12 +126,19 @@ export default function Settings() {
|
||||||
title: <Text id="app.settings.pages.experiments.title" />,
|
title: <Text id="app.settings.pages.experiments.title" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: !isExperimentEnabled('theme_shop'),
|
||||||
category: "revolt",
|
category: "revolt",
|
||||||
id: "bots",
|
id: "bots",
|
||||||
icon: <Bot size={20} />,
|
icon: <Bot size={20} />,
|
||||||
title: <Text id="app.settings.pages.bots.title" />,
|
title: <Text id="app.settings.pages.bots.title" />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
hidden: !isExperimentEnabled('theme_shop'),
|
||||||
|
divider: true,
|
||||||
|
id: "theme_shop",
|
||||||
|
icon: <Store size={20} />,
|
||||||
|
title: <Text id="app.settings.pages.theme_shop.title" />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "feedback",
|
id: "feedback",
|
||||||
icon: <Megaphone size={20} />,
|
icon: <Megaphone size={20} />,
|
||||||
|
@ -169,6 +179,9 @@ export default function Settings() {
|
||||||
<Route path="/settings/bots">
|
<Route path="/settings/bots">
|
||||||
<MyBots />
|
<MyBots />
|
||||||
</Route>
|
</Route>
|
||||||
|
{isExperimentEnabled('theme_shop') && <Route path="/settings/theme_shop">
|
||||||
|
<ThemeShop />
|
||||||
|
</Route>}
|
||||||
<Route path="/settings/feedback">
|
<Route path="/settings/feedback">
|
||||||
<Feedback />
|
<Feedback />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
179
src/pages/settings/assets/preview.svg
Normal file
179
src/pages/settings/assets/preview.svg
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="preview" viewBox="0 0 323 202" fill="none" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<defs>
|
||||||
|
<g id="author">
|
||||||
|
<circle r="9.25" cy="10" cx="10" fill="#CFCFCF" />
|
||||||
|
<rect height="7" width="29.5" y="0.75" x="26" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
<rect height="7" width="24.5" y="0.75" x="59.5" rx="1.5" fill="var(--tertiary-foreground)"/>
|
||||||
|
</g>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- backgrounds -->
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="var(--background)" />
|
||||||
|
<path d="M27 14C27 11.7909 28.7909 10 31 10H90V202H31C28.7909 202 27 200.209 27 198V14Z" fill="var(--secondary-background)"/>
|
||||||
|
<rect x="90" y="10" width="233" height="192" fill="var(--primary-background)"/>
|
||||||
|
<rect x="90" y="10" width="233" height="18" fill="var(--primary-header)"/>
|
||||||
|
<rect x="97" y="16" width="30" height="6" rx="2" fill="var(--foreground)"/>
|
||||||
|
|
||||||
|
<!-- bottom message -->
|
||||||
|
<use x="96.5" y="144.5" href="#author" />
|
||||||
|
<rect height="5.75" width="21" y="158.25" x="122.5" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
<rect height="5.25" width="40" y="158.25" x="147" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
<rect height="5.25" width="47.5" y="158.25" x="190.5" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
|
||||||
|
<path opacity="0.5" d="M97 131.868H262.242" stroke="var(--tertiary-foreground)" stroke-width="0.85" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- middle message -->
|
||||||
|
<use x="96.5" y="80.5" href="#author" />
|
||||||
|
<rect height="5.25" width="70" y="94.5" x="122.5" rx="1.5" fill="var(--accent)"/>
|
||||||
|
<rect height="5.25" width="20" y="106.75" x="128.5" rx="1.5" fill="var(--accent)"/>
|
||||||
|
<rect height="5.25" width="89.25" y="115" x="128.5" rx="1.5" fill="var(--secondary-foreground)"/>
|
||||||
|
<path d="M122.954 105.913V120.621" stroke="#BFBFBF" stroke-width="0.86514" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- top message -->
|
||||||
|
<use x="96.5" y="37.25" href="#author" />
|
||||||
|
<rect height="5.25" width="82.25" y="51.25" x="122.5" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
<rect height="5.25" width="58.75" y="51.25" x="208.25" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
<rect height="5.25" width="25" y="62.25" x="122.5" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
<rect height="5.25" width="43.25" y="62.25" x="151" rx="1.5" fill="var(--foreground)"/>
|
||||||
|
|
||||||
|
<!-- message box -->
|
||||||
|
<rect x="90" y="184" width="233" height="18" fill="var(--message-box)"/>
|
||||||
|
|
||||||
|
<!-- window buttons -->
|
||||||
|
<circle cx="317" cy="5" r="2" fill="#C4C4C4"/>
|
||||||
|
<circle cx="310" cy="5" r="2" fill="#C4C4C4"/>
|
||||||
|
<circle cx="303" cy="5" r="2" fill="#C4C4C4"/>
|
||||||
|
|
||||||
|
<!-- guild separator -->
|
||||||
|
<line x1="4.5" y1="34.5" x2="21.5" y2="34.5" stroke="#C0C0C0" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- sidebar information -->
|
||||||
|
<rect x="30" y="16" width="36" height="6" rx="2" fill="var(--foreground)" opacity="0.9"/>
|
||||||
|
<rect x="30" y="35" width="26" height="4" rx="2" fill="var(--foreground)"/>
|
||||||
|
<rect x="39" y="46" width="32" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="39" y="70" width="29" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="39" y="58" width="13" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="55" y="58" width="22" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="30" y="83" width="26" height="4" rx="2" fill="var(--foreground)"/>
|
||||||
|
<rect x="39" y="94" width="32" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="39" y="118" width="29" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="39" y="106" width="13" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
<rect x="55" y="106" width="22" height="4" rx="2" fill="var(--tertiary-foreground)"/>
|
||||||
|
|
||||||
|
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="4" y="10" width="18" height="18">
|
||||||
|
<circle cx="13" cy="19" r="9" fill="#C4C4C4"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0)">
|
||||||
|
<circle cx="13" cy="19" r="9" fill="url(#paint0_linear)"/>
|
||||||
|
<circle cx="11.9199" cy="22.24" r="3.6" fill="#F9FAFB"/>
|
||||||
|
<path d="M4 22.6H6.88L9.04 21.52L11.2 20.8L12.64 21.52L14.44 21.16L16.24 21.52L16.96 21.88L19.12 21.52L20.56 21.88L22 21.16V29.08H16.6H11.92H4V24.04V22.6Z" fill="#C42626"/>
|
||||||
|
<path d="M6.88 22.6H4V24.04L6.88 22.6Z" fill="#882C2F"/>
|
||||||
|
<path d="M14.44 21.16L12.64 21.52L11.2 24.04L11.92 29.08H16.6L15.88 27.64L16.24 22.96L16.96 21.88L16.24 21.52L14.44 21.16Z" fill="#AF373B"/>
|
||||||
|
</g>
|
||||||
|
<mask id="mask1" mask-type="alpha" maskUnits="userSpaceOnUse" x="4" y="42" width="18" height="18">
|
||||||
|
<circle cx="13" cy="51" r="9" fill="#C4C4C4"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask1)">
|
||||||
|
<circle cx="13" cy="51" r="9" fill="#D6D4D5"/>
|
||||||
|
<path d="M13.612 53.8162C19.048 49.6124 22.0251 53.8162 22.0251 53.8162V60.4402H5.89715L7.49195 53.988C7.9966 53.5705 8.17595 58.02 13.612 53.8162Z" fill="url(#paint1_linear)"/>
|
||||||
|
<path d="M4.54004 54.0601C4.54004 54.0601 8.10873 49.2615 12.388 54.9961C16.3012 60.2399 20.3146 56.741 20.668 55.8518V55.6801C20.7021 55.7083 20.7011 55.7686 20.668 55.8518V60.684H4.54004V54.0601Z" fill="url(#paint2_linear)"/>
|
||||||
|
<path d="M21.568 47.1119C21.568 48.2254 20.6654 49.1279 19.552 49.1279C18.4386 49.1279 17.536 48.2254 17.536 47.1119C17.536 45.9985 18.4386 45.0959 19.552 45.0959C20.6654 45.0959 21.568 45.9985 21.568 47.1119Z" fill="#E76563"/>
|
||||||
|
<path d="M19.12 49.0559H19.984V49.4879H19.12V49.0559Z" fill="#E76563"/>
|
||||||
|
<rect x="19.12" y="49.344" width="0.864" height="0.072" fill="white"/>
|
||||||
|
<path d="M19.264 49.488H19.336V49.776H19.264V49.488Z" fill="#4F65B6"/>
|
||||||
|
<path d="M19.48 49.488H19.624V49.776H19.48V49.488Z" fill="#4F65B6"/>
|
||||||
|
<path d="M19.768 49.488H19.84V49.776H19.768V49.488Z" fill="#4F65B6"/>
|
||||||
|
<path d="M19.048 49.776H20.056L19.984 50.28H19.12L19.048 49.776Z" fill="#4F65B6"/>
|
||||||
|
</g>
|
||||||
|
<mask id="mask2" mask-type="alpha" maskUnits="userSpaceOnUse" x="4" y="65" width="18" height="18">
|
||||||
|
<circle cx="13" cy="74" r="9" fill="#C4C4C4"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask2)">
|
||||||
|
<circle cx="13" cy="74" r="9" fill="url(#paint3_linear)"/>
|
||||||
|
<path d="M11.056 79.184L13.936 75.764V80.516L11.992 80.336L11.056 79.184Z" fill="#2E2816"/>
|
||||||
|
<path d="M5.97998 76.2679L13.72 80.3719L13.792 85.0159L5.97998 82.3519L5.15198 79.1839L5.97998 76.2679Z" fill="url(#paint4_linear)"/>
|
||||||
|
<path d="M4.75598 78.0319L5.97998 76.2679L5.22398 79.4719L4.93598 78.5359L4.75598 78.0319Z" fill="#7EA6A6"/>
|
||||||
|
<path d="M19.12 68.708L21.64 70.544L24.484 76.124L22.468 79.94L19.12 68.708Z" fill="#EDEDED"/>
|
||||||
|
<path d="M12.964 79.976L13.864 80.444L13.936 84.008L13 83.972L12.964 79.976Z" fill="#878787" fill-opacity="0.5"/>
|
||||||
|
<path d="M13.468 75.584L19.12 68.708L23.008 79.112L13.72 85.736L13.468 75.584Z" fill="url(#paint5_linear)"/>
|
||||||
|
</g>
|
||||||
|
<mask id="mask3" mask-type="alpha" maskUnits="userSpaceOnUse" x="4" y="88" width="18" height="18">
|
||||||
|
<circle cx="13" cy="97" r="9" fill="#C4C4C4"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask3)">
|
||||||
|
<circle cx="13" cy="97" r="9" fill="url(#paint6_linear)"/>
|
||||||
|
<path d="M4.252 89.404C4.252 89.404 11.236 87.2439 16.024 92.284C20.812 97.324 19.732 106.396 19.732 106.396H4.252L3.604 97.936L4.252 89.404Z" fill="url(#paint7_linear)"/>
|
||||||
|
<path d="M14.404 106.396C12.208 111.508 19.732 106.396 19.732 106.396C19.732 106.396 20.488 100.348 18.508 95.956C16.528 91.564 13.72 90.448 13.72 90.448C13.72 90.448 16.6 101.284 14.404 106.396Z" fill="url(#paint8_linear)"/>
|
||||||
|
</g>
|
||||||
|
<mask id="mask4" mask-type="alpha" maskUnits="userSpaceOnUse" x="4" y="110" width="18" height="18">
|
||||||
|
<circle cx="13" cy="119" r="9" fill="#C4C4C4"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask4)">
|
||||||
|
<circle cx="13" cy="119" r="9" fill="url(#paint9_linear)"/>
|
||||||
|
<path d="M3.35205 122.708L22.2161 122.708" stroke="#8181B1" stroke-width="0.144"/>
|
||||||
|
<path d="M3.28003 121.376L22.144 121.376" stroke="#A2A2BE" stroke-width="0.144"/>
|
||||||
|
<path d="M3.56799 119.936L22.432 119.936" stroke="#ADADBD" stroke-width="0.216"/>
|
||||||
|
<path d="M3.78406 118.496L22.6481 118.496" stroke="#BBBBCD" stroke-width="0.216"/>
|
||||||
|
<line x1="3.35205" y1="124.004" x2="22.2161" y2="124.004" stroke="#8181B1" stroke-width="0.072"/>
|
||||||
|
<line x1="3.35205" y1="125.3" x2="22.2161" y2="125.3" stroke="#8181B1" stroke-width="0.072"/>
|
||||||
|
<path d="M13.144 122.816L13.828 123.824C13.828 123.824 13.936 124.256 13.828 124.328C13.72 124.4 13.288 123.824 13.18 123.5C13.072 123.176 13.144 122.816 13.144 122.816Z" fill="#E6E7F4"/>
|
||||||
|
<path d="M13.828 124.328V123.824C13.828 123.824 15.304 123.608 16.708 122.816C18.112 122.024 18.364 120.944 18.364 120.944C18.364 120.944 18.292 121.952 16.924 123.032C15.556 124.112 13.828 124.328 13.828 124.328Z" fill="#E6E7F4"/>
|
||||||
|
<path d="M18.364 120.944C15.448 120.764 13.144 122.826 13.144 122.826L13.828 123.834C13.828 123.834 17.644 123.248 18.364 120.944Z" fill="white"/>
|
||||||
|
<path d="M18.256 121.016C15.6818 120.86 13.252 122.816 13.252 122.816L13.864 123.716C13.864 123.716 17.6204 123.017 18.256 121.016Z" fill="url(#paint10_linear)"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- notification icon -->
|
||||||
|
<circle cx="20" cy="45" r="3.5" fill="#EF3B3B" stroke="var(--background)"/>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear" x1="13" y1="10" x2="13" y2="28" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#AAB6BD"/>
|
||||||
|
<stop offset="1" stop-color="#D4DDE1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear" x1="14.908" y1="54.744" x2="22.7559" y2="61.08" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4F65B6"/>
|
||||||
|
<stop offset="1" stop-color="#C6D0F1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear" x1="8.39204" y1="54.276" x2="21.496" y2="60.324" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4F65B6"/>
|
||||||
|
<stop offset="1" stop-color="#C6D0F1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear" x1="9.292" y1="65.432" x2="18.22" y2="82.388" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#009092"/>
|
||||||
|
<stop offset="1" stop-color="#79C6C8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint4_linear" x1="9.39998" y1="76.2679" x2="9.39998" y2="85.0519" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#CBCBCB"/>
|
||||||
|
<stop offset="1" stop-color="#FAFAFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint5_linear" x1="18.238" y1="68.708" x2="18.238" y2="85.736" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#95ABA9"/>
|
||||||
|
<stop offset="1" stop-color="#DCDCDC"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint6_linear" x1="10.876" y1="87.604" x2="17.86" y2="105.136" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#41486A"/>
|
||||||
|
<stop offset="1" stop-color="#3B3F5C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint7_linear" x1="7.312" y1="91.168" x2="19.84" y2="107.224" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4C7799"/>
|
||||||
|
<stop offset="0.9999" stop-color="#39AEBF"/>
|
||||||
|
<stop offset="1" stop-color="#4C7799" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint8_linear" x1="16.636" y1="96.568" x2="12.388" y2="87.316" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#DD4878"/>
|
||||||
|
<stop offset="1" stop-color="#D7E1E8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint9_linear" x1="9.94" y1="109.244" x2="13.756" y2="125.912" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#847DAF"/>
|
||||||
|
<stop offset="1" stop-color="#4547AE"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint10_linear" x1="15.484" y1="122.024" x2="16.924" y2="123.5" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#DFDFE1"/>
|
||||||
|
<stop offset="1" stop-color="#F5F4FB"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 11 KiB |
|
@ -1,5 +1,5 @@
|
||||||
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
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.
|
// @ts-expect-error shade-blend-color does not have typings.
|
||||||
import pSBC from "shade-blend-color";
|
import pSBC from "shade-blend-color";
|
||||||
|
|
||||||
|
@ -8,12 +8,16 @@ import { Text } from "preact-i18n";
|
||||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||||
|
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
||||||
|
|
||||||
|
|
||||||
import { debounce } from "../../../lib/debounce";
|
import { debounce } from "../../../lib/debounce";
|
||||||
|
|
||||||
import { dispatch } from "../../../redux";
|
import { dispatch } from "../../../redux";
|
||||||
import { connectState } from "../../../redux/connector";
|
import { connectState } from "../../../redux/connector";
|
||||||
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT,
|
DEFAULT_FONT,
|
||||||
DEFAULT_MONO_FONT,
|
DEFAULT_MONO_FONT,
|
||||||
|
@ -42,6 +46,8 @@ import mutantSVG from "../assets/mutant_emoji.svg";
|
||||||
import notoSVG from "../assets/noto_emoji.svg";
|
import notoSVG from "../assets/noto_emoji.svg";
|
||||||
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
||||||
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
@ -131,15 +137,12 @@ export function Component(props: Props) {
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/*<Checkbox
|
|
||||||
checked={props.settings.theme?.ligatures === true}
|
{isExperimentEnabled('theme_shop') && <Link to="/settings/theme_shop">
|
||||||
onChange={() =>
|
<CategoryButton icon={<Store size={24} />} action="chevron" hover>
|
||||||
setTheme({
|
<Text id="app.settings.pages.theme_shop.title" />
|
||||||
ligatures: !props.settings.theme?.ligatures,
|
</CategoryButton>
|
||||||
})
|
</Link>}
|
||||||
}>
|
|
||||||
Use the system theme
|
|
||||||
</Checkbox>*/}
|
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.appearance.accent_selector" />
|
<Text id="app.settings.pages.appearance.accent_selector" />
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
AVAILABLE_EXPERIMENTS,
|
AVAILABLE_EXPERIMENTS,
|
||||||
ExperimentOptions,
|
ExperimentOptions,
|
||||||
EXPERIMENTS,
|
EXPERIMENTS,
|
||||||
|
isExperimentEnabled,
|
||||||
} from "../../../redux/reducers/experiments";
|
} from "../../../redux/reducers/experiments";
|
||||||
|
|
||||||
import Checkbox from "../../../components/ui/Checkbox";
|
import Checkbox from "../../../components/ui/Checkbox";
|
||||||
|
@ -24,7 +25,7 @@ export function Component(props: Props) {
|
||||||
{AVAILABLE_EXPERIMENTS.map((key) => (
|
{AVAILABLE_EXPERIMENTS.map((key) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
key={key}
|
key={key}
|
||||||
checked={(props.options?.enabled ?? []).indexOf(key) > -1}
|
checked={isExperimentEnabled(key, props.options)}
|
||||||
onChange={(enabled) =>
|
onChange={(enabled) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: enabled
|
type: enabled
|
||||||
|
|
178
src/pages/settings/panes/ThemeShop.tsx
Normal file
178
src/pages/settings/panes/ThemeShop.tsx
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
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"
|
||||||
|
|
||||||
|
export const fetchManifest = (): Promise<Manifest> =>
|
||||||
|
fetch(`${import.meta.env.VITE_THEMES_URL}/manifest.json`).then(res => res.json())
|
||||||
|
|
||||||
|
export const fetchTheme = (slug: string): Promise<Theme> =>
|
||||||
|
fetch(`${import.meta.env.VITE_THEMES_URL}/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.article`
|
||||||
|
display: grid;
|
||||||
|
grid:
|
||||||
|
"preview name creator" min-content
|
||||||
|
"preview desc desc" 1fr
|
||||||
|
/ 200px 1fr 1fr;
|
||||||
|
|
||||||
|
gap: 0.5rem 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: var(--secondary-background);
|
||||||
|
|
||||||
|
&[data-loaded] {
|
||||||
|
.preview {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
grid-area: preview;
|
||||||
|
aspect-ratio: 323 / 202;
|
||||||
|
|
||||||
|
background-color: var(--secondary-background);
|
||||||
|
border-radius: calc(var(--border-radius) / 2);
|
||||||
|
|
||||||
|
// prep style for later
|
||||||
|
outline: 3px solid transparent;
|
||||||
|
|
||||||
|
// hide random svg parts, crop border on firefox
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// hide until loaded
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
// style button
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
transition: 0.25s opacity, 0.25s outline;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover, &:active, &:focus-visible {
|
||||||
|
outline: 3px solid var(--tertiary-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
grid-area: name;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator {
|
||||||
|
grid-area: creator;
|
||||||
|
justify-self: end;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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="323" height="202" aria-hidden="true" data-loaded={!!theme}>
|
||||||
|
<use href={`${previewPath}#preview`} width="100%" height="100%" />
|
||||||
|
</ThemedSVG >
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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 (<ThemeShopRoot>
|
||||||
|
<Tip warning>This section is under construction.</Tip>
|
||||||
|
<ThemeList>
|
||||||
|
{themeList?.map(([slug, theme]) => (
|
||||||
|
<ThemeInfo key={slug} data-loaded={Reflect.has(themeData, slug)}>
|
||||||
|
<h2 class="name">{theme.name}</h2>
|
||||||
|
{/* Maybe id's of the users should be included as well / instead? */}
|
||||||
|
<div class="creator">by {theme.creator}</div>
|
||||||
|
<div class="description">{theme.description}</div>
|
||||||
|
<button
|
||||||
|
class="preview"
|
||||||
|
onClick={() => dispatch({
|
||||||
|
type: "SETTINGS_SET_THEME",
|
||||||
|
theme: {
|
||||||
|
custom: themeData[slug],
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ThemePreview
|
||||||
|
slug={slug}
|
||||||
|
theme={themeData[slug]}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</ThemeInfo>
|
||||||
|
))}
|
||||||
|
</ThemeList>
|
||||||
|
</ThemeShopRoot>)
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
export type Experiments = "search";
|
import { getState } from "..";
|
||||||
export const AVAILABLE_EXPERIMENTS: Experiments[] = [];
|
|
||||||
|
export type Experiments = "search" | "theme_shop";
|
||||||
|
|
||||||
|
export const AVAILABLE_EXPERIMENTS: Experiments[] = ["theme_shop"];
|
||||||
|
|
||||||
export const EXPERIMENTS: {
|
export const EXPERIMENTS: {
|
||||||
[key in Experiments]: { title: string; description: string };
|
[key in Experiments]: { title: string; description: string };
|
||||||
} = {
|
} = {
|
||||||
|
@ -7,6 +11,10 @@ export const EXPERIMENTS: {
|
||||||
title: "Search",
|
title: "Search",
|
||||||
description: "Allows you to search for messages in channels.",
|
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 {
|
export interface ExperimentOptions {
|
||||||
|
@ -50,3 +58,10 @@ export function experiments(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isExperimentEnabled(
|
||||||
|
name: Experiments,
|
||||||
|
experiments: ExperimentOptions = getState().experiments,
|
||||||
|
) {
|
||||||
|
return experiments.enabled?.includes(name) ?? false;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue