Skip to content

Commit

Permalink
work on theme store
Browse files Browse the repository at this point in the history
  • Loading branch information
beebls committed Jul 7, 2024
1 parent 97faf89 commit 5f9e1ce
Show file tree
Hide file tree
Showing 34 changed files with 503 additions and 40 deletions.
2 changes: 2 additions & 0 deletions src/backend/state/theme-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { FetchError } from "../errors";
const apiUrl = "https://api.deckthemes.com";

export interface CSSLoaderStateValues {
apiUrl: string;
// Account Data
apiShortToken: string;
apiFullToken: string;
Expand Down Expand Up @@ -107,6 +108,7 @@ export const createCSSLoaderStore = (backend: Backend) =>
}

return {
apiUrl: apiUrl,
// Account Data
apiShortToken: "",
apiFullToken: "",
Expand Down
9 changes: 8 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { StyleProvider, TitleView } from "@/lib";
import { RiPaintFill } from "react-icons/ri";
import { QamTabPage } from "@/modules/qam-tab-page";
import { definePlugin } from "@decky/api";
import { definePlugin, routerHook } from "@decky/api";
import { getCSSLoaderState } from "@/backend";
import { getDeckyPatchState } from "./decky-patches";
import { ThemeStoreRouter } from "./modules/theme-store/pages/ThemeStoreRouter";

export default definePlugin(() => {
getCSSLoaderState().initializeStore();
getDeckyPatchState().initializeStore();

routerHook.addRoute("/cssloader/theme-store", () => (
<StyleProvider>
<ThemeStoreRouter />
</StyleProvider>
));

return {
titleView: (
<StyleProvider>
Expand Down
4 changes: 1 addition & 3 deletions src/lib/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
export * from "./title-view";
export * from "./motd-display";
export * from "./style-provider";
export * from "./preset-selection-dropdown";
export * from "./theme-patch";
export * from "./optional-deps-modal";
export * from "./nav-patch-info-modal";
export * from "./modals";
2 changes: 2 additions & 0 deletions src/lib/components/modals/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./nav-patch-info-modal";
export * from "./optional-deps-modal";
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useDeckyPatchStateAction } from "@/decky-patches";
import { ConfirmModal } from "../../primitives";
import { ConfirmModal } from "../../../primitives";

export function NavPatchInfoModal({ closeModal }: { closeModal?: () => void }) {
const setNavPatchState = useDeckyPatchStateAction("setNavPatchState");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Theme } from "@/types";
import { Modal } from "../../primitives";
import { Modal } from "../../../primitives";
import { DialogButton, Focusable } from "@decky/ui";

export function OptionalDepsModal({
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/title-view/TitleView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function TitleView() {

const onStoreClick = () => {
Navigation.CloseSideMenus();
Navigation.Navigate("/cssloader/theme-manager");
Navigation.Navigate("/cssloader/theme-store");
};

return (
Expand Down
1 change: 1 addition & 0 deletions src/lib/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./useForcedRerender";
export * from "./useThemeInstallState";
12 changes: 12 additions & 0 deletions src/lib/hooks/useThemeInstallState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LocalThemeStatus, PartialCSSThemeInfo, Theme } from "@/types";
import { useCSSLoaderStateValue } from "@/backend";

export function useThemeInstallState(theme: Theme | PartialCSSThemeInfo): LocalThemeStatus {
const updateStatuses = useCSSLoaderStateValue("updateStatuses");

const status = updateStatuses.find((status) => status[0] === theme.id);
if (status) {
return status[1];
}
return "notinstalled";
}
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./components";
export * from "./hooks";
export * from "./utils";
export * from "./providers";
2 changes: 1 addition & 1 deletion src/lib/primitives/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ConfirmModal as CM } from "@decky/ui";
import { StyleProvider } from "../components";
import { StyleProvider } from "../providers";

export function ConfirmModal({
closeModal,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/primitives/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ModalRoot } from "@decky/ui";
import { StyleProvider } from "../components";
import { StyleProvider } from "../providers";

export function Modal({
closeModal,
Expand Down
1 change: 1 addition & 0 deletions src/lib/providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./style-provider";
File renamed without changes.
1 change: 1 addition & 0 deletions src/lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./classname-merger";
export * from "./toggleThemeWithModals";
export * from "./shorten-number";
48 changes: 48 additions & 0 deletions src/lib/utils/shorten-number.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Code from the short-number package, could not be imported due
// to TypeScript issues.
// https://www.npmjs.com/package/short-number

export function shortenNumber(num: number) {
if (typeof num !== "number") {
throw new TypeError("Expected a number");
}

if (num > 1e19) {
throw new RangeError("Input expected to be < 1e19");
}

if (num < -1e19) {
throw new RangeError("Input expected to be > 1e19");
}

if (Math.abs(num) < 1000) {
return num;
}

var shortNumber;
var exponent;
var size;
var sign = num < 0 ? "-" : "";
var suffixes = {
K: 6,
M: 9,
B: 12,
T: 16,
};

num = Math.abs(num);
size = Math.floor(num).toString().length;

exponent = size % 3 === 0 ? size - 3 : size - (size % 3);
shortNumber = String(Math.round(10 * (num / Math.pow(10, exponent))) / 10);

for (var suffix in suffixes) {
// @ts-ignore
if (exponent < suffixes[suffix]) {
shortNumber += suffix;
break;
}
}

return sign + shortNumber;
}
2 changes: 1 addition & 1 deletion src/lib/utils/toggleThemeWithModals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Flags, Theme } from "@/types";
import { showModal } from "@decky/ui";
import { getCSSLoaderState } from "@/backend";
import { getDeckyPatchState } from "../../decky-patches";
import { NavPatchInfoModal, OptionalDepsModal } from "../components";
import { NavPatchInfoModal, OptionalDepsModal } from "../components/modals";

export async function toggleThemeWithModals(theme: Theme, value: boolean, rerender?: () => void) {
const { toggleTheme } = getCSSLoaderState();
Expand Down
9 changes: 3 additions & 6 deletions src/modules/theme-store/components/BrowserSearchFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function BrowserSearchFields() {
return (
<>
<PanelSectionRow>
<Focusable className="flex w-full justify-between">
<Focusable className="flex justify-between">
<div className="cl-store-filter-field-container">
<span className="DialogLabel">Sort</span>
<Dropdown
Expand Down Expand Up @@ -72,7 +72,7 @@ export function BrowserSearchFields() {
</Focusable>
</PanelSectionRow>
<PanelSectionRow>
<Focusable className="flex items-center w-full justify-between">
<Focusable className="flex items-center justify-between">
<div className="cl-store-searchbar">
<TextField
label="Search"
Expand All @@ -87,10 +87,7 @@ export function BrowserSearchFields() {
<FaRotate />
<span>Refresh</span>
</DialogButton>
<div
style={{ maxWidth: "20%", minWidth: "20%", marginLeft: "auto" }}
className="cl-store-scale-slider"
>
<div className="cl-store-scale-slider">
<SliderField
min={3}
max={5}
Expand Down
34 changes: 33 additions & 1 deletion src/modules/theme-store/components/ThemeBrowserPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
import { Focusable } from "@decky/ui";
import { useThemeBrowserStoreAction, useThemeBrowserStoreValue } from "../context";
import { BrowserSearchFields } from "./BrowserSearchFields";
import { useCSSLoaderStateValue } from "@/backend";
import { ThemeCard } from "./ThemeCard";
import { useEffect, useRef } from "react";

export function ThemeBrowserPage() {
const initializeStore = useThemeBrowserStoreAction("initializeStore");
const themes = useThemeBrowserStoreValue("themes");
const indexToSnapToOnLoad = useThemeBrowserStoreValue("indexToSnapToOnLoad");
const backendVersion = useCSSLoaderStateValue("backendVersion");

const endOfPageRef = useRef<HTMLDivElement>(null);
const firstCardRef = useRef<HTMLDivElement>(null);

useEffect(() => {
void initializeStore();
}, []);

return (
<>
<BrowserSearchFields />

<Focusable className="flex flex-wrap justify-center gap-3">
{themes.items
.filter((theme) => theme.manifestVersion <= backendVersion)
.map((theme, index) => (
<ThemeCard
ref={
index === indexToSnapToOnLoad
? endOfPageRef
: index === 0
? firstCardRef
: undefined
}
key={theme.id}
theme={theme}
/>
))}
</Focusable>
</>
);
}
79 changes: 75 additions & 4 deletions src/modules/theme-store/components/ThemeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,86 @@
import { PartialCSSThemeInfo } from "@/types";
import { useThemeBrowserSharedStateValue } from "../context";
import {
ColumnNumbers,
useThemeBrowserSharedStateValue,
useThemeBrowserStoreValue,
} from "../context";
import { forwardRef } from "react";
import { shortenNumber, useThemeInstallState } from "@/lib";
import { useCSSLoaderStateValue } from "@/backend";
import { AiOutlineDownload } from "react-icons/ai";
import { Focusable } from "@decky/ui";
import { FaBullseye, FaDownload, FaStar } from "react-icons/fa6";

interface ThemeCardProps {
theme: PartialCSSThemeInfo;
size?: number;
size?: ColumnNumbers;
}

export const ThemeCard = forwardRef<HTMLElement, ThemeCardProps>(({ theme, size }, ref) => {
const cardWidth = {
5: 152,
4: 195,
3: 260,
};

export const ThemeCard = forwardRef<HTMLDivElement, ThemeCardProps>(({ theme, size }, ref) => {
const apiUrl = useCSSLoaderStateValue("apiUrl");
const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize");
const cols = size ?? browserCardSize;
const installStatus = useThemeInstallState(theme);

const imageUrl =
theme?.images[0]?.id && theme.images[0].id !== "MISSING"
? `${apiUrl}/blobs/${theme.images[0].id}`
: `https://share.deckthemes.com/cssplaceholder.png`;

return null;
return (
<div className="relative">
{installStatus === "outdated" && (
<div className="cl_storeitem_notifbubble">
<AiOutlineDownload className="cl_storeitem_bubbleicon" />
</div>
)}
<Focusable
ref={ref}
className="cl_storeitem_container"
focusWithinClassName="gpfocuswithin"
onActivate={() => {
// ADD IN CLICK LOGIC
}}
>
<div className="cl_storeitem_imagecontainer">
<img
className="cl_storeitem_image"
src={imageUrl}
width={cardWidth[cols]}
height={(cardWidth[cols] / 16) * 10}
/>
<div className="cl_storeitem_imagedarkener" />
<div className="cl_storeitem_supinfocontainer">
<div className="cl_storeitem_iconinfoitem">
<FaDownload />
<span>
{shortenNumber(theme.download.downloadCount) ?? theme.download.downloadCount}
</span>
</div>
<div className="cl_storeitem_iconinfoitem">
<FaStar />
<span>{shortenNumber(theme.starCount) ?? theme.starCount}</span>
</div>
<div className="cl_storeitem_iconinfoitem">
<FaBullseye />
<span>{theme.target}</span>
</div>
</div>
</div>
<div className="cl_storeitem_maininfocontainer">
<span className="cl_storeitem_title">{theme.displayName}</span>
<span className="cl_storeitem_subtitle">
{theme.version} - Last Updated {new Date(theme.updated).toLocaleDateString()}
</span>
<span className="cl_storeitem_subtitle">By {theme.specifiedAuthor}</span>
</div>
</Focusable>
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { themeCardStylesGenerator } from "@/styles";
import { useThemeBrowserSharedStateValue } from "../context";

export function ThemeCardCSSVariableProvider() {
const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize");

return <style>{themeCardStylesGenerator(browserCardSize)}</style>;
}
13 changes: 0 additions & 13 deletions src/modules/theme-store/components/ThemeGridDisplay.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions src/modules/theme-store/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./ThemeCardCSSVariableProvider";
export * from "./ThemeBrowserPage";
8 changes: 5 additions & 3 deletions src/modules/theme-store/context/ThemeBrowserSharedStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import { createStore, useStore } from "zustand";

export type ColumnNumbers = 3 | 4 | 5;

interface ThemeBrowserSharedStoreValues {
browserCardSize: number;
browserCardSize: ColumnNumbers;
}

interface ThemeBrowserSharedStoreActions {
setBrowserCardSize: (value: number) => void;
setBrowserCardSize: (value: ColumnNumbers) => void;
}

interface IThemeBrowserSharedStore
Expand All @@ -17,7 +19,7 @@ interface IThemeBrowserSharedStore
const themeBrowserSharedStore = createStore<IThemeBrowserSharedStore>((set) => {
return {
browserCardSize: 3,
setBrowserCardSize: (value: number) => set({ browserCardSize: value }),
setBrowserCardSize: (value: ColumnNumbers) => set({ browserCardSize: value }),
};
});

Expand Down
Loading

0 comments on commit 5f9e1ce

Please sign in to comment.