diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 38b26f0..cdb0b77 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -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; @@ -107,6 +108,7 @@ export const createCSSLoaderStore = (backend: Backend) => } return { + apiUrl: apiUrl, // Account Data apiShortToken: "", apiFullToken: "", diff --git a/src/index.tsx b/src/index.tsx index f073050..adb9e3b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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", () => ( + + + + )); + return { titleView: ( diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 3d3b960..10bfaa9 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -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"; diff --git a/src/lib/components/modals/index.ts b/src/lib/components/modals/index.ts new file mode 100644 index 0000000..313e35d --- /dev/null +++ b/src/lib/components/modals/index.ts @@ -0,0 +1,2 @@ +export * from "./nav-patch-info-modal"; +export * from "./optional-deps-modal"; diff --git a/src/lib/components/nav-patch-info-modal/NavPatchInfoModal.tsx b/src/lib/components/modals/nav-patch-info-modal/NavPatchInfoModal.tsx similarity index 93% rename from src/lib/components/nav-patch-info-modal/NavPatchInfoModal.tsx rename to src/lib/components/modals/nav-patch-info-modal/NavPatchInfoModal.tsx index 77eb015..fd80ab4 100644 --- a/src/lib/components/nav-patch-info-modal/NavPatchInfoModal.tsx +++ b/src/lib/components/modals/nav-patch-info-modal/NavPatchInfoModal.tsx @@ -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"); diff --git a/src/lib/components/nav-patch-info-modal/index.ts b/src/lib/components/modals/nav-patch-info-modal/index.ts similarity index 100% rename from src/lib/components/nav-patch-info-modal/index.ts rename to src/lib/components/modals/nav-patch-info-modal/index.ts diff --git a/src/lib/components/optional-deps-modal/OptionalDepsModal.tsx b/src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx similarity index 97% rename from src/lib/components/optional-deps-modal/OptionalDepsModal.tsx rename to src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx index ff114f8..bd4d878 100644 --- a/src/lib/components/optional-deps-modal/OptionalDepsModal.tsx +++ b/src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx @@ -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({ diff --git a/src/lib/components/optional-deps-modal/index.ts b/src/lib/components/modals/optional-deps-modal/index.ts similarity index 100% rename from src/lib/components/optional-deps-modal/index.ts rename to src/lib/components/modals/optional-deps-modal/index.ts diff --git a/src/lib/components/title-view/TitleView.tsx b/src/lib/components/title-view/TitleView.tsx index 2d7ed63..2f28e58 100644 --- a/src/lib/components/title-view/TitleView.tsx +++ b/src/lib/components/title-view/TitleView.tsx @@ -14,7 +14,7 @@ export function TitleView() { const onStoreClick = () => { Navigation.CloseSideMenus(); - Navigation.Navigate("/cssloader/theme-manager"); + Navigation.Navigate("/cssloader/theme-store"); }; return ( diff --git a/src/lib/hooks/index.ts b/src/lib/hooks/index.ts index eda6231..b5dcc91 100644 --- a/src/lib/hooks/index.ts +++ b/src/lib/hooks/index.ts @@ -1 +1,2 @@ export * from "./useForcedRerender"; +export * from "./useThemeInstallState"; diff --git a/src/lib/hooks/useThemeInstallState.ts b/src/lib/hooks/useThemeInstallState.ts new file mode 100644 index 0000000..f8a134c --- /dev/null +++ b/src/lib/hooks/useThemeInstallState.ts @@ -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"; +} diff --git a/src/lib/index.ts b/src/lib/index.ts index fc82a00..dbef729 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,4 @@ export * from "./components"; export * from "./hooks"; export * from "./utils"; +export * from "./providers"; diff --git a/src/lib/primitives/ConfirmModal.tsx b/src/lib/primitives/ConfirmModal.tsx index 4734daf..69a0c78 100644 --- a/src/lib/primitives/ConfirmModal.tsx +++ b/src/lib/primitives/ConfirmModal.tsx @@ -1,5 +1,5 @@ import { ConfirmModal as CM } from "@decky/ui"; -import { StyleProvider } from "../components"; +import { StyleProvider } from "../providers"; export function ConfirmModal({ closeModal, diff --git a/src/lib/primitives/Modal.tsx b/src/lib/primitives/Modal.tsx index 2375e89..d49a0af 100644 --- a/src/lib/primitives/Modal.tsx +++ b/src/lib/primitives/Modal.tsx @@ -1,5 +1,5 @@ import { ModalRoot } from "@decky/ui"; -import { StyleProvider } from "../components"; +import { StyleProvider } from "../providers"; export function Modal({ closeModal, diff --git a/src/lib/providers/index.ts b/src/lib/providers/index.ts new file mode 100644 index 0000000..8a0bc2d --- /dev/null +++ b/src/lib/providers/index.ts @@ -0,0 +1 @@ +export * from "./style-provider"; diff --git a/src/lib/components/style-provider/StyleProvider.tsx b/src/lib/providers/style-provider/StyleProvider.tsx similarity index 100% rename from src/lib/components/style-provider/StyleProvider.tsx rename to src/lib/providers/style-provider/StyleProvider.tsx diff --git a/src/lib/components/style-provider/index.ts b/src/lib/providers/style-provider/index.ts similarity index 100% rename from src/lib/components/style-provider/index.ts rename to src/lib/providers/style-provider/index.ts diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 58c2257..daefe64 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,2 +1,3 @@ export * from "./classname-merger"; export * from "./toggleThemeWithModals"; +export * from "./shorten-number"; diff --git a/src/lib/utils/shorten-number.ts b/src/lib/utils/shorten-number.ts new file mode 100644 index 0000000..f3c170d --- /dev/null +++ b/src/lib/utils/shorten-number.ts @@ -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; +} diff --git a/src/lib/utils/toggleThemeWithModals.tsx b/src/lib/utils/toggleThemeWithModals.tsx index b9b7d7f..b44e128 100644 --- a/src/lib/utils/toggleThemeWithModals.tsx +++ b/src/lib/utils/toggleThemeWithModals.tsx @@ -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(); diff --git a/src/modules/theme-store/components/BrowserSearchFields.tsx b/src/modules/theme-store/components/BrowserSearchFields.tsx index 2d9161f..6bed73d 100644 --- a/src/modules/theme-store/components/BrowserSearchFields.tsx +++ b/src/modules/theme-store/components/BrowserSearchFields.tsx @@ -42,7 +42,7 @@ export function BrowserSearchFields() { return ( <> - +
Sort - +
Refresh -
+
(null); + const firstCardRef = useRef(null); + + useEffect(() => { + void initializeStore(); + }, []); + return ( <> - + + {themes.items + .filter((theme) => theme.manifestVersion <= backendVersion) + .map((theme, index) => ( + + ))} + ); } diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx index dcce9f9..5bf61b2 100644 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -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(({ theme, size }, ref) => { +const cardWidth = { + 5: 152, + 4: 195, + 3: 260, +}; + +export const ThemeCard = forwardRef(({ 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 ( +
+ {installStatus === "outdated" && ( +
+ +
+ )} + { + // ADD IN CLICK LOGIC + }} + > +
+ +
+
+
+ + + {shortenNumber(theme.download.downloadCount) ?? theme.download.downloadCount} + +
+
+ + {shortenNumber(theme.starCount) ?? theme.starCount} +
+
+ + {theme.target} +
+
+
+
+ {theme.displayName} + + {theme.version} - Last Updated {new Date(theme.updated).toLocaleDateString()} + + By {theme.specifiedAuthor} +
+ +
+ ); }); diff --git a/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx b/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx new file mode 100644 index 0000000..bd1ce07 --- /dev/null +++ b/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx @@ -0,0 +1,8 @@ +import { themeCardStylesGenerator } from "@/styles"; +import { useThemeBrowserSharedStateValue } from "../context"; + +export function ThemeCardCSSVariableProvider() { + const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize"); + + return ; +} diff --git a/src/modules/theme-store/components/ThemeGridDisplay.tsx b/src/modules/theme-store/components/ThemeGridDisplay.tsx deleted file mode 100644 index eb7bd94..0000000 --- a/src/modules/theme-store/components/ThemeGridDisplay.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Focusable } from "@decky/ui"; -import { useThemeBrowserStoreValue } from "../context"; -import { useCSSLoaderStateValue } from "@/backend"; - -export function ThemeGridDisplay() { - const backendVersion = useCSSLoaderStateValue("backendVersion") - const themes = useThemeBrowserStoreValue("themes"); - return - {themes.items.filter((theme) => theme.manifestVersion <= backendVersion).map((theme, index) => ( - < - ))} - ; -} diff --git a/src/modules/theme-store/components/index.ts b/src/modules/theme-store/components/index.ts new file mode 100644 index 0000000..be45aeb --- /dev/null +++ b/src/modules/theme-store/components/index.ts @@ -0,0 +1,2 @@ +export * from "./ThemeCardCSSVariableProvider"; +export * from "./ThemeBrowserPage"; diff --git a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx index 6cc71b2..a5a2555 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -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 @@ -17,7 +19,7 @@ interface IThemeBrowserSharedStore const themeBrowserSharedStore = createStore((set) => { return { browserCardSize: 3, - setBrowserCardSize: (value: number) => set({ browserCardSize: value }), + setBrowserCardSize: (value: ColumnNumbers) => set({ browserCardSize: value }), }; }); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 4fe8da8..6435831 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -2,6 +2,7 @@ import { createContext, useContext, useRef } from "react"; import { FilterQueryResponse, ThemeQueryRequest, ThemeQueryResponse } from "@/types"; import { StoreApi, createStore, useStore } from "zustand"; import { getCSSLoaderState } from "@/backend"; +import { isEqual } from "lodash"; interface ThemeBrowserStoreValues { themes: ThemeQueryResponse; @@ -81,6 +82,7 @@ export function ThemeBrowserStoreProvider({ initializeStore: async () => { try { await get().getFilters(); + await get().getThemes(); } catch (error) {} }, getFilters: async () => { @@ -97,8 +99,12 @@ export function ThemeBrowserStoreProvider({ } catch (error) {} }, setSearchOpts(searchOpts) { - const prevSearchOpts = get().searchOpts; + const { searchOpts: prevSearchOpts, themes, getThemes } = get(); set({ searchOpts, prevSearchOpts }); + + if (!isEqual(prevSearchOpts, searchOpts) || themes.total === 0) { + getThemes(); + } }, refreshThemes: async () => {}, getThemes: async () => { diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx new file mode 100644 index 0000000..61c5b88 --- /dev/null +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -0,0 +1,32 @@ +import { Tabs } from "@decky/ui"; +import { ThemeBrowserPage, ThemeCardCSSVariableProvider } from "../components"; +import { ThemeBrowserStoreProvider } from "../context"; +import { useState } from "react"; + +export function ThemeStoreRouter() { + const [currentTab, setCurrentTab] = useState("allthemes"); + return ( +
+ + setCurrentTab(tab)} + tabs={[ + { + id: "allthemes", + title: "All Themes", + content: ( + + + + ), + }, + ]} + > +
+ ); +} diff --git a/src/styles/index.ts b/src/styles/index.ts index 3f89b5b..6e06c6a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1 +1,2 @@ -export * from "./stylesAsString"; +export * from "./styles-as-string"; +export * from "./theme-card-styles-generator"; diff --git a/src/styles/stylesAsString.ts b/src/styles/styles-as-string.ts similarity index 57% rename from src/styles/stylesAsString.ts rename to src/styles/styles-as-string.ts index e49b895..a7d30d2 100644 --- a/src/styles/stylesAsString.ts +++ b/src/styles/styles-as-string.ts @@ -9,6 +9,18 @@ export const styles = ` flex-direction: column !important; } +.flex-wrap { + flex-wrap: wrap !important; +} + +.gap-2 { + gap: 0.5rem !important; +} + +.gap-3 { + gap: 0.75rem !important; +} + .gap-4 { gap: 1rem !important; } @@ -57,6 +69,14 @@ export const styles = ` font-weight: bold !important; } +/* Fullscreen Routes */ + +.cl_fullscreenroute_container { + margin-top: 40px !important; + height: calc(100% - 40px) !important; + background: #0e141b !important; +} + /* TitleView */ .cl-title-view-button { @@ -192,4 +212,101 @@ export const styles = ` justify-content: center !important; gap: 5px !important; } + +/* Store Theme Cards */ +/* The variables should be injected wherever needed */ +/* This one actually is based on font-size, so EM makes sense */ + +.cl_storeitem_notifbubble { + position: absolute; + background: linear-gradient(135deg, #fca904 50%, transparent 51%); + z-index: 10001; + left: 0; + top: 0; + color: black; + font-size: var(--cl-storeitem-fontsize); + width: var(--cl-storeitem-bubblesize); + height: var(--cl-storeitem-bubblesize); +} +.cl_storeitem_bubbleicon { + padding: 0.25em; +} +.cl_storeitem_container { + display: flex; + flex-direction: column; + background-color: #ACB2C924; + overflow: hidden; + width: var(--cl-storeitem-width); +} +.gpfocuswithin.cl_storeitem_container { + background-color: #ACB2C947; +} +.cl_storeitem_imagecontainer { + overflow: hidden; + position: relative; + width: var(--cl-storeitem-width); + height: var(--cl-storeitem-imgheight); +} +.cl_storeitem_supinfocontainer { + display: flex; + gap: 0.5em; + width: 100%; + align-items: center; + justify-content: center; + position: absolute; + bottom: 0; + transform: translateY(100%); + opacity: 0; + transition-property: transform,opacity; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83); + transition-duration: 0.15s; + font-size: var(--cl-storeitem-fontsize); +} +.gpfocuswithin > div > .cl_storeitem_supinfocontainer { + transform: translateY(0); + opacity: 1; + transition-delay: 0.1s; +} +.cl_storeitem_maininfocontainer { + display: flex; + flex-direction: column; + padding: 0.5em; + font-size: var(--cl-storeitem-fontsize); +} +.cl_storeitem_image { + object-fit: cover; + transition-property: filter,transform; + transition-duration: 0.32s; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83); +} +.cl_storeitem_imagedarkener { + position: absolute; + top: 0; + left: 0; + opacity: 0; + transition-property: opacity; + transition-duration: 0.65s; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83); + background: linear-gradient(0deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 30%); + mix-blend-mode: multiply; + width: var(--cl-storeitem-width); + height: var(--cl-storeitem-imgheight); +} +.gpfocuswithin > div > .cl_storeitem_imagedarkener { + opacity: 1; +} +.cl_storeitem_title { + font-weight: bold; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +.cl_storeitem_iconinfoitem { + display: flex; + gap: 0.25em; + align-items: center; +} +.cl_storeitem_subtitle { + font-size: 0.75em; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index 434792f..86c5b0f 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -10,6 +10,18 @@ flex-direction: column !important; } +.flex-wrap { + flex-wrap: wrap !important; +} + +.gap-2 { + gap: 0.5rem !important; +} + +.gap-3 { + gap: 0.75rem !important; +} + .gap-4 { gap: 1rem !important; } @@ -58,6 +70,14 @@ font-weight: bold !important; } +/* Fullscreen Routes */ + +.cl_fullscreenroute_container { + margin-top: 40px !important; + height: calc(100% - 40px) !important; + background: #0e141b !important; +} + /* TitleView */ .cl-title-view-button { @@ -192,4 +212,101 @@ flex-wrap: wrap !important; justify-content: center !important; gap: 5px !important; +} + +/* Store Theme Cards */ +/* The variables should be injected wherever needed */ +/* This one actually is based on font-size, so EM makes sense */ + +.cl_storeitem_notifbubble { + position: absolute; + background: linear-gradient(135deg, #fca904 50%, transparent 51%); + z-index: 10001; + left: 0; + top: 0; + color: black; + font-size: var(--cl-storeitem-fontsize); + width: var(--cl-storeitem-bubblesize); + height: var(--cl-storeitem-bubblesize); +} +.cl_storeitem_bubbleicon { + padding: 0.25em; +} +.cl_storeitem_container { + display: flex; + flex-direction: column; + background-color: #ACB2C924; + overflow: hidden; + width: var(--cl-storeitem-width); +} +.gpfocuswithin.cl_storeitem_container { + background-color: #ACB2C947; +} +.cl_storeitem_imagecontainer { + overflow: hidden; + position: relative; + width: var(--cl-storeitem-width); + height: var(--cl-storeitem-imgheight); +} +.cl_storeitem_supinfocontainer { + display: flex; + gap: 0.5em; + width: 100%; + align-items: center; + justify-content: center; + position: absolute; + bottom: 0; + transform: translateY(100%); + opacity: 0; + transition-property: transform,opacity; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83); + transition-duration: 0.15s; + font-size: var(--cl-storeitem-fontsize); +} +.gpfocuswithin > div > .cl_storeitem_supinfocontainer { + transform: translateY(0); + opacity: 1; + transition-delay: 0.1s; +} +.cl_storeitem_maininfocontainer { + display: flex; + flex-direction: column; + padding: 0.5em; + font-size: var(--cl-storeitem-fontsize); +} +.cl_storeitem_image { + object-fit: cover; + transition-property: filter,transform; + transition-duration: 0.32s; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83); +} +.cl_storeitem_imagedarkener { + position: absolute; + top: 0; + left: 0; + opacity: 0; + transition-property: opacity; + transition-duration: 0.65s; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83); + background: linear-gradient(0deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 30%); + mix-blend-mode: multiply; + width: var(--cl-storeitem-width); + height: var(--cl-storeitem-imgheight); +} +.gpfocuswithin > div > .cl_storeitem_imagedarkener { + opacity: 1; +} +.cl_storeitem_title { + font-weight: bold; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +.cl_storeitem_iconinfoitem { + display: flex; + gap: 0.25em; + align-items: center; +} +.cl_storeitem_subtitle { + font-size: 0.75em; } \ No newline at end of file diff --git a/src/styles/theme-card-styles-generator.ts b/src/styles/theme-card-styles-generator.ts new file mode 100644 index 0000000..d448704 --- /dev/null +++ b/src/styles/theme-card-styles-generator.ts @@ -0,0 +1,18 @@ +import { ColumnNumbers } from "@/modules/theme-store/context"; + +export function themeCardStylesGenerator(size: ColumnNumbers) { + return ` + :root { + --cl-storeitem-width: ${size === 3 ? "260px" : size === 4 ? "195px" : "152px"}; + --cl-storeitem-imgheight: ${ + size === 3 + ? (260 / 16) * 10 + "px" + : size === 4 + ? (195 / 16) * 10 + "px" + : (152 / 16) * 10 + "px" + }; + --cl-storeitem-fontsize: ${size === 3 ? "1em" : size === 4 ? "0.75em" : "0.5em"}; + --cl-storeitem-bubblesize: ${size === 3 ? "40px" : size === 4 ? "30px" : "20px"}; + } + `; +} diff --git a/src/types/ThemeTypes.ts b/src/types/ThemeTypes.ts index dba92ad..cb92410 100644 --- a/src/types/ThemeTypes.ts +++ b/src/types/ThemeTypes.ts @@ -37,7 +37,7 @@ export enum Flags { "navPatch" = "REQUIRE_NAV_PATCH", } -export type LocalThemeStatus = "installed" | "outdated" | "local"; +export type LocalThemeStatus = "installed" | "outdated" | "local" | "notinstalled"; export type UpdateStatus = [string, LocalThemeStatus, false | MinimalCSSThemeInfo]; type ThemeErrorTitle = string;