From f1e7ef71c3dc36a0782dbf7d5b3c8a8f7e881756 Mon Sep 17 00:00:00 2001 From: SpikeHD <25207995+SpikeHD@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:01:00 -0800 Subject: [PATCH] feat: WIP dorion theme browser --- plugins/dorion-theme-browser/api.ts | 36 +++++++ .../components/ThemeCard.tsx | 60 ++++++++++++ .../components/ThemeCard.tsx.scss | 51 ++++++++++ .../components/ThemePage.tsx | 96 +++++++++++++++++++ .../components/ThemePage.tsx.scss | 29 ++++++ plugins/dorion-theme-browser/index.ts | 18 ++++ plugins/dorion-theme-browser/plugin.json | 5 + plugins/inline-css/components/Editor.tsx | 6 +- plugins/inline-css/components/Window.tsx | 6 +- plugins/inline-css/index.tsx | 2 +- plugins/inline-css/package.json | 3 +- .../inline-css/util.ts => util/debounce.ts | 0 12 files changed, 304 insertions(+), 8 deletions(-) create mode 100644 plugins/dorion-theme-browser/api.ts create mode 100644 plugins/dorion-theme-browser/components/ThemeCard.tsx create mode 100644 plugins/dorion-theme-browser/components/ThemeCard.tsx.scss create mode 100644 plugins/dorion-theme-browser/components/ThemePage.tsx create mode 100644 plugins/dorion-theme-browser/components/ThemePage.tsx.scss create mode 100644 plugins/dorion-theme-browser/index.ts create mode 100644 plugins/dorion-theme-browser/plugin.json rename plugins/inline-css/util.ts => util/debounce.ts (100%) diff --git a/plugins/dorion-theme-browser/api.ts b/plugins/dorion-theme-browser/api.ts new file mode 100644 index 0000000..3512b6e --- /dev/null +++ b/plugins/dorion-theme-browser/api.ts @@ -0,0 +1,36 @@ +interface ThemeOptions { + filter?: string + page?: string + sort?: 'popular' | 'creationdate' | 'name' | 'likes' | 'downloads' | 'recentlyupdated' +} + +const BASE = 'https://betterdiscord.app' + +export const themeListEndpoint = async (options: ThemeOptions) => { + const query = new URLSearchParams(options as Record) + + query.set('type', 'theme') + query.set('pages', '1') + query.set('sortDirection', 'descending') + query.set('tags', '[]') + + const resp = await fetch(`${BASE}/Addon/GetApprovedAddons?${query}`) + + if (!resp.ok) { + throw new Error('Failed to fetch themes') + } + + const parser = new DOMParser() + const dom = parser.parseFromString(await resp.text(), 'text/html') + + const themes = Array.from(dom.querySelectorAll('.card-wrap')).map((e: Element) => ({ + thumbnail: `${BASE}${e.querySelector('.card-image')?.getAttribute('src')}`, + name: e.querySelector('.card-title')?.textContent?.trim(), + author: e.querySelector('.author-link')?.textContent?.trim(), + description: e.querySelector('.card-description')?.textContent?.trim(), + likes: e.querySelector('#addon-likes')?.textContent?.trim(), + downloads: e.querySelector('#addon-downloads')?.textContent?.trim(), + })) + + return themes +} \ No newline at end of file diff --git a/plugins/dorion-theme-browser/components/ThemeCard.tsx b/plugins/dorion-theme-browser/components/ThemeCard.tsx new file mode 100644 index 0000000..90f6db8 --- /dev/null +++ b/plugins/dorion-theme-browser/components/ThemeCard.tsx @@ -0,0 +1,60 @@ +import { css, classes } from './ThemeCard.tsx.scss' + +interface Props { + key: string + theme: string + thumbnail: string + likes: string + downloads: string + description: string + author: string + install_url: string +} + +const { + ui: { + injectCss, + Button, + Text + }, + solid: { + createSignal, + createEffect, + }, +} = shelter + +let injectedCss = false + +export function ThemeCard(props: Props) { + if (!injectedCss) { + injectCss(css) + injectedCss = true + } + + const installTheme = () => { + // TODO + } + + return ( +
+
+ +
+ + {props.theme} by {props.author} + + + {props.description} + +
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/plugins/dorion-theme-browser/components/ThemeCard.tsx.scss b/plugins/dorion-theme-browser/components/ThemeCard.tsx.scss new file mode 100644 index 0000000..b7b741b --- /dev/null +++ b/plugins/dorion-theme-browser/components/ThemeCard.tsx.scss @@ -0,0 +1,51 @@ +.themeCard { + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: flex-start; + text-align: left; + + padding: 0px; + margin: 8px; + + color: var(--text-primary); + background: var(--background-secondary); + + border-radius: 8px; + + .thumbnail { + width: 100%; + height: 160px; + overflow: hidden; + + /* TL & TR 8px, others 0px */ + border-radius: 8px 8px 0px 0px; + + /* image is set in the background so we can use cover */ + background-size: cover; + background-position: center; + } + + .info { + display: flex; + flex-direction: column; + + margin-top: 6px; + padding: 16px; + width: 100%; + + text-overflow: ellipsis; + overflow: hidden; + + .name, + .contents, + .installButton { + margin-bottom: 8px; + } + + .installButton { + margin-top: 8px; + width: 100%; + } + } +} \ No newline at end of file diff --git a/plugins/dorion-theme-browser/components/ThemePage.tsx b/plugins/dorion-theme-browser/components/ThemePage.tsx new file mode 100644 index 0000000..d95da2b --- /dev/null +++ b/plugins/dorion-theme-browser/components/ThemePage.tsx @@ -0,0 +1,96 @@ +import { Dropdown } from '../../../components/Dropdown.jsx' +import { debounce } from '../../../util/debounce.js' +import { themeListEndpoint } from '../api.js' +import { ThemeCard } from './ThemeCard.jsx' +import { css, classes } from './ThemePage.tsx.scss' + +const { + ui: { + injectCss, + Divider, + Header, + HeaderTags, + TextBox + }, + solid: { + createSignal, + createEffect, + }, +} = shelter + +let injectedCss = false + +export function ThemePage() { + if (!injectedCss) { + injectCss(css) + injectedCss = true + } + + const [themeData, setThemeData] = createSignal([]) + const [sort, setSort] = createSignal('popular') + const [search, setSearch] = createSignal('') + + createEffect(async () => { + await loadThemes() + }) + + const loadThemes = async () => { + setThemeData(await themeListEndpoint({ page: '1', sort: sort(), filter: search() })) + } + + const doSearch = debounce((v: string) => setSearch(v), 500) + + return ( + <> +
Theme Browser
+ +
+ { + setSort(e.target.value) + loadThemes() + }} + style='width: 30%;' + options={[ + { label: 'Popular', value: 'popular' }, + { label: 'Creation Date', value: 'creationdate' }, + { label: 'Name', value: 'name' }, + { label: 'Likes', value: 'likes' }, + { label: 'Downloads', value: 'downloads' }, + { label: 'Recently Updated', value: 'recentlyupdated' }, + ]} + placeholder={'Sort by...'} + /> + + + doSearch(v)} + placeholder={'Search...'} + /> + +
+ + + + +
+ { + themeData().map((t) => ( + + )) + } +
+ + ) +} \ No newline at end of file diff --git a/plugins/dorion-theme-browser/components/ThemePage.tsx.scss b/plugins/dorion-theme-browser/components/ThemePage.tsx.scss new file mode 100644 index 0000000..4318eb4 --- /dev/null +++ b/plugins/dorion-theme-browser/components/ThemePage.tsx.scss @@ -0,0 +1,29 @@ +.shead { + margin-top: 16px; + margin-bottom: 8px; +} + +.bot16 { + margin-bottom: 16px; +} + +.themeCards { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-gap: 8px; + margin-top: 16px; + + width: 100%; +} + +.sortSection { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.searchBox { + flex-grow: 0 !important; + width: 50%; +} \ No newline at end of file diff --git a/plugins/dorion-theme-browser/index.ts b/plugins/dorion-theme-browser/index.ts new file mode 100644 index 0000000..c68dd67 --- /dev/null +++ b/plugins/dorion-theme-browser/index.ts @@ -0,0 +1,18 @@ +import { appName } from '../../api/api.js' +import { ThemePage } from './components/ThemePage.jsx' + +const { + settings: { + registerSection, + }, +} = shelter + +const uninjects = [ + registerSection('divider'), + registerSection('header', 'Theme Browser'), + registerSection('section', `${appName}-theme-browser`, 'Theme Browser', ThemePage), +] + +export const onUnload = () => { + uninjects.forEach((u) => u()) +} \ No newline at end of file diff --git a/plugins/dorion-theme-browser/plugin.json b/plugins/dorion-theme-browser/plugin.json new file mode 100644 index 0000000..1c8b26c --- /dev/null +++ b/plugins/dorion-theme-browser/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "Dorion Theme Browser", + "description": "Browse and install themes directly within Dorion", + "author": "SpikeHD" +} \ No newline at end of file diff --git a/plugins/inline-css/components/Editor.tsx b/plugins/inline-css/components/Editor.tsx index efe099b..ad5ab31 100644 --- a/plugins/inline-css/components/Editor.tsx +++ b/plugins/inline-css/components/Editor.tsx @@ -3,9 +3,9 @@ import hljs from 'highlight.js/lib/core' import cssModule from 'highlight.js/lib/languages/css' import {css, classes} from './Editor.scss' -import { debounce } from '../util' -import { Popout } from './Popout' -import { Window } from './Window' +import { debounce } from '../../../util/debounce.js' +import { Popout } from './Popout.jsx' +import { Window } from './Window.jsx' interface Props { styleElm?: HTMLStyleElement diff --git a/plugins/inline-css/components/Window.tsx b/plugins/inline-css/components/Window.tsx index 2d07228..44b8530 100644 --- a/plugins/inline-css/components/Window.tsx +++ b/plugins/inline-css/components/Window.tsx @@ -1,7 +1,7 @@ import { css, classes } from './Window.scss' -import Editor from './Editor' -import { Close } from './Close' -import { debounce } from '../util' +import Editor from './Editor.jsx' +import { Close } from './Close.jsx' +import { debounce } from '../../../util/debounce.js' const { ui: { injectCss }, diff --git a/plugins/inline-css/index.tsx b/plugins/inline-css/index.tsx index 1508d54..2f8efbf 100644 --- a/plugins/inline-css/index.tsx +++ b/plugins/inline-css/index.tsx @@ -1,4 +1,4 @@ -import Editor from './components/Editor' +import Editor from './components/Editor.jsx' const { settings: { diff --git a/plugins/inline-css/package.json b/plugins/inline-css/package.json index ffa5803..0e59972 100644 --- a/plugins/inline-css/package.json +++ b/plugins/inline-css/package.json @@ -3,5 +3,6 @@ "dependencies": { "@srsholmes/solid-code-input": "^0.0.18", "highlight.js": "^11.9.0" - } + }, + "type": "module" } \ No newline at end of file diff --git a/plugins/inline-css/util.ts b/util/debounce.ts similarity index 100% rename from plugins/inline-css/util.ts rename to util/debounce.ts