From 63925c6235cf40261bb39e5cd31b882dbdbe4393 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 21 Jan 2024 18:38:52 -0700 Subject: [PATCH 01/53] WIP: display uploaded profiles in settings --- src/api.ts | 3 +- src/apiTypes/AccountData.ts | 9 ++ src/components/Modals/PremiumFeatureModal.tsx | 15 ++ src/components/Modals/UploadProfileModal.tsx | 63 ++++++++ src/components/Modals/index.ts | 1 + src/pages/settings/PresetSettings.tsx | 134 +++++++++++++++++- src/pages/settings/SettingsPageRouter.tsx | 3 +- .../theme-manager/ThemeManagerRouter.tsx | 2 - src/state/CssLoaderState.tsx | 5 +- 9 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 src/components/Modals/PremiumFeatureModal.tsx create mode 100644 src/components/Modals/UploadProfileModal.tsx diff --git a/src/api.ts b/src/api.ts index dd2a9d7..3338293 100644 --- a/src/api.ts +++ b/src/api.ts @@ -58,7 +58,7 @@ export async function logInWithShortToken( setGlobalState("apiShortToken", shortTokenValue); setGlobalState("apiFullToken", data.token); setGlobalState("apiTokenExpireDate", new Date().valueOf() + 1000 * 60 * 10); - genericGET(`/auth/me`, true, data.token).then((meData) => { + genericGET(`/auth/me_full`, true, data.token).then((meData) => { if (meData?.username) { setGlobalState("apiMeData", meData); toast("Logged In!", `Logged in as ${meData.username}`); @@ -70,6 +70,7 @@ export async function logInWithShortToken( }) .catch((err) => { console.error(`Error authenticating from short token.`, err); + toast("Error Authenticating", JSON.stringify(err)); }); } else { toast("Invalid Token", "Token must be 12 characters long."); diff --git a/src/apiTypes/AccountData.ts b/src/apiTypes/AccountData.ts index b7f2a3e..61c9acf 100644 --- a/src/apiTypes/AccountData.ts +++ b/src/apiTypes/AccountData.ts @@ -12,6 +12,15 @@ export interface AccountData extends UserInfo { permissions: Permissions[]; } +export type PremiumTiers = "None" | "Tier1" | "Tier2" | "Tier3"; + +export interface FullAccountData extends AccountData { + hasDeckKey: boolean; + premiumTier: PremiumTiers; + email: string; + lastLoginDate: string; +} + export interface AuthContextContents { accountInfo: AccountData | undefined; setAccountInfo: diff --git a/src/components/Modals/PremiumFeatureModal.tsx b/src/components/Modals/PremiumFeatureModal.tsx new file mode 100644 index 0000000..ddb0e2e --- /dev/null +++ b/src/components/Modals/PremiumFeatureModal.tsx @@ -0,0 +1,15 @@ +import { Focusable, ModalRoot } from "decky-frontend-lib"; + +export function PremiumFeatureModal({ closeModal, blurb }: { closeModal?: any; blurb: string }) { + return ( + + + Premium Feature +

{blurb}

+ + To support DeckThemes and unlock premium features, visit https://patreon.com/deckthemes + +
+
+ ); +} diff --git a/src/components/Modals/UploadProfileModal.tsx b/src/components/Modals/UploadProfileModal.tsx new file mode 100644 index 0000000..67c294a --- /dev/null +++ b/src/components/Modals/UploadProfileModal.tsx @@ -0,0 +1,63 @@ +import { CssLoaderContextProvider, useCssLoaderState } from "../../state"; +import * as python from "../../python"; +import { + ButtonItem, + ConfirmModal, + DropdownItem, + Focusable, + ModalRoot, + TextField, + ToggleField, +} from "decky-frontend-lib"; +import { useMemo, useState } from "react"; +import { Flags } from "../../ThemeTypes"; + +export function UploadProfileModalRoot({ closeModal }: { closeModal?: any }) { + return ( + + {/* @ts-ignore */} + + + + + ); +} + +function UploadProfileModal() { + const { localThemeList } = useCssLoaderState(); + let [selectedProfile, setProfile] = useState( + localThemeList.find((e) => e.flags.includes(Flags.isPreset))?.id + ); + const profiles = useMemo(() => { + return localThemeList.filter((e) => e.flags.includes(Flags.isPreset)); + }, [localThemeList]); + const eligibleProfiles = useMemo(() => { + return profiles; + }, [profiles]); + + const [isPublic, setPublic] = useState(false); + const [description, setDescription] = useState(""); + + return ( + + Upload Profile + ({ data: e.id, label: e.display_name }))} + onChange={(chosen) => { + setProfile(chosen.data); + }} + label="Profile To Upload" + /> + + setDescription(e.target.value)} + /> + + Upload + + + ); +} diff --git a/src/components/Modals/index.ts b/src/components/Modals/index.ts index e6ef5d1..3b796f0 100644 --- a/src/components/Modals/index.ts +++ b/src/components/Modals/index.ts @@ -1,2 +1,3 @@ export * from "./CreatePresetModal"; export * from "./ThemeSettingsModal"; +export * from "./UploadProfileModal"; diff --git a/src/pages/settings/PresetSettings.tsx b/src/pages/settings/PresetSettings.tsx index b1ddc20..b7b3a7d 100644 --- a/src/pages/settings/PresetSettings.tsx +++ b/src/pages/settings/PresetSettings.tsx @@ -1,14 +1,22 @@ -import { Focusable, PanelSection } from "decky-frontend-lib"; +import { ButtonItem, DialogButton, Focusable, PanelSection, showModal } from "decky-frontend-lib"; import { useCssLoaderState } from "../../state"; import { Flags, Theme } from "../../ThemeTypes"; -import { useState } from "react"; -import { PresetSelectionDropdown } from "../../components"; +import { useEffect, useState } from "react"; +import { + PresetSelectionDropdown, + UploadProfileModalRoot, + VariableSizeCard, +} from "../../components"; import { FullscreenProfileEntry } from "../../components/ThemeSettings/FullscreenProfileEntry"; -import { installTheme } from "../../api"; +import { genericGET, installTheme, logInWithShortToken } from "../../api"; import * as python from "../../python"; +import { PartialCSSThemeInfo, ThemeQueryResponse } from "../../apiTypes"; +import { ThemeBrowserCardStyles } from "../../components/Styles"; +import { PremiumFeatureModal } from "../../components/Modals/PremiumFeatureModal"; export function PresetSettings() { - const { localThemeList, setGlobalState, updateStatuses } = useCssLoaderState(); + const { localThemeList, setGlobalState, updateStatuses, apiShortToken, apiFullToken, apiMeData } = + useCssLoaderState(); const [isInstalling, setInstalling] = useState(false); @@ -47,6 +55,122 @@ export function PresetSettings() { ))} + + + ); } + +function UploadedProfilesDisplay() { + const { apiFullToken, apiShortToken, apiMeData } = useCssLoaderState(); + + const [publicProfiles, setPublicProfiles] = useState([]); + const [privateProfiles, setPrivateProfiles] = useState([]); + const [profilesLoaded, setLoaded] = useState(false); + useEffect(() => { + async function getUserProfiles() { + if (!apiFullToken) { + await logInWithShortToken(); + } + + // Since the short token could be invalid, we still have to re-check for if the log in actually worked. + // The react value doesn't update mid function, so we re-grab it. + const upToDateFullToken = python.globalState?.getGlobalState("apiFullToken"); + console.log("up to date token", upToDateFullToken); + if (!upToDateFullToken) return; + const publicProfileData = await genericGET("/users/me/themes?filters=", true); + if (publicProfileData && publicProfileData.total > 0) { + setPublicProfiles(publicProfileData.items); + } + const privateProfileData = await genericGET("/users/me/themes/private?filters=", true); + if (privateProfileData && privateProfileData.total > 0) { + setPrivateProfiles(privateProfileData.items); + } + setLoaded(true); + } + if (apiShortToken) getUserProfiles(); + }, []); + + if (!apiMeData) { + return ( + <> + {apiShortToken ? ( + <> + Loading + + ) : ( + <> + + You are not logged in. Log In with your DeckThemes account to view your uploaded + profiles. + + + )} + + ); + } + + return ( + <> + + + { + if (apiMeData.premiumTier && apiMeData.premiumTier !== "None") { + showModal(); + return; + } + showModal( + + ); + return; + }} + > + Upload Profile + + {profilesLoaded ? ( + <> + + {publicProfiles.length > 0 && ( + <> + + Public Profiles: + + {publicProfiles.map((e) => ( + {}} /> + ))} + + + + )} + {apiMeData.premiumTier && + apiMeData.premiumTier !== "None" && + privateProfiles.length > 0 ? ( + <> + + Private Profiles: + + {privateProfiles.map((e) => ( + {}} /> + ))} + + + + ) : null} + + + ) : ( + Loading Profiles... + )} + + + ); +} diff --git a/src/pages/settings/SettingsPageRouter.tsx b/src/pages/settings/SettingsPageRouter.tsx index b2f8952..365e32a 100644 --- a/src/pages/settings/SettingsPageRouter.tsx +++ b/src/pages/settings/SettingsPageRouter.tsx @@ -5,13 +5,14 @@ import { ThemeSettings } from "./ThemeSettings"; import { PresetSettings } from "./PresetSettings"; import { PluginSettings } from "./PluginSettings"; import { Credits } from "./Credits"; -import { AiFillGithub, AiFillHeart } from "react-icons/ai"; import { DonatePage } from "./DonatePage"; import { FaFolder, FaGithub, FaHeart } from "react-icons/fa"; +import { ThemeBrowserCardStyles } from "../../components/Styles"; export function SettingsPageRouter() { return ( <> + + { + uploadZipAsBlob("round.zip"); + }} + > + TEST + <> {localThemeList .filter((e) => !unpinnedThemes.includes(e.id) && !e.flags.includes(Flags.isPreset)) diff --git a/src/components/TaskStatus.tsx b/src/components/TaskStatus.tsx new file mode 100644 index 0000000..7fc0b2f --- /dev/null +++ b/src/components/TaskStatus.tsx @@ -0,0 +1,153 @@ +import { useState, useEffect } from "react"; +import { TaskQueryResponse } from "../apiTypes/SubmissionTypes"; +import { getTaskStatus } from "../backend/apiHelpers/profileUploadingHelpers"; +import { BsCheckCircleFill, BsXCircleFill } from "react-icons/bs"; +import { ImSpinner5 } from "react-icons/im"; + +export function TaskStatus({ + task, + onFinish, +}: { + task: string; + onFinish: (success: boolean) => void; +}) { + const [apiStatus, setStatus] = useState(null); + + async function getStatus() { + if (task) { + const data = await getTaskStatus(task); + setStatus(data); + } + } + useEffect(() => { + if (apiStatus?.completed === null) { + setTimeout(() => { + getStatus(); + }, 1000); + } + if (apiStatus?.completed) { + onFinish(apiStatus.success); + } + }, [apiStatus]); + + useEffect(() => { + getStatus(); + }, [task]); + + if (!apiStatus) { + return Loading; + } + + // This is 100% ripped from deckthemes + // Eventually please do re-do this + return ( + <> + +
+
+ {apiStatus.name} + Task {task?.split("-")[0]} +
+ {apiStatus.completed ? ( + <> +
+ {apiStatus.success ? ( +
+ + Success +
+ ) : ( +
+ + Failed +
+ )} +
+ + {apiStatus?.success ? "Completed " : "Failed "}In{" "} + + {(new Date(apiStatus.completed).valueOf() - new Date(apiStatus.started).valueOf()) / + 1000}{" "} + + Seconds + + {!apiStatus.success && {apiStatus.status}} + + ) : ( + <> +
+ + Processing +
+ {apiStatus.status} + + )} +
+ + ); +} From ed88cbc8da14790969bbbb7e70cca68fcacfc98e Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 2 Mar 2024 18:49:39 -0700 Subject: [PATCH 06/53] Working profile uploadig!!!!!! --- src/backend/apiHelpers/fetchWrappers.ts | 28 ++++--- .../apiHelpers/profileUploadingHelpers.ts | 82 ++++++++----------- src/components/Modals/UploadProfileModal.tsx | 29 +++++-- src/components/QAMTab/QAMThemeToggleList.tsx | 9 +- src/pages/settings/PresetSettings.tsx | 63 +++++++------- 5 files changed, 108 insertions(+), 103 deletions(-) diff --git a/src/backend/apiHelpers/fetchWrappers.ts b/src/backend/apiHelpers/fetchWrappers.ts index 7457f12..b4da010 100644 --- a/src/backend/apiHelpers/fetchWrappers.ts +++ b/src/backend/apiHelpers/fetchWrappers.ts @@ -2,18 +2,29 @@ import { refreshToken } from "../../api"; import { toast } from "../../python"; import { globalState, server } from "../pythonRoot"; +// function createHeadersObj(authToken: string | undefined, request: RequestInit | undefined) { +// const headers = new Headers(); +// if (request && request.headers) { +// for (const [key, value] of Object.entries(request.headers)) { +// headers.append(key, value); +// } +// } +// if (authToken) headers.set("Authorization", `Bearer ${authToken}`); + +// return headers; +// } + function createHeadersObj(authToken: string | undefined, request: RequestInit | undefined) { - const headers = new Headers(); + let headers = {}; if (request && request.headers) { for (const [key, value] of Object.entries(request.headers)) { - headers.append(key, value); + headers[key] = value; } } - if (authToken) headers.set("Authorization", `Bearer ${authToken}`); + if (authToken) headers["Authorization"] = `Bearer ${authToken}`; return headers; } - export async function genericApiFetch( fetchPath: string, request: RequestInit | undefined = undefined, @@ -39,14 +50,7 @@ export async function genericApiFetch( const { apiUrl } = globalState!.getPublicState(); function doTheFetching(authToken: string | undefined = undefined) { const headers = createHeadersObj(authToken, request); - console.log("TOKEN", authToken); - console.log("TEST", { - method: "GET", - // If a custom method is specified in request it will overwrite - ...request, - headers: headers, - }); return server! .fetchNoCors(`${apiUrl}${fetchPath}`, { method: "GET", @@ -68,8 +72,6 @@ export async function genericApiFetch( throw new Error(`Res not OK!, code ${res.status} - ${res.body}`); }) .then((json) => { - console.log("JSON", json); - if (json) { return json; } diff --git a/src/backend/apiHelpers/profileUploadingHelpers.ts b/src/backend/apiHelpers/profileUploadingHelpers.ts index 0f82d50..cdf9a2d 100644 --- a/src/backend/apiHelpers/profileUploadingHelpers.ts +++ b/src/backend/apiHelpers/profileUploadingHelpers.ts @@ -1,66 +1,52 @@ +import { refreshToken } from "../../api"; import { TaskQueryResponse } from "../../apiTypes/SubmissionTypes"; +import { server } from "../pythonRoot"; import { genericApiFetch } from "./fetchWrappers"; -async function fetchLocalZip(fileName: string) { - if (!fileName.endsWith(".zip")) { - throw new Error(`File must be a .zip!`); - } - const filesRes = await fetch(`/themes_custom/${fileName}`); - if (!filesRes.ok) { - throw new Error(`Couldn't fetch zip!`); - } - const rawBlob = await filesRes.blob(); - const correctBlob = new Blob([rawBlob], { type: "application/x-zip-compressed" }); - return correctBlob; -} - -export async function uploadZipAsBlob(fileName: string): Promise { - if (!fileName.endsWith(".zip")) { - throw new Error(`File must be a .zip!`); - } - const fileBlob = await fetchLocalZip(fileName); - console.log("BLOB", fileBlob); - const formData = new FormData(); - formData.append("File", fileBlob, fileName); - console.log("FORM", formData); - - const json = await genericApiFetch( - "/blobs", - { - method: "POST", - headers: { - "Content-Type": "multipart/form-data", - }, - body: formData, - }, - { requiresAuth: true } - ); - if (json) { - return json; - } - throw new Error(`No json returned!`); -} +type BlobResponse = { + message: { + blobType: string; + downloadCount: number; + id: string; + uploaded: string; + }; +}; export async function publishProfile( - profileId: string, + profileName: string, isPublic: boolean, description: string ): Promise { - // const zipName = `${profileId}.zip`; - const zipName = "round.zip"; - const blobId = await uploadZipAsBlob(zipName); - if (!blobId) throw new Error(`No blobId returned!`); + const token = await refreshToken(); + + const deckyRes = await server!.callPluginMethod<{}, BlobResponse>("upload_theme", { + name: profileName, + base_url: "https://api.deckthemes.com", + bearer_token: token, + }); + if (!deckyRes.success) { + throw new Error(`Failed To Call Backend ${deckyRes.result.toString()}`); + } + deckyRes.result = deckyRes.result as BlobResponse; + if (!deckyRes.result?.message?.id) { + throw new Error(`No blobId returned!`); + } + const blobId = deckyRes.result.message.id; - // ALL OF THIS IS UNTESTED, BLOB IS 415'ing RN const json = await genericApiFetch( `/submissions/css_zip`, { method: "POST", + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ blob: blobId, - imageBlobs: [], - description: description, - privateSubmission: !isPublic, + meta: { + imageBlobs: [], + description: description, + privateSubmission: !isPublic, + }, }), }, { requiresAuth: true } diff --git a/src/components/Modals/UploadProfileModal.tsx b/src/components/Modals/UploadProfileModal.tsx index 34dbcb3..f85d068 100644 --- a/src/components/Modals/UploadProfileModal.tsx +++ b/src/components/Modals/UploadProfileModal.tsx @@ -14,18 +14,30 @@ import { Flags } from "../../ThemeTypes"; import { publishProfile } from "../../backend/apiHelpers/profileUploadingHelpers"; import { TaskStatus } from "../TaskStatus"; -export function UploadProfileModalRoot({ closeModal }: { closeModal?: any }) { +export function UploadProfileModalRoot({ + closeModal, + onUploadFinish, +}: { + closeModal?: any; + onUploadFinish: () => void; +}) { return ( {/* @ts-ignore */} - + ); } -function UploadProfileModal() { +function UploadProfileModal({ + closeModal, + onUploadFinish, +}: { + closeModal: () => void; + onUploadFinish: () => void; +}) { const { localThemeList } = useCssLoaderState(); const [selectedProfile, setProfile] = useState( localThemeList.find((e) => e.flags.includes(Flags.isPreset))?.id @@ -49,7 +61,11 @@ function UploadProfileModal() { if (!selectedProfile) return; setUploadStatus("submitting"); // eventually run the submit here - const taskId = await publishProfile(selectedProfile, isPublic, description); + const taskId = await publishProfile( + localThemeList.find((e) => e.id === selectedProfile)!.name, + isPublic, + description + ); setUploadStatus("taskStatus"); setTaskId(taskId); } @@ -57,7 +73,8 @@ function UploadProfileModal() { function onTaskFinish(success: boolean) { setUploadStatus("completed"); if (success) { - // closeModal(); + closeModal(); + onUploadFinish(); } } @@ -82,7 +99,7 @@ function UploadProfileModal() { value={description} onChange={(e) => setDescription(e.target.value)} /> - + Upload
diff --git a/src/components/QAMTab/QAMThemeToggleList.tsx b/src/components/QAMTab/QAMThemeToggleList.tsx index aacb9e4..ed0d608 100644 --- a/src/components/QAMTab/QAMThemeToggleList.tsx +++ b/src/components/QAMTab/QAMThemeToggleList.tsx @@ -5,7 +5,7 @@ import { Flags } from "../../ThemeTypes"; import { ThemeErrorCard } from "../ThemeErrorCard"; import { BsArrowDown } from "react-icons/bs"; import { FaEyeSlash } from "react-icons/fa"; -import { uploadZipAsBlob } from "../../backend/apiHelpers/profileUploadingHelpers"; +import { publishProfile } from "../../backend/apiHelpers/profileUploadingHelpers"; export function QAMThemeToggleList() { const { localThemeList, unpinnedThemes } = useCssLoaderState(); @@ -40,13 +40,6 @@ export function QAMThemeToggleList() { `} - { - uploadZipAsBlob("round.zip"); - }} - > - TEST - <> {localThemeList .filter((e) => !unpinnedThemes.includes(e.id) && !e.flags.includes(Flags.isPreset)) diff --git a/src/pages/settings/PresetSettings.tsx b/src/pages/settings/PresetSettings.tsx index 04fe73e..f825669 100644 --- a/src/pages/settings/PresetSettings.tsx +++ b/src/pages/settings/PresetSettings.tsx @@ -71,36 +71,43 @@ function UploadedProfilesDisplay() { >([]); const [profilesLoaded, setLoaded] = useState(false); - useEffect(() => { - async function getUserProfiles() { - if (!apiFullToken) { - await logInWithShortToken(); - } - - // Since the short token could be invalid, we still have to re-check for if the log in actually worked. - // The react value doesn't update mid function, so we re-grab it. - const upToDateFullToken = python.globalState?.getGlobalState("apiFullToken"); - if (!upToDateFullToken) return; - - let profileArray: PartialCSSThemeInfo[] = []; - const publicProfileData = await genericGET("/users/me/themes?filters=", true); - if (publicProfileData && publicProfileData.total > 0) { - profileArray.push(...publicProfileData.items); - } - const privateProfileData = await genericGET("/users/me/themes/private?filters=", true); - if (privateProfileData && privateProfileData.total > 0) { - profileArray.push( - ...privateProfileData.items.map((e: PartialCSSThemeInfo) => ({ ...e, isPrivate: true })) - ); - } - profileArray.sort((a, b) => (a.name > b.name ? 1 : -1)); - setUploadedProfiles(profileArray); - - setLoaded(true); + + async function getUserProfiles() { + setLoaded(false); + if (!apiFullToken) { + await logInWithShortToken(); + } + + // Since the short token could be invalid, we still have to re-check for if the log in actually worked. + // The react value doesn't update mid function, so we re-grab it. + const upToDateFullToken = python.globalState?.getGlobalState("apiFullToken"); + if (!upToDateFullToken) return; + + let profileArray: PartialCSSThemeInfo[] = []; + const publicProfileData = await genericGET("/users/me/themes?filters=", true); + if (publicProfileData && publicProfileData.total > 0) { + profileArray.push(...publicProfileData.items); + } + const privateProfileData = await genericGET("/users/me/themes/private?filters=", true); + if (privateProfileData && privateProfileData.total > 0) { + profileArray.push( + ...privateProfileData.items.map((e: PartialCSSThemeInfo) => ({ ...e, isPrivate: true })) + ); } - if (apiShortToken) getUserProfiles(); + profileArray.sort((a, b) => (a.name > b.name ? 1 : -1)); + setUploadedProfiles(profileArray); + + setLoaded(true); + } + + useEffect(() => { + if (apiShortToken) void getUserProfiles(); }, []); + function onUploadFinish() { + void getUserProfiles(); + } + if (!apiMeData) { return ( <> @@ -134,7 +141,7 @@ function UploadedProfilesDisplay() { }} onClick={() => { if (apiMeData.premiumTier && apiMeData.premiumTier !== "None") { - showModal(); + showModal(); return; } showModal( From 9985da7053f078ccf0dbfaf75b750d17884f5631 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:16:36 -0700 Subject: [PATCH 07/53] finish profile uploading --- src/backend/apiHelpers/fetchWrappers.ts | 47 ++-- .../apiHelpers/profileUploadingHelpers.ts | 46 ++-- src/components/Loading.tsx | 29 +++ src/components/Modals/UploadProfileModal.tsx | 68 ++++-- src/components/TaskStatus.tsx | 205 +++++++----------- src/components/index.ts | 1 + src/pages/settings/PresetSettings.tsx | 36 +-- src/pages/theme-manager/ExpandedView.tsx | 34 +-- 8 files changed, 227 insertions(+), 239 deletions(-) create mode 100644 src/components/Loading.tsx diff --git a/src/backend/apiHelpers/fetchWrappers.ts b/src/backend/apiHelpers/fetchWrappers.ts index b4da010..06d515a 100644 --- a/src/backend/apiHelpers/fetchWrappers.ts +++ b/src/backend/apiHelpers/fetchWrappers.ts @@ -48,41 +48,34 @@ export async function genericApiFetch( } = options; const { apiUrl } = globalState!.getPublicState(); - function doTheFetching(authToken: string | undefined = undefined) { + async function doTheFetching(authToken: string | undefined = undefined) { const headers = createHeadersObj(authToken, request); - - return server! - .fetchNoCors(`${apiUrl}${fetchPath}`, { + try { + const deckyRes = await server!.fetchNoCors(`${apiUrl}${fetchPath}`, { method: "GET", // If a custom method is specified in request it will overwrite ...request, headers: headers, - }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } + }); + if (!deckyRes.success) { throw new Error(`Fetch not successful!`); - }) - .then((res) => { - if (res.status >= 200 && res.status <= 300 && res.body) { - // @ts-ignore - return JSON.parse(res.body || ""); - } + } + const res = deckyRes.result; + if (res.status < 200 || res.status > 300 || !res.body) { throw new Error(`Res not OK!, code ${res.status} - ${res.body}`); - }) - .then((json) => { - if (json) { - return json; - } + } + // @ts-ignore + const json = JSON.parse(res.body || ""); + if (!json) { throw new Error(`No json returned!`); - }) - .catch((err) => { - if (!failSilently) { - console.error(`Error fetching ${fetchPath}`, err); - } - onError(); - }); + } + return json; + } catch (err) { + if (!failSilently) { + console.error(`Error fetching ${fetchPath}`, err); + } + onError(); + } } if (requiresAuth) { if (customAuthToken) { diff --git a/src/backend/apiHelpers/profileUploadingHelpers.ts b/src/backend/apiHelpers/profileUploadingHelpers.ts index cdf9a2d..3ac19db 100644 --- a/src/backend/apiHelpers/profileUploadingHelpers.ts +++ b/src/backend/apiHelpers/profileUploadingHelpers.ts @@ -32,27 +32,35 @@ export async function publishProfile( throw new Error(`No blobId returned!`); } const blobId = deckyRes.result.message.id; - - const json = await genericApiFetch( - `/submissions/css_zip`, - { - method: "POST", - headers: { - "Content-Type": "application/json", + try { + const json = await genericApiFetch( + `/submissions/css_zip`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + blob: blobId, + meta: { + imageBlobs: [], + description: description, + privateSubmission: !isPublic, + }, + }), }, - body: JSON.stringify({ - blob: blobId, - meta: { - imageBlobs: [], - description: description, - privateSubmission: !isPublic, + { + requiresAuth: true, + onError: () => { + throw new Error(`Error Posting Request`); }, - }), - }, - { requiresAuth: true } - ); - if (!json || !json.task) throw new Error(`No task returned`); - return json.task; + } + ); + if (!json || !json.task) throw new Error(`No task returned`); + return json.task; + } catch (error) { + throw new Error(`Failed to submit profile: ${error}`); + } } export async function getTaskStatus(taskId: string): Promise { diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx new file mode 100644 index 0000000..d3f8889 --- /dev/null +++ b/src/components/Loading.tsx @@ -0,0 +1,29 @@ +import { ImSpinner5 } from "react-icons/im"; + +export function Loading() { + return ( +
+ + + Loading +
+ ); +} diff --git a/src/components/Modals/UploadProfileModal.tsx b/src/components/Modals/UploadProfileModal.tsx index f85d068..b327198 100644 --- a/src/components/Modals/UploadProfileModal.tsx +++ b/src/components/Modals/UploadProfileModal.tsx @@ -3,6 +3,7 @@ import * as python from "../../python"; import { ButtonItem, ConfirmModal, + DialogButton, DropdownItem, Focusable, ModalRoot, @@ -53,38 +54,56 @@ function UploadProfileModal({ const [description, setDescription] = useState(""); const [uploadStatus, setUploadStatus] = useState< - "idle" | "submitting" | "taskStatus" | "completed" + "idle" | "submitting" | "taskStatus" | "completed" | "error" >("idle"); const [taskId, setTaskId] = useState(undefined); + const [errorData, setErrorData] = useState(undefined); async function onUpload() { if (!selectedProfile) return; - setUploadStatus("submitting"); - // eventually run the submit here - const taskId = await publishProfile( - localThemeList.find((e) => e.id === selectedProfile)!.name, - isPublic, - description - ); - setUploadStatus("taskStatus"); - setTaskId(taskId); + try { + setUploadStatus("submitting"); + const taskId = await publishProfile( + localThemeList.find((e) => e.id === selectedProfile)!.name, + isPublic, + description + ); + setUploadStatus("taskStatus"); + setTaskId(taskId); + } catch (error) { + if (typeof error === "string") setErrorData(error); + setUploadStatus("error"); + } } - function onTaskFinish(success: boolean) { - setUploadStatus("completed"); - if (success) { - closeModal(); - onUploadFinish(); - } + function onTaskFinish() { + closeModal(); + onUploadFinish(); } - if (uploadStatus === "taskStatus" && taskId) { - return ; + if (uploadStatus !== "idle") { + return ( + + ); } return ( - Upload Profile + + Upload Profile ({ data: e.id, label: e.display_name }))} @@ -99,9 +118,14 @@ function UploadProfileModal({ value={description} onChange={(e) => setDescription(e.target.value)} /> - - Upload - + + + Upload + + + Cancel + + ); } diff --git a/src/components/TaskStatus.tsx b/src/components/TaskStatus.tsx index 7fc0b2f..89f6788 100644 --- a/src/components/TaskStatus.tsx +++ b/src/components/TaskStatus.tsx @@ -1,153 +1,106 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { TaskQueryResponse } from "../apiTypes/SubmissionTypes"; import { getTaskStatus } from "../backend/apiHelpers/profileUploadingHelpers"; -import { BsCheckCircleFill, BsXCircleFill } from "react-icons/bs"; -import { ImSpinner5 } from "react-icons/im"; +import { DialogButton, Focusable } from "decky-frontend-lib"; +import { Loading } from "./Loading"; export function TaskStatus({ - task, + uploadStatus, + setUploadStatus, + errorData, + taskId, onFinish, }: { - task: string; - onFinish: (success: boolean) => void; + errorData: string | undefined; + uploadStatus: "submitting" | "taskStatus" | "completed" | "error"; + setUploadStatus: (status: "submitting" | "taskStatus" | "completed" | "error") => void; + taskId: string | undefined; + onFinish: () => void; }) { const [apiStatus, setStatus] = useState(null); async function getStatus() { - if (task) { - const data = await getTaskStatus(task); + if (taskId) { + const data = await getTaskStatus(taskId); setStatus(data); } } useEffect(() => { - if (apiStatus?.completed === null) { - setTimeout(() => { - getStatus(); - }, 1000); - } - if (apiStatus?.completed) { - onFinish(apiStatus.success); + if (!apiStatus) return; + + if (apiStatus.completed) { + setUploadStatus("completed"); + return; } + + setTimeout(() => { + getStatus(); + }, 1000); }, [apiStatus]); useEffect(() => { getStatus(); - }, [task]); + }, [taskId]); - if (!apiStatus) { - return Loading; + if (["submitting", "taskStatus"].includes(uploadStatus)) { + return ( +
+ +
+ ); } - // This is 100% ripped from deckthemes - // Eventually please do re-do this + if (uploadStatus === "error") { + return ( + + Error Submitting Theme! + {errorData} + + ); + } + + return ; +} + +// This was split out essentially just so the ref works +function TaskStatusFinishedDisplay({ + apiStatus, + onFinish, +}: { + apiStatus: TaskQueryResponse | null; + onFinish: () => void; +}) { + const closeButtonRef = useRef(null); + useEffect(() => { + closeButtonRef?.current && closeButtonRef.current.focus(); + }, [closeButtonRef]); + return ( <> - -
-
- {apiStatus.name} - Task {task?.split("-")[0]} -
- {apiStatus.completed ? ( - <> -
- {apiStatus.success ? ( -
- - Success -
- ) : ( -
- - Failed -
- )} -
- - {apiStatus?.success ? "Completed " : "Failed "}In{" "} - - {(new Date(apiStatus.completed).valueOf() - new Date(apiStatus.started).valueOf()) / - 1000}{" "} - - Seconds - - {!apiStatus.success && {apiStatus.status}} - - ) : ( - <> -
- - Processing -
- {apiStatus.status} - - )} -
+ + + Profile Upload {apiStatus?.success ? "Succeeded" : "Failed"} + + {apiStatus?.status} + + + Close + + + ); } diff --git a/src/components/index.ts b/src/components/index.ts index 1c3c511..c97e1d8 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,3 +6,4 @@ export * from "./ThemeManager"; export * from "./OptionalDepsModal"; export * from "./QAMTab"; export * from "./Modals"; +export * from "./Loading"; diff --git a/src/pages/settings/PresetSettings.tsx b/src/pages/settings/PresetSettings.tsx index f825669..abf8693 100644 --- a/src/pages/settings/PresetSettings.tsx +++ b/src/pages/settings/PresetSettings.tsx @@ -84,11 +84,11 @@ function UploadedProfilesDisplay() { if (!upToDateFullToken) return; let profileArray: PartialCSSThemeInfo[] = []; - const publicProfileData = await genericGET("/users/me/themes?filters=", true); + const publicProfileData = await genericGET("/users/me/themes?filters=Profile", true); if (publicProfileData && publicProfileData.total > 0) { profileArray.push(...publicProfileData.items); } - const privateProfileData = await genericGET("/users/me/themes/private?filters=", true); + const privateProfileData = await genericGET("/users/me/themes/private?filters=Profile", true); if (privateProfileData && privateProfileData.total > 0) { profileArray.push( ...privateProfileData.items.map((e: PartialCSSThemeInfo) => ({ ...e, isPrivate: true })) @@ -154,28 +154,18 @@ function UploadedProfilesDisplay() { {profilesLoaded ? ( <> - {uploadedProfiles.length > 0 && ( + {uploadedProfiles.length > 0 ? ( <> {uploadedProfiles.map((e) => ( - {}} - CustomBubbleIcon={ - e.isPrivate ? null : ( - - ) - } - /> + ))} + ) : ( + You have no uploaded profiles )} ) : ( @@ -185,3 +175,17 @@ function UploadedProfilesDisplay() { ); } + +function UploadedProfileCard({ data }: { data: PartialCSSThemeInfo & { isPrivate?: boolean } }) { + return ( + + ) + } + /> + ); +} diff --git a/src/pages/theme-manager/ExpandedView.tsx b/src/pages/theme-manager/ExpandedView.tsx index 653b9fb..8212afe 100644 --- a/src/pages/theme-manager/ExpandedView.tsx +++ b/src/pages/theme-manager/ExpandedView.tsx @@ -20,6 +20,7 @@ import { AuthorViewModalRoot } from "../../components/Modals/AuthorViewModal"; import { ExpandedViewStyles } from "../../components/Styles"; import { shortenNumber } from "../../logic/numbers"; import { FaRegStar, FaStar } from "react-icons/fa"; +import { Loading } from "../../components"; export const ExpandedViewPage: VFC = () => { const { @@ -139,37 +140,12 @@ export const ExpandedViewPage: VFC = () => { const [focusedImage, setFocusedImage] = useState(0); - if (!loaded) { + if (!loaded) return ( - <> - -
- - Loading -
- +
+ +
); - } // if theres no theme in the detailed view if (fullThemeData) { From 25c56406763322c127ee5ea885fac1a2d46175be Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 12 May 2024 17:04:10 -0600 Subject: [PATCH 08/53] spiff it up a bit yknow --- .../FullscreenCloudProfileEntry.tsx | 87 +++++++ .../ThemeSettings/FullscreenProfileEntry.tsx | 19 +- src/pages/settings/PresetSettings.tsx | 229 ++++++++---------- src/pages/settings/ThemeSettings.tsx | 1 + src/pages/theme-manager/ExpandedView.tsx | 3 +- 5 files changed, 196 insertions(+), 143 deletions(-) create mode 100644 src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx diff --git a/src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx b/src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx new file mode 100644 index 0000000..774a033 --- /dev/null +++ b/src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx @@ -0,0 +1,87 @@ +import { DialogButton, Focusable, PanelSectionRow } from "decky-frontend-lib"; +import { LocalThemeStatus, Theme } from "../../ThemeTypes"; +import { useCssLoaderState } from "../../state"; +import { AiOutlineDownload } from "react-icons/ai"; +import { FaTrash } from "react-icons/fa"; +import { PartialCSSThemeInfo } from "../../apiTypes"; +import { FaCheck, FaCloud } from "react-icons/fa6"; + +export function FullscreenCloudProfileEntry({ + data: e, + handleUninstall, + isInstalling, + handleUpdate, +}: { + data: PartialCSSThemeInfo & { isPrivate?: boolean }; + handleUninstall: (e: Theme | PartialCSSThemeInfo) => void; + handleUpdate: (e: Theme | PartialCSSThemeInfo) => void; + isInstalling: boolean; +}) { + const { updateStatuses } = useCssLoaderState(); + + // If it's null, that means it's cloud-only, not installed + let updateStatus: LocalThemeStatus | null = null; + const themeArrPlace = updateStatuses.find((f) => f[0] === e.id); + if (themeArrPlace) { + updateStatus = themeArrPlace[1]; + } + + const isOutdated = updateStatus === "outdated"; + const isInstalled = updateStatus !== null; + + return ( + +
+ {e.displayName} + {!isInstalled ? : } + + {/* Update Button */} + {(isOutdated || !isInstalled) && ( + handleUpdate(e)} + disabled={isInstalling} + > + + + )} + {/* Only show delete button if it IS installed */} + {isInstalled && ( + handleUninstall(e)} + disabled={isInstalling} + > + + + )} + +
+
+ ); +} diff --git a/src/components/ThemeSettings/FullscreenProfileEntry.tsx b/src/components/ThemeSettings/FullscreenProfileEntry.tsx index 2192945..e7d633a 100644 --- a/src/components/ThemeSettings/FullscreenProfileEntry.tsx +++ b/src/components/ThemeSettings/FullscreenProfileEntry.tsx @@ -1,9 +1,8 @@ import { DialogButton, Focusable, PanelSectionRow } from "decky-frontend-lib"; -import { Flags, LocalThemeStatus, Theme } from "../../ThemeTypes"; +import { LocalThemeStatus, Theme } from "../../ThemeTypes"; import { useCssLoaderState } from "../../state"; import { AiOutlineDownload } from "react-icons/ai"; import { FaTrash } from "react-icons/fa"; -import { installTheme } from "../../api"; export function FullscreenProfileEntry({ data: e, @@ -17,7 +16,7 @@ export function FullscreenProfileEntry({ isInstalling: boolean; }) { const { updateStatuses } = useCssLoaderState(); - let [updateStatus]: [LocalThemeStatus] = ["installed"]; + let updateStatus: LocalThemeStatus = "installed"; const themeArrPlace = updateStatuses.find((f) => f[0] === e.id); if (themeArrPlace) { updateStatus = themeArrPlace[1]; @@ -28,6 +27,7 @@ export function FullscreenProfileEntry({ style={{ display: "flex", padding: "0", + alignItems: "center", }} > {e.display_name} @@ -55,19 +55,6 @@ export function FullscreenProfileEntry({ )} - {/* This shows when a theme is local, but not a preset */} - {updateStatus === "local" && !e.flags.includes(Flags.isPreset) && ( - - Local Theme - - )} (f[0] === e.id ? [e.id, "installed", false] : e)) - ); - setInstalling(false); - } - - async function handleUninstall(listEntry: Theme) { - setInstalling(true); - await python.deleteTheme(listEntry.name); - await python.reloadBackend(); - setInstalling(false); - } - - return ( -
- - - - {localThemeList - .filter((e) => e.flags.includes(Flags.isPreset)) - .map((e) => ( - - ))} - - - - - -
- ); -} - -function UploadedProfilesDisplay() { const { apiFullToken, apiShortToken, apiMeData } = useCssLoaderState(); const [uploadedProfiles, setUploadedProfiles] = useState< @@ -104,88 +55,116 @@ function UploadedProfilesDisplay() { if (apiShortToken) void getUserProfiles(); }, []); + type MergedCloudProfile = { isCloud: true; data: PartialCSSThemeInfo & { isPrivate: boolean } }; + type MergedNormalProfile = { isCloud: false; data: Theme }; + + const mergedProfileList: (MergedCloudProfile | MergedNormalProfile)[] = useMemo(() => { + const filteredLocalProfiles: MergedNormalProfile[] = localThemeList + .filter((e) => e.flags.includes(Flags.isPreset)) + .filter((e) => { + if (uploadedProfiles.some((f) => f.id === e.id)) return false; + return true; + }) + .map((e) => ({ isCloud: false, data: e })); + + const filteredCloudProfiles = uploadedProfiles.map((e) => ({ + isCloud: true, + data: e, + })) as MergedCloudProfile[]; + + return [...filteredLocalProfiles, ...filteredCloudProfiles].sort((a, b) => + a.data.name > b.data.name ? 1 : -1 + ); + }, [uploadedProfiles, localThemeList]); + function onUploadFinish() { void getUserProfiles(); } - if (!apiMeData) { - return ( - <> - {apiShortToken ? ( - <> - Loading - - ) : ( - <> - - You are not logged in. Log In with your DeckThemes account to view your uploaded - profiles. - - - )} - + async function handleUpdate(e: Theme | PartialCSSThemeInfo) { + setInstalling(true); + await installTheme(e.id); + // This just updates the updateStatuses arr to know that this theme now is up to date, no need to re-fetch the API to know that + setGlobalState( + "updateStatuses", + updateStatuses.map((f) => (f[0] === e.id ? [e.id, "installed", false] : f)) + ); + setInstalling(false); + } + + async function handleUninstall(listEntry: Theme | PartialCSSThemeInfo) { + setInstalling(true); + await python.deleteTheme(listEntry.name); + await python.reloadBackend(); + setGlobalState( + "updateStatuses", + updateStatuses.filter((f) => f[0] !== listEntry.id) ); + setInstalling(false); } return ( - <> - - - { - if (apiMeData.premiumTier && apiMeData.premiumTier !== "None") { - showModal(); - return; - } - showModal( - +
+ + + {mergedProfileList.map((e) => { + if (e.isCloud) { + return ( + ); - return; - }} - > - Upload Profile - - {profilesLoaded ? ( + } + return ( + + ); + })} + + + {apiMeData ? ( <> - {uploadedProfiles.length > 0 ? ( - <> - - {uploadedProfiles.map((e) => ( - - ))} - - + { + if (apiMeData.premiumTier && apiMeData.premiumTier !== "None") { + showModal(); + return; + } + showModal( + + ); + return; + }} + > + Upload Profile + + + ) : ( + <> + {apiShortToken ? ( + Loading ) : ( - You have no uploaded profiles + + You are not logged in. Log In with your DeckThemes account to view your uploaded + profiles. + )} - ) : ( - Loading Profiles... )} - - ); -} - -function UploadedProfileCard({ data }: { data: PartialCSSThemeInfo & { isPrivate?: boolean } }) { - return ( - - ) - } - /> +
); } diff --git a/src/pages/settings/ThemeSettings.tsx b/src/pages/settings/ThemeSettings.tsx index 09c8f50..9e95784 100644 --- a/src/pages/settings/ThemeSettings.tsx +++ b/src/pages/settings/ThemeSettings.tsx @@ -34,6 +34,7 @@ export function ThemeSettings() { setInstalling(true); const unpinned = unpinnedThemes.includes(e.id); await installTheme(e.id); + // This just updates the updateStatuses arr to know that this theme now is up to date, no need to re-fetch the API to know that setGlobalState( "updateStatuses", diff --git a/src/pages/theme-manager/ExpandedView.tsx b/src/pages/theme-manager/ExpandedView.tsx index 8212afe..b2170f1 100644 --- a/src/pages/theme-manager/ExpandedView.tsx +++ b/src/pages/theme-manager/ExpandedView.tsx @@ -6,8 +6,7 @@ import { ScrollPanelGroup, } from "decky-frontend-lib"; import { useEffect, useRef, useState, VFC } from "react"; -import { ImCog, ImSpinner5 } from "react-icons/im"; -import { BsStar, BsStarFill } from "react-icons/bs"; +import { ImCog } from "react-icons/im"; import * as python from "../../python"; import { genericGET, refreshToken, toggleStar as apiToggleStar, installTheme } from "../../api"; From eeba16b6073df362a53189aef93104e1294a71cc Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Mon, 20 May 2024 15:20:10 -0600 Subject: [PATCH 09/53] re-order the profiles list --- src/pages/settings/PresetSettings.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/PresetSettings.tsx b/src/pages/settings/PresetSettings.tsx index ffd0ae9..15b6bed 100644 --- a/src/pages/settings/PresetSettings.tsx +++ b/src/pages/settings/PresetSettings.tsx @@ -72,9 +72,10 @@ export function PresetSettings() { data: e, })) as MergedCloudProfile[]; - return [...filteredLocalProfiles, ...filteredCloudProfiles].sort((a, b) => - a.data.name > b.data.name ? 1 : -1 - ); + return [ + ...filteredLocalProfiles.sort((a, b) => (a.data.name > b.data.name ? 1 : -1)), + ...filteredCloudProfiles.sort((a, b) => (a.data.name > b.data.name ? 1 : -1)), + ]; }, [uploadedProfiles, localThemeList]); function onUploadFinish() { From 59fd035900f9671256a9b1bd03a04f5b9cd5c056 Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 29 Jun 2024 16:22:52 -0600 Subject: [PATCH 10/53] first commit --- main.py | 11 +- package.json | 26 +- plugin.json | 1 + pnpm-lock.yaml | 3472 +++++++++++------ rollup.config.js | 51 +- src/api.ts | 260 -- .../decky-backend-repository-impl.ts | 37 + src/backend-impl/decky-backend-service.ts | 4 + src/backend-impl/decky-theme-store.ts | 31 + src/backend-impl/index.ts | 2 + src/backend-impl/readme.md | 6 + src/backend/apiHelpers/fetchWrappers.ts | 95 - src/backend/apiHelpers/index.ts | 2 - .../apiHelpers/profileUploadingHelpers.ts | 68 - src/backend/backendHelpers/index.ts | 0 src/backend/backendHelpers/toggleTheme.tsx | 78 - src/backend/errors/call-error.ts | 20 + src/backend/errors/fetch-error.ts | 20 + src/backend/errors/index.ts | 2 + src/backend/index.ts | 4 + .../pythonMethods/pluginSettingsMethods.ts | 56 - src/backend/pythonMethods/storeUtils.ts | 42 - src/backend/pythonRoot.ts | 4 - .../repositories/backend-repository.ts | 5 + src/backend/repositories/index.ts | 1 + src/backend/services/backend-service.ts | 91 + src/backend/services/index.ts | 1 + src/backend/state/index.ts | 1 + src/backend/state/theme-store.ts | 418 ++ src/components/DepsOptionSelector.tsx | 30 - src/components/Loading.tsx | 29 - src/components/Modals/AuthorViewModal.tsx | 152 - src/components/Modals/CreatePresetModal.tsx | 54 - .../Modals/DeleteConfirmationModal.tsx | 45 - src/components/Modals/PremiumFeatureModal.tsx | 15 - .../ThemeSettingsModal/ThemeSettingsModal.tsx | 166 - .../ThemeSettingsModalButtons.tsx | 139 - .../Modals/ThemeSettingsModal/index.ts | 1 - src/components/Modals/UploadProfileModal.tsx | 131 - src/components/Modals/index.ts | 3 - src/components/OptionalDepsModal.tsx | 51 - src/components/PatchComponent.tsx | 130 - .../QAMTab/PresetSelectionDropdown.tsx | 70 - src/components/QAMTab/QAMThemeToggleList.tsx | 69 - src/components/QAMTab/index.ts | 3 - src/components/Styles/ExpandedViewStyles.tsx | 214 - .../Styles/ThemeBrowserCardStyles.tsx | 132 - src/components/Styles/index.ts | 2 - src/components/SupporterIcon.tsx | 47 - src/components/TaskStatus.tsx | 106 - src/components/ThemeErrorCard.tsx | 30 - .../ThemeManager/BrowserItemCard.tsx | 120 - .../ThemeManager/BrowserSearchFields.tsx | 218 -- .../FilterDropdownCustomLabel.tsx | 20 - .../ThemeManager/LoadMoreButton.tsx | 65 - src/components/ThemeManager/index.ts | 4 - src/components/ThemePatch.tsx | 151 - src/components/ThemeSettings/DeleteMenu.tsx | 54 - .../FullscreenCloudProfileEntry.tsx | 87 - .../ThemeSettings/FullscreenProfileEntry.tsx | 73 - .../FullscreenSingleThemeEntry.tsx | 114 - .../ThemeSettings/UpdateAllThemesButton.tsx | 31 - src/components/ThemeToggle.tsx | 146 - src/components/TitleView.tsx | 67 - src/components/index.ts | 9 - src/decky-patches/decky-patch-store.ts | 81 + src/decky-patches/index.ts | 1 + src/decky-patches/nav-patch/index.ts | 1 + src/decky-patches/nav-patch/nav-patch.ts | 30 + .../unminify-mode/class-hash-map.ts} | 2 +- src/decky-patches/unminify-mode/index.ts | 1 + .../steam-tab-elements-finder.ts} | 2 +- .../unminify-mode/unminify-mode.ts} | 4 +- src/deckyPatches/NavControllerFinder.tsx | 18 - src/deckyPatches/NavPatch.tsx | 47 - src/deckyPatches/NavPatchInfoModal.tsx | 31 - src/hooks/index.ts | 1 - src/index.tsx | 212 +- src/lib/components/index.ts | 7 + .../components/motd-display}/MOTDDisplay.tsx | 68 +- src/lib/components/motd-display/index.ts | 1 + .../NavPatchInfoModal.tsx | 24 + .../components/nav-patch-info-modal/index.ts | 1 + .../optional-deps-modal/OptionalDepsModal.tsx | 45 + .../components/optional-deps-modal/index.ts | 1 + .../PresetSelectionDropdown.tsx | 60 + .../preset-selection-dropdown/index.ts | 1 + .../style-provider/StyleProvider.tsx | 10 + src/lib/components/style-provider/index.ts | 1 + src/lib/components/theme-patch/ThemePatch.tsx | 103 + .../theme-patch/ThemePatchComponent.tsx | 93 + src/lib/components/theme-patch/index.ts | 1 + src/lib/components/title-view/TitleView.tsx | 39 + src/lib/components/title-view/index.ts | 1 + src/lib/hooks/index.ts | 1 + .../hooks/useForcedRerender.ts} | 2 +- src/lib/index.ts | 3 + src/lib/primitives/ConfirmModal.tsx | 31 + src/lib/primitives/Modal.tsx | 23 + src/lib/primitives/index.ts | 2 + src/lib/utils/classname-merger.ts | 6 + src/lib/utils/index.ts | 2 + src/lib/utils/toggleThemeWithModals.tsx | 27 + src/logic/bulkThemeUpdateCheck.ts | 41 - src/logic/calcButtonColor.ts | 12 - src/logic/generateParamStr.ts | 16 - src/logic/index.ts | 2 - src/logic/numbers.ts | 47 - .../components/QamDummyFunctionBoundary.tsx | 21 + .../components/QamHiddenThemesDisplay.tsx | 19 + .../components/QamRefreshButton.tsx | 18 + .../qam-tab-page/components/QamThemeList.tsx | 25 + .../components/QamThemeToggle.tsx | 92 + src/modules/qam-tab-page/components/index.ts | 5 + src/modules/qam-tab-page/index.ts | 1 + src/modules/qam-tab-page/pages/QamTabPage.tsx | 27 + src/modules/qam-tab-page/pages/index.ts | 1 + .../components/BrowserSearchFields.tsx | 106 + .../components/FilterOptionLabel.tsx | 14 + .../components/ThemeBrowserPage.tsx | 11 + .../theme-store/components/ThemeCard.tsx | 15 + .../components/ThemeGridDisplay.tsx | 13 + .../context/ThemeBrowserSharedStore.tsx | 33 + .../theme-store/context/ThemeBrowserStore.tsx | 142 + src/modules/theme-store/context/index.ts | 2 + src/pages/settings/Credits.tsx | 40 - src/pages/settings/DonatePage.tsx | 164 - src/pages/settings/PluginSettings.tsx | 125 - src/pages/settings/PresetSettings.tsx | 171 - src/pages/settings/SettingsPageRouter.tsx | 106 - src/pages/settings/ThemeSettings.tsx | 126 - src/pages/theme-manager/ExpandedView.tsx | 398 -- src/pages/theme-manager/LogInPage.tsx | 96 - src/pages/theme-manager/StarredThemesPage.tsx | 113 - .../theme-manager/SubmissionBrowserPage.tsx | 113 - src/pages/theme-manager/ThemeBrowserPage.tsx | 125 - .../theme-manager/ThemeManagerRouter.tsx | 58 - src/pages/theme-manager/index.ts | 6 - src/python.ts | 274 -- src/state/CssLoaderState.tsx | 260 -- src/state/index.ts | 1 - src/styles/index.ts | 1 + src/styles/styles.css | 195 + src/styles/stylesAsString.ts | 195 + src/styles/themeSettingsModalStyles.css | 70 - src/{apiTypes => types}/AccountData.ts | 2 +- src/{apiTypes => types}/BlobTypes.ts | 0 src/{apiTypes => types}/Motd.ts | 0 src/{apiTypes => types}/SubmissionTypes.ts | 0 .../ThemeQueryTypes.ts} | 0 src/{ => types}/ThemeTypes.ts | 2 +- src/{apiTypes => types}/index.ts | 3 +- tsconfig.json | 11 +- 153 files changed, 4562 insertions(+), 7327 deletions(-) delete mode 100644 src/api.ts create mode 100644 src/backend-impl/decky-backend-repository-impl.ts create mode 100644 src/backend-impl/decky-backend-service.ts create mode 100644 src/backend-impl/decky-theme-store.ts create mode 100644 src/backend-impl/index.ts create mode 100644 src/backend-impl/readme.md delete mode 100644 src/backend/apiHelpers/fetchWrappers.ts delete mode 100644 src/backend/apiHelpers/index.ts delete mode 100644 src/backend/apiHelpers/profileUploadingHelpers.ts delete mode 100644 src/backend/backendHelpers/index.ts delete mode 100644 src/backend/backendHelpers/toggleTheme.tsx create mode 100644 src/backend/errors/call-error.ts create mode 100644 src/backend/errors/fetch-error.ts create mode 100644 src/backend/errors/index.ts create mode 100644 src/backend/index.ts delete mode 100644 src/backend/pythonMethods/pluginSettingsMethods.ts delete mode 100644 src/backend/pythonMethods/storeUtils.ts delete mode 100644 src/backend/pythonRoot.ts create mode 100644 src/backend/repositories/backend-repository.ts create mode 100644 src/backend/repositories/index.ts create mode 100644 src/backend/services/backend-service.ts create mode 100644 src/backend/services/index.ts create mode 100644 src/backend/state/index.ts create mode 100644 src/backend/state/theme-store.ts delete mode 100644 src/components/DepsOptionSelector.tsx delete mode 100644 src/components/Loading.tsx delete mode 100644 src/components/Modals/AuthorViewModal.tsx delete mode 100644 src/components/Modals/CreatePresetModal.tsx delete mode 100644 src/components/Modals/DeleteConfirmationModal.tsx delete mode 100644 src/components/Modals/PremiumFeatureModal.tsx delete mode 100644 src/components/Modals/ThemeSettingsModal/ThemeSettingsModal.tsx delete mode 100644 src/components/Modals/ThemeSettingsModal/ThemeSettingsModalButtons.tsx delete mode 100644 src/components/Modals/ThemeSettingsModal/index.ts delete mode 100644 src/components/Modals/UploadProfileModal.tsx delete mode 100644 src/components/Modals/index.ts delete mode 100644 src/components/OptionalDepsModal.tsx delete mode 100644 src/components/PatchComponent.tsx delete mode 100644 src/components/QAMTab/PresetSelectionDropdown.tsx delete mode 100644 src/components/QAMTab/QAMThemeToggleList.tsx delete mode 100644 src/components/QAMTab/index.ts delete mode 100644 src/components/Styles/ExpandedViewStyles.tsx delete mode 100644 src/components/Styles/ThemeBrowserCardStyles.tsx delete mode 100644 src/components/Styles/index.ts delete mode 100644 src/components/SupporterIcon.tsx delete mode 100644 src/components/TaskStatus.tsx delete mode 100644 src/components/ThemeErrorCard.tsx delete mode 100644 src/components/ThemeManager/BrowserItemCard.tsx delete mode 100644 src/components/ThemeManager/BrowserSearchFields.tsx delete mode 100644 src/components/ThemeManager/FilterDropdownCustomLabel.tsx delete mode 100644 src/components/ThemeManager/LoadMoreButton.tsx delete mode 100644 src/components/ThemeManager/index.ts delete mode 100644 src/components/ThemePatch.tsx delete mode 100644 src/components/ThemeSettings/DeleteMenu.tsx delete mode 100644 src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx delete mode 100644 src/components/ThemeSettings/FullscreenProfileEntry.tsx delete mode 100644 src/components/ThemeSettings/FullscreenSingleThemeEntry.tsx delete mode 100644 src/components/ThemeSettings/UpdateAllThemesButton.tsx delete mode 100644 src/components/ThemeToggle.tsx delete mode 100644 src/components/TitleView.tsx delete mode 100644 src/components/index.ts create mode 100644 src/decky-patches/decky-patch-store.ts create mode 100644 src/decky-patches/index.ts create mode 100644 src/decky-patches/nav-patch/index.ts create mode 100644 src/decky-patches/nav-patch/nav-patch.ts rename src/{deckyPatches/ClassHashMap.tsx => decky-patches/unminify-mode/class-hash-map.ts} (96%) create mode 100644 src/decky-patches/unminify-mode/index.ts rename src/{deckyPatches/SteamTabElementsFinder.tsx => decky-patches/unminify-mode/steam-tab-elements-finder.ts} (89%) rename src/{deckyPatches/UnminifyMode.tsx => decky-patches/unminify-mode/unminify-mode.ts} (93%) delete mode 100644 src/deckyPatches/NavControllerFinder.tsx delete mode 100644 src/deckyPatches/NavPatch.tsx delete mode 100644 src/deckyPatches/NavPatchInfoModal.tsx delete mode 100644 src/hooks/index.ts create mode 100644 src/lib/components/index.ts rename src/{components/QAMTab => lib/components/motd-display}/MOTDDisplay.tsx (58%) create mode 100644 src/lib/components/motd-display/index.ts create mode 100644 src/lib/components/nav-patch-info-modal/NavPatchInfoModal.tsx create mode 100644 src/lib/components/nav-patch-info-modal/index.ts create mode 100644 src/lib/components/optional-deps-modal/OptionalDepsModal.tsx create mode 100644 src/lib/components/optional-deps-modal/index.ts create mode 100644 src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx create mode 100644 src/lib/components/preset-selection-dropdown/index.ts create mode 100644 src/lib/components/style-provider/StyleProvider.tsx create mode 100644 src/lib/components/style-provider/index.ts create mode 100644 src/lib/components/theme-patch/ThemePatch.tsx create mode 100644 src/lib/components/theme-patch/ThemePatchComponent.tsx create mode 100644 src/lib/components/theme-patch/index.ts create mode 100644 src/lib/components/title-view/TitleView.tsx create mode 100644 src/lib/components/title-view/index.ts create mode 100644 src/lib/hooks/index.ts rename src/{hooks/useRerender.ts => lib/hooks/useForcedRerender.ts} (88%) create mode 100644 src/lib/index.ts create mode 100644 src/lib/primitives/ConfirmModal.tsx create mode 100644 src/lib/primitives/Modal.tsx create mode 100644 src/lib/primitives/index.ts create mode 100644 src/lib/utils/classname-merger.ts create mode 100644 src/lib/utils/index.ts create mode 100644 src/lib/utils/toggleThemeWithModals.tsx delete mode 100644 src/logic/bulkThemeUpdateCheck.ts delete mode 100644 src/logic/calcButtonColor.ts delete mode 100644 src/logic/generateParamStr.ts delete mode 100644 src/logic/index.ts delete mode 100644 src/logic/numbers.ts create mode 100644 src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx create mode 100644 src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx create mode 100644 src/modules/qam-tab-page/components/QamRefreshButton.tsx create mode 100644 src/modules/qam-tab-page/components/QamThemeList.tsx create mode 100644 src/modules/qam-tab-page/components/QamThemeToggle.tsx create mode 100644 src/modules/qam-tab-page/components/index.ts create mode 100644 src/modules/qam-tab-page/index.ts create mode 100644 src/modules/qam-tab-page/pages/QamTabPage.tsx create mode 100644 src/modules/qam-tab-page/pages/index.ts create mode 100644 src/modules/theme-store/components/BrowserSearchFields.tsx create mode 100644 src/modules/theme-store/components/FilterOptionLabel.tsx create mode 100644 src/modules/theme-store/components/ThemeBrowserPage.tsx create mode 100644 src/modules/theme-store/components/ThemeCard.tsx create mode 100644 src/modules/theme-store/components/ThemeGridDisplay.tsx create mode 100644 src/modules/theme-store/context/ThemeBrowserSharedStore.tsx create mode 100644 src/modules/theme-store/context/ThemeBrowserStore.tsx create mode 100644 src/modules/theme-store/context/index.ts delete mode 100644 src/pages/settings/Credits.tsx delete mode 100644 src/pages/settings/DonatePage.tsx delete mode 100644 src/pages/settings/PluginSettings.tsx delete mode 100644 src/pages/settings/PresetSettings.tsx delete mode 100644 src/pages/settings/SettingsPageRouter.tsx delete mode 100644 src/pages/settings/ThemeSettings.tsx delete mode 100644 src/pages/theme-manager/ExpandedView.tsx delete mode 100644 src/pages/theme-manager/LogInPage.tsx delete mode 100644 src/pages/theme-manager/StarredThemesPage.tsx delete mode 100644 src/pages/theme-manager/SubmissionBrowserPage.tsx delete mode 100644 src/pages/theme-manager/ThemeBrowserPage.tsx delete mode 100644 src/pages/theme-manager/ThemeManagerRouter.tsx delete mode 100644 src/pages/theme-manager/index.ts delete mode 100644 src/python.ts delete mode 100644 src/state/CssLoaderState.tsx delete mode 100644 src/state/index.ts create mode 100644 src/styles/index.ts create mode 100644 src/styles/styles.css create mode 100644 src/styles/stylesAsString.ts delete mode 100644 src/styles/themeSettingsModalStyles.css rename src/{apiTypes => types}/AccountData.ts (93%) rename src/{apiTypes => types}/BlobTypes.ts (100%) rename src/{apiTypes => types}/Motd.ts (100%) rename src/{apiTypes => types}/SubmissionTypes.ts (100%) rename src/{apiTypes/CSSThemeTypes.ts => types/ThemeQueryTypes.ts} (100%) rename src/{ => types}/ThemeTypes.ts (94%) rename src/{apiTypes => types}/index.ts (64%) diff --git a/main.py b/main.py index dec1d1c..78a7344 100644 --- a/main.py +++ b/main.py @@ -19,7 +19,7 @@ try: if not store_or_file_config("no_redirect_logs"): - import decky_plugin + import decky except: pass @@ -219,16 +219,16 @@ async def _main(self): await self.loader.load(False) if (store_or_file_config("watch")): - await self.toggle_watch_state(self) + await self.toggle_watch_state() else: Log("Not observing themes folder for file changes") Log(f"Initialized css loader. Found {len(self.loader.themes)} themes. Total {len(ALL_INJECTS)} injects, {len([x for x in ALL_INJECTS if x.enabled])} injected") if (ALWAYS_RUN_SERVER or store_or_file_config("server")): - await self.enable_server(self) + await self.enable_server() - await self._fetch_class_mappings(self, True) + await self._fetch_class_mappings(True) await initialize() if __name__ == '__main__': @@ -254,7 +254,8 @@ async def run(self): count = 0 while count < 5: try: - task = asyncio.create_task(Plugin._main(Plugin)) + instance = Plugin() + task = asyncio.create_task(instance._main()) await asyncio.shield(task) except asyncio.CancelledError as e: print(str(e)) diff --git a/package.json b/package.json index 270bd5d..ded1ab2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "SDH-CssLoader", + "type": "module", "version": "2.1.1", "description": "A css loader", "scripts": { @@ -18,23 +19,26 @@ "steam-deck", "deck" ], - "author": "Jonas Dellinger ", + "author": "Deckthemes", "license": "GPL-2.0-or-later", "bugs": { "url": "https://github.com/SteamDeckHomebrew/decky-plugin-template/issues" }, "homepage": "https://github.com/SteamDeckHomebrew/decky-plugin-template#readme", "devDependencies": { - "@rollup/plugin-commonjs": "^21.1.0", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^13.2.1", - "@rollup/plugin-replace": "^4.0.0", - "@rollup/plugin-typescript": "^8.3.2", + "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.7", + "@rollup/plugin-typescript": "^11.1.6", "@types/color": "^3.0.3", "@types/lodash": "^4.14.191", "@types/react": "16.14.0", "@types/webpack": "^5.28.0", - "rollup": "^2.70.2", + "rollup": "^4.18.0", + "rollup-plugin-delete": "^2.0.0", + "rollup-plugin-external-globals": "^0.10.0", "rollup-plugin-import-assets": "^1.1.1", "rollup-plugin-styles": "^4.0.0", "shx": "^0.3.4", @@ -42,10 +46,14 @@ "typescript": "^4.6.4" }, "dependencies": { + "@decky/api": "^1.0.3", + "@decky/ui": "^4.0.1", + "clsx": "^2.1.1", "color": "^4.2.3", - "decky-frontend-lib": "^3.25.0", "lodash": "^4.17.21", - "react-icons": "^4.12.0" + "react-icons": "^4.12.0", + "tailwind-merge": "^2.3.0", + "zustand": "^4.5.2" }, "pnpm": { "peerDependencyRules": { diff --git a/plugin.json b/plugin.json index 71f3629..9fb71d0 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,7 @@ { "name": "CSS Loader", "author": "DeckThemes", + "api_version": 1, "flags": [], "publish": { "tags": ["style"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f109e78..cfc768a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,1736 +1,1399 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - color: - specifier: ^4.2.3 - version: 4.2.3 - decky-frontend-lib: - specifier: ^3.25.0 - version: 3.25.0 - lodash: - specifier: ^4.17.21 - version: 4.17.21 - react-icons: - specifier: ^4.12.0 - version: 4.12.0 - -devDependencies: - '@rollup/plugin-commonjs': - specifier: ^21.1.0 - version: 21.1.0(rollup@2.75.6) - '@rollup/plugin-json': - specifier: ^4.1.0 - version: 4.1.0(rollup@2.75.6) - '@rollup/plugin-node-resolve': - specifier: ^13.2.1 - version: 13.3.0(rollup@2.75.6) - '@rollup/plugin-replace': - specifier: ^4.0.0 - version: 4.0.0(rollup@2.75.6) - '@rollup/plugin-typescript': - specifier: ^8.3.2 - version: 8.3.3(rollup@2.75.6)(tslib@2.4.0)(typescript@4.7.3) - '@types/color': - specifier: ^3.0.3 - version: 3.0.6 - '@types/lodash': - specifier: ^4.14.191 - version: 4.14.201 - '@types/react': - specifier: 16.14.0 - version: 16.14.0 - '@types/webpack': - specifier: ^5.28.0 - version: 5.28.0 - rollup: - specifier: ^2.70.2 - version: 2.75.6 - rollup-plugin-import-assets: - specifier: ^1.1.1 - version: 1.1.1(rollup@2.75.6) - rollup-plugin-styles: - specifier: ^4.0.0 - version: 4.0.0(rollup@2.75.6) - shx: - specifier: ^0.3.4 - version: 0.3.4 - tslib: - specifier: ^2.4.0 - version: 2.4.0 - typescript: - specifier: ^4.6.4 - version: 4.7.3 +importers: + + .: + dependencies: + '@decky/api': + specifier: ^1.0.3 + version: 1.0.3 + '@decky/ui': + specifier: ^4.0.1 + version: 4.0.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + color: + specifier: ^4.2.3 + version: 4.2.3 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + react-icons: + specifier: ^4.12.0 + version: 4.12.0(react@18.3.1) + rollup-plugin-delete: + specifier: ^2.0.0 + version: 2.0.0 + rollup-plugin-external-globals: + specifier: ^0.10.0 + version: 0.10.0(rollup@4.18.0) + tailwind-merge: + specifier: ^2.3.0 + version: 2.3.0 + zustand: + specifier: ^4.5.2 + version: 4.5.2(@types/react@16.14.0)(react@18.3.1) + devDependencies: + '@rollup/plugin-alias': + specifier: ^5.1.0 + version: 5.1.0(rollup@4.18.0) + '@rollup/plugin-commonjs': + specifier: ^26.0.1 + version: 26.0.1(rollup@4.18.0) + '@rollup/plugin-json': + specifier: ^6.1.0 + version: 6.1.0(rollup@4.18.0) + '@rollup/plugin-node-resolve': + specifier: ^15.2.3 + version: 15.2.3(rollup@4.18.0) + '@rollup/plugin-replace': + specifier: ^5.0.7 + version: 5.0.7(rollup@4.18.0) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@5.4.5) + '@types/color': + specifier: ^3.0.3 + version: 3.0.6 + '@types/lodash': + specifier: ^4.14.191 + version: 4.17.4 + '@types/react': + specifier: 16.14.0 + version: 16.14.0 + '@types/webpack': + specifier: ^5.28.0 + version: 5.28.5 + rollup: + specifier: ^4.18.0 + version: 4.18.0 + rollup-plugin-import-assets: + specifier: ^1.1.1 + version: 1.1.1(rollup@4.18.0) + rollup-plugin-styles: + specifier: ^4.0.0 + version: 4.0.0(rollup@4.18.0) + shx: + specifier: ^0.3.4 + version: 0.3.4 + tslib: + specifier: ^2.4.0 + version: 2.6.2 + typescript: + specifier: ^5.4.5 + version: 5.4.5 packages: - /@babel/code-frame@7.22.13: - resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} + '@babel/code-frame@7.24.2': + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.22.20 - chalk: 2.4.2 - dev: true - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + '@babel/helper-validator-identifier@7.24.5': + resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} engines: {node: '>=6.9.0'} - dev: true - /@babel/highlight@7.22.20: - resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} + '@babel/highlight@7.24.5': + resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.24.5': + resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.20 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - /@jridgewell/gen-mapping@0.3.1: - resolution: {integrity: sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==} + '@decky/api@1.0.3': + resolution: {integrity: sha512-7hMKEHWcyz/bttx7DcKXqsOXcrtmC4CB6UwxRVrtlb/aolQtv1NVKHIEkIM6ND5hqTUU/VJ2HPUmCOwKm3Of0Q==} + + '@decky/ui@4.0.1': + resolution: {integrity: sha512-BCT10sirZtG6eK2FSwffySM9fAUzwKMi1nsXJP7x+BTQ/UqEudy4IqxSe0AV7qaG5PzmiHuQZWoRwaaqIbUdeg==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.1 - '@jridgewell/sourcemap-codec': 1.4.13 - '@jridgewell/trace-mapping': 0.3.13 - dev: true - /@jridgewell/resolve-uri@3.0.7: - resolution: {integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==} + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/set-array@1.1.1: - resolution: {integrity: sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/source-map@0.3.2: - resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} - dependencies: - '@jridgewell/gen-mapping': 0.3.1 - '@jridgewell/trace-mapping': 0.3.13 - dev: true + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - /@jridgewell/sourcemap-codec@1.4.13: - resolution: {integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==} - dev: true + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - /@jridgewell/trace-mapping@0.3.13: - resolution: {integrity: sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==} - dependencies: - '@jridgewell/resolve-uri': 3.0.7 - '@jridgewell/sourcemap-codec': 1.4.13 - dev: true + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - /@rollup/plugin-commonjs@21.1.0(rollup@2.75.6): - resolution: {integrity: sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA==} - engines: {node: '>= 8.0.0'} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/plugin-alias@5.1.0': + resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.38.3 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.75.6) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 7.2.3 - is-reference: 1.2.1 - magic-string: 0.25.9 - resolve: 1.22.0 - rollup: 2.75.6 - dev: true + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - /@rollup/plugin-json@4.1.0(rollup@2.75.6): - resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==} + '@rollup/plugin-commonjs@26.0.1': + resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: - rollup: ^1.20.0 || ^2.0.0 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.75.6) - rollup: 2.75.6 - dev: true + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - /@rollup/plugin-node-resolve@13.3.0(rollup@2.75.6): - resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==} - engines: {node: '>= 10.0.0'} + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.42.0 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.75.6) - '@types/resolve': 1.17.1 - deepmerge: 4.2.2 - is-builtin-module: 3.1.0 - is-module: 1.0.0 - resolve: 1.22.0 - rollup: 2.75.6 - dev: true + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - /@rollup/plugin-replace@4.0.0(rollup@2.75.6): - resolution: {integrity: sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==} + '@rollup/plugin-node-resolve@15.2.3': + resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0 || ^2.0.0 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.75.6) - magic-string: 0.25.9 - rollup: 2.75.6 - dev: true + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - /@rollup/plugin-typescript@8.3.3(rollup@2.75.6)(tslib@2.4.0)(typescript@4.7.3): - resolution: {integrity: sha512-55L9SyiYu3r/JtqdjhwcwaECXP7JeJ9h1Sg1VWRJKIutla2MdZQodTgcCNybXLMCnqpNLEhS2vGENww98L1npg==} - engines: {node: '>=8.0.0'} + '@rollup/plugin-replace@5.0.7': + resolution: {integrity: sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-typescript@11.1.6': + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.14.0 + rollup: ^2.14.0||^3.0.0||^4.0.0 tslib: '*' typescript: '>=3.7.0' peerDependenciesMeta: + rollup: + optional: true tslib: optional: true - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.75.6) - resolve: 1.22.0 - rollup: 2.75.6 - tslib: 2.4.0 - typescript: 4.7.3 - dev: true - /@rollup/pluginutils@3.1.0(rollup@2.75.6): - resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} + + '@rollup/pluginutils@5.1.0': + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0 - dependencies: - '@types/estree': 0.0.39 - estree-walker: 1.0.1 - picomatch: 2.3.1 - rollup: 2.75.6 - dev: true + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true + '@rollup/rollup-android-arm-eabi@4.18.0': + resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + cpu: [arm] + os: [android] - /@trysound/sax@0.2.0: + '@rollup/rollup-android-arm64@4.18.0': + resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.18.0': + resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.18.0': + resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.18.0': + resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.18.0': + resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.18.0': + resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.18.0': + resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.18.0': + resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.18.0': + resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.18.0': + resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.18.0': + resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + cpu: [x64] + os: [win32] + + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} - dev: true - /@types/color-convert@2.0.3: + '@types/color-convert@2.0.3': resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==} - dependencies: - '@types/color-name': 1.1.3 - dev: true - /@types/color-name@1.1.3: - resolution: {integrity: sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw==} - dev: true + '@types/color-name@1.1.4': + resolution: {integrity: sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==} - /@types/color@3.0.6: + '@types/color@3.0.6': resolution: {integrity: sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==} - dependencies: - '@types/color-convert': 2.0.3 - dev: true - /@types/cssnano@5.1.0(postcss@8.4.31): + '@types/cssnano@5.1.0': resolution: {integrity: sha512-ikR+18UpFGgvaWSur4og6SJYF/6QEYHXvrIt36dp81p1MG3cAPTYDMBJGeyWa3LCnqEbgNMHKRb+FP0NrXtoWQ==} deprecated: This is a stub types definition. cssnano provides its own type definitions, so you do not need this installed. - dependencies: - cssnano: 5.1.15(postcss@8.4.31) - transitivePeerDependencies: - - postcss - dev: true - /@types/eslint-scope@3.7.3: - resolution: {integrity: sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==} - dependencies: - '@types/eslint': 8.4.3 - '@types/estree': 0.0.51 - dev: true + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - /@types/eslint@8.4.3: - resolution: {integrity: sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==} - dependencies: - '@types/estree': 0.0.51 - '@types/json-schema': 7.0.11 - dev: true + '@types/eslint@8.56.10': + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - /@types/estree@0.0.39: - resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} - dev: true + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} - /@types/estree@0.0.51: - resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} - dev: true + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - /@types/json-schema@7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} - dev: true + '@types/lodash@4.17.4': + resolution: {integrity: sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==} - /@types/lodash@4.14.201: - resolution: {integrity: sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==} - dev: true + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - /@types/node@17.0.42: - resolution: {integrity: sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==} - dev: true + '@types/node@20.12.12': + resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} - /@types/parse-json@4.0.2: + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - dev: true - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - dev: true + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - /@types/react@16.14.0: + '@types/react@16.14.0': resolution: {integrity: sha512-jJjHo1uOe+NENRIBvF46tJimUvPnmbQ41Ax0pEm7pRvhPg+wuj8VMOHHiMvaGmZRzRrCtm7KnL5OOE/6kHPK8w==} - dependencies: - '@types/prop-types': 15.7.5 - csstype: 3.1.0 - dev: true - /@types/resolve@1.17.1: - resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} - dependencies: - '@types/node': 17.0.42 - dev: true + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - /@types/webpack@5.28.0: - resolution: {integrity: sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==} - dependencies: - '@types/node': 17.0.42 - tapable: 2.2.1 - webpack: 5.73.0 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - - webpack-cli - dev: true + '@types/webpack@5.28.5': + resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} - /@webassemblyjs/ast@1.11.1: - resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} - dependencies: - '@webassemblyjs/helper-numbers': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - dev: true + '@webassemblyjs/ast@1.12.1': + resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} - /@webassemblyjs/floating-point-hex-parser@1.11.1: - resolution: {integrity: sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==} - dev: true + '@webassemblyjs/floating-point-hex-parser@1.11.6': + resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} - /@webassemblyjs/helper-api-error@1.11.1: - resolution: {integrity: sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==} - dev: true + '@webassemblyjs/helper-api-error@1.11.6': + resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} - /@webassemblyjs/helper-buffer@1.11.1: - resolution: {integrity: sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==} - dev: true + '@webassemblyjs/helper-buffer@1.12.1': + resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} - /@webassemblyjs/helper-numbers@1.11.1: - resolution: {integrity: sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==} - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.11.1 - '@webassemblyjs/helper-api-error': 1.11.1 - '@xtuc/long': 4.2.2 - dev: true + '@webassemblyjs/helper-numbers@1.11.6': + resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} - /@webassemblyjs/helper-wasm-bytecode@1.11.1: - resolution: {integrity: sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==} - dev: true + '@webassemblyjs/helper-wasm-bytecode@1.11.6': + resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} - /@webassemblyjs/helper-wasm-section@1.11.1: - resolution: {integrity: sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==} - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-buffer': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/wasm-gen': 1.11.1 - dev: true + '@webassemblyjs/helper-wasm-section@1.12.1': + resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} - /@webassemblyjs/ieee754@1.11.1: - resolution: {integrity: sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==} - dependencies: - '@xtuc/ieee754': 1.2.0 - dev: true + '@webassemblyjs/ieee754@1.11.6': + resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} - /@webassemblyjs/leb128@1.11.1: - resolution: {integrity: sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==} - dependencies: - '@xtuc/long': 4.2.2 - dev: true - - /@webassemblyjs/utf8@1.11.1: - resolution: {integrity: sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==} - dev: true - - /@webassemblyjs/wasm-edit@1.11.1: - resolution: {integrity: sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==} - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-buffer': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/helper-wasm-section': 1.11.1 - '@webassemblyjs/wasm-gen': 1.11.1 - '@webassemblyjs/wasm-opt': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 - '@webassemblyjs/wast-printer': 1.11.1 - dev: true - - /@webassemblyjs/wasm-gen@1.11.1: - resolution: {integrity: sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==} - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/ieee754': 1.11.1 - '@webassemblyjs/leb128': 1.11.1 - '@webassemblyjs/utf8': 1.11.1 - dev: true - - /@webassemblyjs/wasm-opt@1.11.1: - resolution: {integrity: sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==} - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-buffer': 1.11.1 - '@webassemblyjs/wasm-gen': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 - dev: true - - /@webassemblyjs/wasm-parser@1.11.1: - resolution: {integrity: sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==} - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-api-error': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/ieee754': 1.11.1 - '@webassemblyjs/leb128': 1.11.1 - '@webassemblyjs/utf8': 1.11.1 - dev: true - - /@webassemblyjs/wast-printer@1.11.1: - resolution: {integrity: sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==} - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@xtuc/long': 4.2.2 - dev: true + '@webassemblyjs/leb128@1.11.6': + resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} - /@xtuc/ieee754@1.2.0: + '@webassemblyjs/utf8@1.11.6': + resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + + '@webassemblyjs/wasm-edit@1.12.1': + resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + + '@webassemblyjs/wasm-gen@1.12.1': + resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + + '@webassemblyjs/wasm-opt@1.12.1': + resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + + '@webassemblyjs/wasm-parser@1.12.1': + resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + + '@webassemblyjs/wast-printer@1.12.1': + resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - dev: true - /@xtuc/long@4.2.2: + '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - dev: true - /acorn-import-assertions@1.8.0(acorn@8.7.1): - resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==} + acorn-import-assertions@1.9.0: + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: acorn: ^8 - dependencies: - acorn: 8.7.1 - dev: true - /acorn@8.7.1: - resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==} + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true - /ajv-keywords@3.5.2(ajv@6.12.6): + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv-keywords@3.5.2: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: ajv: ^6.9.1 - dependencies: - ajv: 6.12.6 - dev: true - /ajv@6.12.6: + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - /ansi-styles@3.2.1: + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: true - /balanced-match@1.0.2: + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - /boolbase@1.0.0: + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - dev: true - /brace-expansion@1.1.11: + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - /browserslist@4.22.1: - resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - dependencies: - caniuse-lite: 1.0.30001562 - electron-to-chromium: 1.4.587 - node-releases: 2.0.13 - update-browserslist-db: 1.0.13(browserslist@4.22.1) - dev: true - /buffer-from@1.1.2: + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - /builtin-modules@3.3.0: + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - dev: true - /callsites@3.1.0: + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: true - /caniuse-api@3.0.0: + caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - dependencies: - browserslist: 4.22.1 - caniuse-lite: 1.0.30001352 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - dev: true - /caniuse-lite@1.0.30001352: - resolution: {integrity: sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==} - dev: true + caniuse-lite@1.0.30001620: + resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==} - /caniuse-lite@1.0.30001562: - resolution: {integrity: sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==} - dev: true - - /chalk@2.4.2: + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: true - /chrome-trace-event@1.0.3: + chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} - dev: true - /color-convert@1.9.3: + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: true - /color-convert@2.0.1: + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: false - /color-name@1.1.3: + color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true - /color-name@1.1.4: + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: false - /color-string@1.9.1: + color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - dev: false - /color@4.2.3: + color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - dev: false - /colord@2.9.3: + colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - dev: true - /commander@2.20.3: + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true - /commander@7.2.0: + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} - dev: true - /commondir@1.0.1: + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: true - /concat-map@0.0.1: + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - /cosmiconfig@7.1.0: + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - dev: true - /css-declaration-sorter@6.4.1(postcss@8.4.31): + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + css-declaration-sorter@6.4.1: resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} engines: {node: ^10 || ^12 || >=14} peerDependencies: postcss: ^8.0.9 - dependencies: - postcss: 8.4.31 - dev: true - /css-select@4.3.0: + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - dev: true - /css-tree@1.1.3: + css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - dev: true - /css-what@6.1.0: + css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} - dev: true - /cssesc@3.0.0: + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - dev: true - /cssnano-preset-default@5.2.14(postcss@8.4.31): + cssnano-preset-default@5.2.14: resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.4.31) - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-calc: 8.2.4(postcss@8.4.31) - postcss-colormin: 5.3.1(postcss@8.4.31) - postcss-convert-values: 5.1.3(postcss@8.4.31) - postcss-discard-comments: 5.1.2(postcss@8.4.31) - postcss-discard-duplicates: 5.1.0(postcss@8.4.31) - postcss-discard-empty: 5.1.1(postcss@8.4.31) - postcss-discard-overridden: 5.1.0(postcss@8.4.31) - postcss-merge-longhand: 5.1.7(postcss@8.4.31) - postcss-merge-rules: 5.1.4(postcss@8.4.31) - postcss-minify-font-values: 5.1.0(postcss@8.4.31) - postcss-minify-gradients: 5.1.1(postcss@8.4.31) - postcss-minify-params: 5.1.4(postcss@8.4.31) - postcss-minify-selectors: 5.2.1(postcss@8.4.31) - postcss-normalize-charset: 5.1.0(postcss@8.4.31) - postcss-normalize-display-values: 5.1.0(postcss@8.4.31) - postcss-normalize-positions: 5.1.1(postcss@8.4.31) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.31) - postcss-normalize-string: 5.1.0(postcss@8.4.31) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.31) - postcss-normalize-unicode: 5.1.1(postcss@8.4.31) - postcss-normalize-url: 5.1.0(postcss@8.4.31) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.31) - postcss-ordered-values: 5.1.3(postcss@8.4.31) - postcss-reduce-initial: 5.1.2(postcss@8.4.31) - postcss-reduce-transforms: 5.1.0(postcss@8.4.31) - postcss-svgo: 5.1.0(postcss@8.4.31) - postcss-unique-selectors: 5.1.1(postcss@8.4.31) - dev: true - - /cssnano-utils@3.1.0(postcss@8.4.31): + + cssnano-utils@3.1.0: resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - dev: true - /cssnano@5.1.15(postcss@8.4.31): + cssnano@5.1.15: resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.4.31) - lilconfig: 2.1.0 - postcss: 8.4.31 - yaml: 1.10.2 - dev: true - /csso@4.2.0: + csso@4.2.0: resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} engines: {node: '>=8.0.0'} - dependencies: - css-tree: 1.1.3 - dev: true - /csstype@3.1.0: - resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==} - dev: true + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - /decky-frontend-lib@3.25.0: - resolution: {integrity: sha512-2lBoHS2AIRmuluq/bGdHBz+uyToQE7k3K/vDq1MQbDZ4eC+8CGDuh2T8yZOj3D0yjGP2MdikNNAWPA9Z5l2qDg==} - dev: false - - /decode-uri-component@0.2.2: + decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} - dev: true - /deepmerge@4.2.2: - resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: true - /dom-serializer@1.4.1: + del@5.1.0: + resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - dev: true - /domelementtype@2.3.0: + domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true - /domhandler@4.3.1: + domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} - dependencies: - domelementtype: 2.3.0 - dev: true - /domutils@2.8.0: + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - dev: true - /electron-to-chromium@1.4.587: - resolution: {integrity: sha512-RyJX0q/zOkAoefZhB9XHghGeATVP0Q3mwA253XD/zj2OeXc+JZB9pCaEv6R578JUYaWM9PRhye0kXvd/V1cQ3Q==} - dev: true + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.4.775: + resolution: {integrity: sha512-JpOfl1aNAiZ88wFzjPczTLwYIoPIsij8S9/XQH9lqMpiJOf23kxea68B8wje4f68t4rOIq4Bh+vP4I65njiJBw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - /enhanced-resolve@5.9.3: - resolution: {integrity: sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==} + enhanced-resolve@5.16.1: + resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} engines: {node: '>=10.13.0'} - dependencies: - graceful-fs: 4.2.10 - tapable: 2.2.1 - dev: true - /entities@2.2.0: + entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - dev: true - /error-ex@1.3.2: + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - dev: true - /es-module-lexer@0.9.3: - resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} - dev: true + es-module-lexer@1.5.3: + resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - dev: true - /escape-string-regexp@1.0.5: + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true - /eslint-scope@5.1.1: + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - /esrecurse@4.3.0: + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - /estraverse@4.3.0: + estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} - dev: true - /estraverse@5.3.0: + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - dev: true - /estree-walker@0.6.1: + estree-walker@0.6.1: resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - dev: true - - /estree-walker@1.0.1: - resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} - dev: true - /estree-walker@2.0.2: + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - /eventemitter3@4.0.7: + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: true - /events@3.3.0: + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - dev: true - /fast-deep-equal@3.1.3: + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - /fast-json-stable-stringify@2.1.0: + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - /filter-obj@1.1.0: + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + filter-obj@1.1.0: resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} engines: {node: '>=0.10.0'} - dev: true - /fs-extra@10.1.0: + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} + + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - dependencies: - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.1 - dev: true - /fs.realpath@1.0.0: + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - requiresBuild: true - dev: true - optional: true - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - /glob-to-regexp@0.4.1: + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - dev: true - /glob@7.2.3: + glob@10.4.1: + resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - /graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: true + globby@10.0.2: + resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} + engines: {node: '>=8'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - /has-flag@3.0.0: + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true - /has-flag@4.0.0: + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} - /icss-utils@5.1.0(postcss@8.4.31): + icss-utils@5.1.0: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - dependencies: - postcss: 8.4.31 - dev: true - /import-fresh@3.3.0: + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - /inflight@1.0.6: + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - /inherits@2.0.4: + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - /interpret@1.4.0: + interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} - dev: true - /is-arrayish@0.2.1: + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true - /is-arrayish@0.3.2: + is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: false - /is-builtin-module@3.1.0: - resolution: {integrity: sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==} + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} - dependencies: - builtin-modules: 3.3.0 - dev: true - /is-core-module@2.9.0: - resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} - dependencies: - has: 1.0.3 - dev: true + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} - /is-module@1.0.0: + is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - dev: true - /is-reference@1.2.1: + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - dependencies: - '@types/estree': 0.0.51 - dev: true - /jest-worker@27.5.1: + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.0: + resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} + engines: {node: '>=14'} + + jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - dependencies: - '@types/node': 17.0.42 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - /js-tokens@4.0.0: + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true - /json-parse-even-better-errors@2.3.1: + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true - /json-schema-traverse@0.4.1: + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - /jsonfile@6.1.0: + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.10 - dev: true - /lilconfig@2.1.0: + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - dev: true - /lines-and-columns@1.2.4: + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - /loader-runner@4.3.0: + loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} - dev: true - /lodash.memoize@4.1.2: + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - dev: true - /lodash.uniq@4.5.0: + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - dev: true - /lodash@4.17.21: + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false - /magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - dependencies: - sourcemap-codec: 1.4.8 - dev: true + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true - /mdn-data@2.0.14: + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + + mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - dev: true - /merge-stream@2.0.0: + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true - /mime-db@1.52.0: + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true - /mime-types@2.1.35: + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: true - /minimatch@3.1.2: + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - /minimist@1.2.6: - resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} - dev: true + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - /nanoid@3.3.7: + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true - /neo-async@2.6.2: + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: true - /node-releases@2.0.13: - resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} - dev: true + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - /normalize-url@6.1.0: + normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} - dev: true - /nth-check@2.1.1: + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - dependencies: - boolbase: 1.0.0 - dev: true - /once@1.4.0: + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - /p-finally@1.0.0: + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} - dev: true - /p-queue@6.6.2: + p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + + p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - dev: true - /p-timeout@3.2.0: + p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} - dependencies: - p-finally: 1.0.0 - dev: true - /parent-module@1.0.1: + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - /parse-json@5.2.0: + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.22.13 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - dev: true - /path-is-absolute@1.0.1: + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - dev: true - /path-parse@1.0.7: + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - /path-type@4.0.0: + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - /picomatch@2.3.1: + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true - /postcss-calc@8.2.4(postcss@8.4.31): + postcss-calc@8.2.4: resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} peerDependencies: postcss: ^8.2.2 - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - postcss-value-parser: 4.2.0 - dev: true - /postcss-colormin@5.3.1(postcss@8.4.31): + postcss-colormin@5.3.1: resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-convert-values@5.1.3(postcss@8.4.31): + postcss-convert-values@5.1.3: resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-discard-comments@5.1.2(postcss@8.4.31): + postcss-discard-comments@5.1.2: resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - dev: true - /postcss-discard-duplicates@5.1.0(postcss@8.4.31): + postcss-discard-duplicates@5.1.0: resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - dev: true - /postcss-discard-empty@5.1.1(postcss@8.4.31): + postcss-discard-empty@5.1.1: resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - dev: true - /postcss-discard-overridden@5.1.0(postcss@8.4.31): + postcss-discard-overridden@5.1.0: resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - dev: true - /postcss-merge-longhand@5.1.7(postcss@8.4.31): + postcss-merge-longhand@5.1.7: resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.31) - dev: true - /postcss-merge-rules@5.1.4(postcss@8.4.31): + postcss-merge-rules@5.1.4: resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - dev: true - /postcss-minify-font-values@5.1.0(postcss@8.4.31): + postcss-minify-font-values@5.1.0: resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-minify-gradients@5.1.1(postcss@8.4.31): + postcss-minify-gradients@5.1.1: resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-minify-params@5.1.4(postcss@8.4.31): + postcss-minify-params@5.1.4: resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-minify-selectors@5.2.1(postcss@8.4.31): + postcss-minify-selectors@5.2.1: resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - dev: true - /postcss-modules-extract-imports@3.0.0(postcss@8.4.31): - resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - dependencies: - postcss: 8.4.31 - dev: true - /postcss-modules-local-by-default@4.0.3(postcss@8.4.31): - resolution: {integrity: sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==} + postcss-modules-local-by-default@4.0.5: + resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - dependencies: - icss-utils: 5.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - postcss-value-parser: 4.2.0 - dev: true - /postcss-modules-scope@3.0.0(postcss@8.4.31): - resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} + postcss-modules-scope@3.2.0: + resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - dev: true - /postcss-modules-values@4.0.0(postcss@8.4.31): + postcss-modules-values@4.0.0: resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - dependencies: - icss-utils: 5.1.0(postcss@8.4.31) - postcss: 8.4.31 - dev: true - /postcss-normalize-charset@5.1.0(postcss@8.4.31): + postcss-normalize-charset@5.1.0: resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - dev: true - /postcss-normalize-display-values@5.1.0(postcss@8.4.31): + postcss-normalize-display-values@5.1.0: resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-positions@5.1.1(postcss@8.4.31): + postcss-normalize-positions@5.1.1: resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-repeat-style@5.1.1(postcss@8.4.31): + postcss-normalize-repeat-style@5.1.1: resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-string@5.1.0(postcss@8.4.31): + postcss-normalize-string@5.1.0: resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-timing-functions@5.1.0(postcss@8.4.31): + postcss-normalize-timing-functions@5.1.0: resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-unicode@5.1.1(postcss@8.4.31): + postcss-normalize-unicode@5.1.1: resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-url@5.1.0(postcss@8.4.31): + postcss-normalize-url@5.1.0: resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - normalize-url: 6.1.0 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-normalize-whitespace@5.1.1(postcss@8.4.31): + postcss-normalize-whitespace@5.1.1: resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-ordered-values@5.1.3(postcss@8.4.31): + postcss-ordered-values@5.1.3: resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-reduce-initial@5.1.2(postcss@8.4.31): + postcss-reduce-initial@5.1.2: resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - caniuse-api: 3.0.0 - postcss: 8.4.31 - dev: true - /postcss-reduce-transforms@5.1.0(postcss@8.4.31): + postcss-reduce-transforms@5.1.0: resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - dev: true - /postcss-selector-parser@6.0.13: - resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} + postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true - /postcss-svgo@5.1.0(postcss@8.4.31): + postcss-svgo@5.1.0: resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 - dev: true - /postcss-unique-selectors@5.1.1(postcss@8.4.31): + postcss-unique-selectors@5.1.1: resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - dev: true - /postcss-value-parser@4.2.0: + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true - /postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - /punycode@2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true - /query-string@7.1.3: + query-string@7.1.3: resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} engines: {node: '>=6'} - dependencies: - decode-uri-component: 0.2.2 - filter-obj: 1.1.0 - split-on-first: 1.1.0 - strict-uri-encode: 2.0.0 - dev: true - /randombytes@2.1.0: + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - dependencies: - safe-buffer: 5.2.1 - dev: true - /react-icons@4.12.0: + react-icons@4.12.0: resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==} peerDependencies: react: '*' - peerDependenciesMeta: - react: - optional: true - dev: false - /rechoir@0.6.2: + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} - dependencies: - resolve: 1.22.0 - dev: true - /resolve-from@4.0.0: + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - dev: true - /resolve@1.22.0: - resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - dependencies: - is-core-module: 2.9.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - /rollup-plugin-import-assets@1.1.1(rollup@2.75.6): + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup-plugin-delete@2.0.0: + resolution: {integrity: sha512-/VpLMtDy+8wwRlDANuYmDa9ss/knGsAgrDhM+tEwB1npHwNu4DYNmDfUL55csse/GHs9Q+SMT/rw9uiaZ3pnzA==} + engines: {node: '>=10'} + + rollup-plugin-external-globals@0.10.0: + resolution: {integrity: sha512-RXlupZrmn97AaaS5dWnktkjM+Iy+od0E+8L0mUkMIs3iuoUXNJebueQocQKV7Ircd54fSGGmkBaXwNzY05J1yQ==} + peerDependencies: + rollup: ^2.25.0 || ^3.3.0 || ^4.1.4 + + rollup-plugin-import-assets@1.1.1: resolution: {integrity: sha512-u5zJwOjguTf2N+wETq2weNKGvNkuVc1UX/fPgg215p5xPvGOaI6/BTc024E9brvFjSQTfIYqgvwogQdipknu1g==} peerDependencies: rollup: '>=1.9.0' - dependencies: - rollup: 2.75.6 - rollup-pluginutils: 2.8.2 - url-join: 4.0.1 - dev: true - /rollup-plugin-styles@4.0.0(rollup@2.75.6): + rollup-plugin-styles@4.0.0: resolution: {integrity: sha512-A2K2sao84OsTmDxXG83JTCdXWrmgvQkkI38XDat46rdtpGMRm9tSYqeCdlwwGDJF4kKIafhV1mUidqu8MxUGig==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: rollup: ^2.63.0 - dependencies: - '@rollup/pluginutils': 4.2.1 - '@types/cssnano': 5.1.0(postcss@8.4.31) - cosmiconfig: 7.1.0 - cssnano: 5.1.15(postcss@8.4.31) - fs-extra: 10.1.0 - icss-utils: 5.1.0(postcss@8.4.31) - mime-types: 2.1.35 - p-queue: 6.6.2 - postcss: 8.4.31 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.31) - postcss-modules-local-by-default: 4.0.3(postcss@8.4.31) - postcss-modules-scope: 3.0.0(postcss@8.4.31) - postcss-modules-values: 4.0.0(postcss@8.4.31) - postcss-value-parser: 4.2.0 - query-string: 7.1.3 - resolve: 1.22.0 - rollup: 2.75.6 - source-map-js: 1.0.2 - tslib: 2.4.0 - dev: true - /rollup-pluginutils@2.8.2: + rollup-pluginutils@2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - dependencies: - estree-walker: 0.6.1 - dev: true - /rollup@2.75.6: - resolution: {integrity: sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==} - engines: {node: '>=10.0.0'} + rollup@4.18.0: + resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - /safe-buffer@5.2.1: + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true - /schema-utils@3.1.1: - resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} - dependencies: - '@types/json-schema': 7.0.11 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) - dev: true - /serialize-javascript@6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} - dependencies: - randombytes: 2.1.0 - dev: true + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} - /shelljs@0.8.5: + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shelljs@0.8.5: resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} engines: {node: '>=4'} hasBin: true - dependencies: - glob: 7.2.3 - interpret: 1.4.0 - rechoir: 0.6.2 - dev: true - /shx@0.3.4: + shx@0.3.4: resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==} engines: {node: '>=6'} hasBin: true - dependencies: - minimist: 1.2.6 - shelljs: 0.8.5 - dev: true - /simple-swizzle@0.2.2: + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - dependencies: - is-arrayish: 0.3.2 - dev: false - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} - dev: true - /source-map-support@0.5.21: + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: true - /source-map@0.6.1: + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - dev: true - /sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - dev: true - - /split-on-first@1.1.0: + split-on-first@1.1.0: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} - dev: true - /stable@0.1.8: + stable@0.1.8: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - dev: true - /strict-uri-encode@2.0.0: + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} - dev: true - /stylehacks@5.1.1(postcss@8.4.31): + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + stylehacks@5.1.1: resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 - dependencies: - browserslist: 4.22.1 - postcss: 8.4.31 - postcss-selector-parser: 6.0.13 - dev: true - /supports-color@5.5.0: + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: true - /supports-color@8.1.1: + supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - dependencies: - has-flag: 4.0.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: true - /svgo@2.8.0: + svgo@2.8.0: resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} engines: {node: '>=10.13.0'} hasBin: true - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.0.0 - stable: 0.1.8 - dev: true - /tapable@2.2.1: + tailwind-merge@2.3.0: + resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} + + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - dev: true - /terser-webpack-plugin@5.3.3(webpack@5.73.0): - resolution: {integrity: sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==} + terser-webpack-plugin@5.3.10: + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -1744,81 +1407,61 @@ packages: optional: true uglify-js: optional: true - dependencies: - '@jridgewell/trace-mapping': 0.3.13 - jest-worker: 27.5.1 - schema-utils: 3.1.1 - serialize-javascript: 6.0.0 - terser: 5.14.1 - webpack: 5.73.0 - dev: true - - /terser@5.14.1: - resolution: {integrity: sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==} + + terser@5.31.0: + resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==} engines: {node: '>=10'} hasBin: true - dependencies: - '@jridgewell/source-map': 0.3.2 - acorn: 8.7.1 - commander: 2.20.3 - source-map-support: 0.5.21 - dev: true - /tslib@2.4.0: - resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - dev: true + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} - /typescript@4.7.3: - resolution: {integrity: sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==} - engines: {node: '>=4.2.0'} + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} hasBin: true - dev: true - /universalify@2.0.1: + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - dev: true - /update-browserslist-db@1.0.13(browserslist@4.22.1): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.22.1 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - /uri-js@4.4.1: + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.1.1 - dev: true - /url-join@4.0.1: + url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} - dev: true - /util-deprecate@1.0.2: + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true - /watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + watchpack@2.4.1: + resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.10 - dev: true - /webpack-sources@3.2.3: + webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - dev: true - /webpack@5.73.0: - resolution: {integrity: sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==} + webpack@5.91.0: + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -1826,42 +1469,1449 @@ packages: peerDependenciesMeta: webpack-cli: optional: true - dependencies: - '@types/eslint-scope': 3.7.3 - '@types/estree': 0.0.51 - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/wasm-edit': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 - acorn: 8.7.1 - acorn-import-assertions: 1.8.0(acorn@8.7.1) - browserslist: 4.22.1 - chrome-trace-event: 1.0.3 - enhanced-resolve: 5.9.3 - es-module-lexer: 0.9.3 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.10 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.1.1 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.3(webpack@5.73.0) - watchpack: 2.4.0 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - dev: true - /wrappy@1.0.2: + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - /yaml@1.10.2: + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - dev: true + + zustand@4.5.2: + resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@babel/code-frame@7.24.2': + dependencies: + '@babel/highlight': 7.24.5 + picocolors: 1.0.1 + + '@babel/helper-validator-identifier@7.24.5': {} + + '@babel/highlight@7.24.5': + dependencies: + '@babel/helper-validator-identifier': 7.24.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/runtime@7.24.5': + dependencies: + regenerator-runtime: 0.14.1 + + '@decky/api@1.0.3': {} + + '@decky/ui@4.0.1': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/plugin-alias@5.1.0(rollup@4.18.0)': + dependencies: + slash: 4.0.0 + optionalDependencies: + rollup: 4.18.0 + + '@rollup/plugin-commonjs@26.0.1(rollup@4.18.0)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 10.4.1 + is-reference: 1.2.1 + magic-string: 0.30.10 + optionalDependencies: + rollup: 4.18.0 + + '@rollup/plugin-json@6.1.0(rollup@4.18.0)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + optionalDependencies: + rollup: 4.18.0 + + '@rollup/plugin-node-resolve@15.2.3(rollup@4.18.0)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-builtin-module: 3.2.1 + is-module: 1.0.0 + resolve: 1.22.8 + optionalDependencies: + rollup: 4.18.0 + + '@rollup/plugin-replace@5.0.7(rollup@4.18.0)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + magic-string: 0.30.10 + optionalDependencies: + rollup: 4.18.0 + + '@rollup/plugin-typescript@11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@5.4.5)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + resolve: 1.22.8 + typescript: 5.4.5 + optionalDependencies: + rollup: 4.18.0 + tslib: 2.6.2 + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + + '@rollup/pluginutils@5.1.0(rollup@4.18.0)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.18.0 + + '@rollup/rollup-android-arm-eabi@4.18.0': + optional: true + + '@rollup/rollup-android-arm64@4.18.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.18.0': + optional: true + + '@rollup/rollup-darwin-x64@4.18.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.18.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.18.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.18.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.18.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.18.0': + optional: true + + '@trysound/sax@0.2.0': {} + + '@types/color-convert@2.0.3': + dependencies: + '@types/color-name': 1.1.4 + + '@types/color-name@1.1.4': {} + + '@types/color@3.0.6': + dependencies: + '@types/color-convert': 2.0.3 + + '@types/cssnano@5.1.0(postcss@8.4.38)': + dependencies: + cssnano: 5.1.15(postcss@8.4.38) + transitivePeerDependencies: + - postcss + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 8.56.10 + '@types/estree': 1.0.5 + + '@types/eslint@8.56.10': + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.5': {} + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 20.12.12 + + '@types/json-schema@7.0.15': {} + + '@types/lodash@4.17.4': {} + + '@types/minimatch@5.1.2': {} + + '@types/node@20.12.12': + dependencies: + undici-types: 5.26.5 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.12': {} + + '@types/react@16.14.0': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@types/resolve@1.20.2': {} + + '@types/webpack@5.28.5': + dependencies: + '@types/node': 20.12.12 + tapable: 2.2.1 + webpack: 5.91.0 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@webassemblyjs/ast@1.12.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + + '@webassemblyjs/floating-point-hex-parser@1.11.6': {} + + '@webassemblyjs/helper-api-error@1.11.6': {} + + '@webassemblyjs/helper-buffer@1.12.1': {} + + '@webassemblyjs/helper-numbers@1.11.6': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.11.6 + '@webassemblyjs/helper-api-error': 1.11.6 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.11.6': {} + + '@webassemblyjs/helper-wasm-section@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/wasm-gen': 1.12.1 + + '@webassemblyjs/ieee754@1.11.6': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.11.6': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.11.6': {} + + '@webassemblyjs/wasm-edit@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-opt': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/wast-printer': 1.12.1 + + '@webassemblyjs/wasm-gen@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + + '@webassemblyjs/wasm-opt@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + + '@webassemblyjs/wasm-parser@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + + '@webassemblyjs/wast-printer@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + acorn-import-assertions@1.9.0(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + + acorn@8.11.3: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + array-union@2.1.0: {} + + balanced-match@1.0.2: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.0: + dependencies: + caniuse-lite: 1.0.30001620 + electron-to-chromium: 1.4.775 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.0) + + buffer-from@1.1.2: {} + + builtin-modules@3.3.0: {} + + callsites@3.1.0: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.23.0 + caniuse-lite: 1.0.30001620 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001620: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chrome-trace-event@1.0.3: {} + + clean-stack@2.2.0: {} + + clsx@2.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + colord@2.9.3: {} + + commander@2.20.3: {} + + commander@7.2.0: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-declaration-sorter@6.4.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.1.0: {} + + cssesc@3.0.0: {} + + cssnano-preset-default@5.2.14(postcss@8.4.38): + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.4.38) + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-calc: 8.2.4(postcss@8.4.38) + postcss-colormin: 5.3.1(postcss@8.4.38) + postcss-convert-values: 5.1.3(postcss@8.4.38) + postcss-discard-comments: 5.1.2(postcss@8.4.38) + postcss-discard-duplicates: 5.1.0(postcss@8.4.38) + postcss-discard-empty: 5.1.1(postcss@8.4.38) + postcss-discard-overridden: 5.1.0(postcss@8.4.38) + postcss-merge-longhand: 5.1.7(postcss@8.4.38) + postcss-merge-rules: 5.1.4(postcss@8.4.38) + postcss-minify-font-values: 5.1.0(postcss@8.4.38) + postcss-minify-gradients: 5.1.1(postcss@8.4.38) + postcss-minify-params: 5.1.4(postcss@8.4.38) + postcss-minify-selectors: 5.2.1(postcss@8.4.38) + postcss-normalize-charset: 5.1.0(postcss@8.4.38) + postcss-normalize-display-values: 5.1.0(postcss@8.4.38) + postcss-normalize-positions: 5.1.1(postcss@8.4.38) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.38) + postcss-normalize-string: 5.1.0(postcss@8.4.38) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.38) + postcss-normalize-unicode: 5.1.1(postcss@8.4.38) + postcss-normalize-url: 5.1.0(postcss@8.4.38) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.38) + postcss-ordered-values: 5.1.3(postcss@8.4.38) + postcss-reduce-initial: 5.1.2(postcss@8.4.38) + postcss-reduce-transforms: 5.1.0(postcss@8.4.38) + postcss-svgo: 5.1.0(postcss@8.4.38) + postcss-unique-selectors: 5.1.1(postcss@8.4.38) + + cssnano-utils@3.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + cssnano@5.1.15(postcss@8.4.38): + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.4.38) + lilconfig: 2.1.0 + postcss: 8.4.38 + yaml: 1.10.2 + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csstype@3.1.3: {} + + decode-uri-component@0.2.2: {} + + deepmerge@4.3.1: {} + + del@5.1.0: + dependencies: + globby: 10.0.2 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 2.2.0 + is-path-inside: 3.0.3 + p-map: 3.0.0 + rimraf: 3.0.2 + slash: 3.0.0 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.4.775: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.16.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + entities@2.2.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@1.5.3: {} + + escalade@3.1.2: {} + + escape-string-regexp@1.0.5: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + estree-walker@0.6.1: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.5 + + eventemitter3@4.0.7: {} + + events@3.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fast-json-stable-stringify@2.1.0: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@1.1.0: {} + + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.1: + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.0 + minimatch: 9.0.4 + minipass: 7.1.2 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globby@10.0.2: + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + glob: 7.2.3 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + icss-utils@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + ignore@5.3.1: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + interpret@1.4.0: {} + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + + is-core-module@2.13.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-module@1.0.0: {} + + is-number@7.0.0: {} + + is-path-cwd@2.2.0: {} + + is-path-inside@3.0.3: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.5 + + is-reference@3.0.2: + dependencies: + '@types/estree': 1.0.5 + + isexe@2.0.0: {} + + jackspeak@3.4.0: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.12.12 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + js-tokens@4.0.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lilconfig@2.1.0: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.0: {} + + lodash.memoize@4.1.2: {} + + lodash.uniq@4.5.0: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.2.2: {} + + magic-string@0.30.10: + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + + mdn-data@2.0.14: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.4: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + nanoid@3.3.7: {} + + neo-async@2.6.2: {} + + node-releases@2.0.14: {} + + normalize-url@6.1.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + p-finally@1.0.0: {} + + p-map@3.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.2 + minipass: 7.1.2 + + path-type@4.0.0: {} + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + postcss-calc@8.2.4(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + postcss-value-parser: 4.2.0 + + postcss-colormin@5.3.1(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-convert-values@5.1.3(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-discard-comments@5.1.2(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + postcss-discard-duplicates@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + postcss-discard-empty@5.1.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + postcss-discard-overridden@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + postcss-merge-longhand@5.1.7(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.4.38) + + postcss-merge-rules@5.1.4(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + + postcss-minify-font-values@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@5.1.1(postcss@8.4.38): + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-minify-params@5.1.4(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@5.2.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + + postcss-modules-extract-imports@3.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + postcss-modules-local-by-default@4.0.5(postcss@8.4.38): + dependencies: + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + + postcss-modules-values@4.0.0(postcss@8.4.38): + dependencies: + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + + postcss-normalize-charset@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + + postcss-normalize-display-values@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@5.1.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@5.1.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@5.1.1(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@5.1.0(postcss@8.4.38): + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@5.1.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-ordered-values@5.1.3(postcss@8.4.38): + dependencies: + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-reduce-initial@5.1.2(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + caniuse-api: 3.0.0 + postcss: 8.4.38 + + postcss-reduce-transforms@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + + postcss-selector-parser@6.0.16: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-svgo@5.1.0(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + + postcss-unique-selectors@5.1.1(postcss@8.4.38): + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.38: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + punycode@2.3.1: {} + + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + react-icons@4.12.0(react@18.3.1): + dependencies: + react: 18.3.1 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + rechoir@0.6.2: + dependencies: + resolve: 1.22.8 + + regenerator-runtime@0.14.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup-plugin-delete@2.0.0: + dependencies: + del: 5.1.0 + + rollup-plugin-external-globals@0.10.0(rollup@4.18.0): + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + estree-walker: 3.0.3 + is-reference: 3.0.2 + magic-string: 0.30.10 + rollup: 4.18.0 + + rollup-plugin-import-assets@1.1.1(rollup@4.18.0): + dependencies: + rollup: 4.18.0 + rollup-pluginutils: 2.8.2 + url-join: 4.0.1 + + rollup-plugin-styles@4.0.0(rollup@4.18.0): + dependencies: + '@rollup/pluginutils': 4.2.1 + '@types/cssnano': 5.1.0(postcss@8.4.38) + cosmiconfig: 7.1.0 + cssnano: 5.1.15(postcss@8.4.38) + fs-extra: 10.1.0 + icss-utils: 5.1.0(postcss@8.4.38) + mime-types: 2.1.35 + p-queue: 6.6.2 + postcss: 8.4.38 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) + postcss-modules-scope: 3.2.0(postcss@8.4.38) + postcss-modules-values: 4.0.0(postcss@8.4.38) + postcss-value-parser: 4.2.0 + query-string: 7.1.3 + resolve: 1.22.8 + rollup: 4.18.0 + source-map-js: 1.2.0 + tslib: 2.6.2 + + rollup-pluginutils@2.8.2: + dependencies: + estree-walker: 0.6.1 + + rollup@4.18.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.0 + '@rollup/rollup-android-arm64': 4.18.0 + '@rollup/rollup-darwin-arm64': 4.18.0 + '@rollup/rollup-darwin-x64': 4.18.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 + '@rollup/rollup-linux-arm-musleabihf': 4.18.0 + '@rollup/rollup-linux-arm64-gnu': 4.18.0 + '@rollup/rollup-linux-arm64-musl': 4.18.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 + '@rollup/rollup-linux-riscv64-gnu': 4.18.0 + '@rollup/rollup-linux-s390x-gnu': 4.18.0 + '@rollup/rollup-linux-x64-gnu': 4.18.0 + '@rollup/rollup-linux-x64-musl': 4.18.0 + '@rollup/rollup-win32-arm64-msvc': 4.18.0 + '@rollup/rollup-win32-ia32-msvc': 4.18.0 + '@rollup/rollup-win32-x64-msvc': 4.18.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shelljs@0.8.5: + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + + shx@0.3.4: + dependencies: + minimist: 1.2.8 + shelljs: 0.8.5 + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + slash@3.0.0: {} + + slash@4.0.0: {} + + source-map-js@1.2.0: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + split-on-first@1.1.0: {} + + stable@0.1.8: {} + + strict-uri-encode@2.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + stylehacks@5.1.1(postcss@8.4.38): + dependencies: + browserslist: 4.23.0 + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.1 + stable: 0.1.8 + + tailwind-merge@2.3.0: + dependencies: + '@babel/runtime': 7.24.5 + + tapable@2.2.1: {} + + terser-webpack-plugin@5.3.10(webpack@5.91.0): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.31.0 + webpack: 5.91.0 + + terser@5.31.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.11.3 + commander: 2.20.3 + source-map-support: 0.5.21 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.6.2: {} + + typescript@5.4.5: {} + + undici-types@5.26.5: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.0.16(browserslist@4.23.0): + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-join@4.0.1: {} + + use-sync-external-store@1.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + watchpack@2.4.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + webpack-sources@3.2.3: {} + + webpack@5.91.0: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + browserslist: 4.23.0 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.16.1 + es-module-lexer: 1.5.3 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.91.0) + watchpack: 2.4.1 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + yaml@1.10.2: {} + + zustand@4.5.2(@types/react@16.14.0)(react@18.3.1): + dependencies: + use-sync-external-store: 1.2.0(react@18.3.1) + optionalDependencies: + '@types/react': 16.14.0 + react: 18.3.1 diff --git a/rollup.config.js b/rollup.config.js index a0bcc37..63360a9 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,36 +6,61 @@ import typescript from "@rollup/plugin-typescript"; import { defineConfig } from "rollup"; import importAssets from "rollup-plugin-import-assets"; import styles from "rollup-plugin-styles"; +import alias from "@rollup/plugin-alias"; +import del from "rollup-plugin-delete"; +import externalGlobals from "rollup-plugin-external-globals"; -import { name } from "./plugin.json"; +// replace "assert" with "with" once node implements that +import manifest from "./plugin.json" assert { type: "json" }; export default defineConfig({ input: "./src/index.tsx", plugins: [ + del({ targets: "./dist/*", force: true }), commonjs(), - nodeResolve(), + nodeResolve({ + browser: true, + }), + externalGlobals({ + react: "SP_REACT", + "react-dom": "SP_REACTDOM", + "@decky/ui": "DFL", + "@decky/manifest": JSON.stringify(manifest), + }), typescript(), json(), - styles(), replace({ preventAssignment: false, "process.env.NODE_ENV": JSON.stringify("production"), }), + styles(), + alias({ + entries: [ + { find: "@cssloader/backend", replacement: "./src/backend" }, + { find: "@/backend", replacement: "./src/backend-impl" }, + { find: "@/lib", replacement: "./src/lib" }, + { find: "@/styles", replacement: "./src/styles" }, + { find: "@/types", replacement: "./src/types" }, + { find: "@/modules", replacement: "./src/modules" }, + { find: "@/decky-patches", replacement: "./src/decky-patches" }, + ], + }), importAssets({ - publicPath: `http://127.0.0.1:1337/plugins/${name}/`, + publicPath: `http://127.0.0.1:1337/plugins/${manifest.name}/`, }), ], context: "window", - external: ["react", "react-dom", "decky-frontend-lib"], + external: ["react", "react-dom", "@decky/ui"], output: { - file: "dist/index.js", - globals: { - react: "SP_REACT", - "react-dom": "SP_REACTDOM", - "decky-frontend-lib": "DFL", - }, - format: "iife", + dir: "dist", + format: "esm", + sourcemap: true, + // **Don't** change this. + sourcemapPathTransform: (relativeSourcePath) => + relativeSourcePath.replace( + /^\.\.\//, + `decky://decky/plugin/${encodeURIComponent(manifest.name)}/` + ), exports: "default", - assetFileNames: "[name]-[hash][extname]", }, }); diff --git a/src/api.ts b/src/api.ts deleted file mode 100644 index 3338293..0000000 --- a/src/api.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { ServerAPI } from "decky-frontend-lib"; -import { CssLoaderState } from "./state"; -import { toast, storeWrite, downloadThemeFromUrl, reloadBackend } from "./python"; -import { ThemeQueryRequest } from "./apiTypes"; -import { generateParamStr } from "./logic"; - -var server: ServerAPI | undefined = undefined; -var globalState: CssLoaderState | undefined = undefined; - -export function setServer(s: ServerAPI): void { - server = s; -} -export function setStateClass(s: CssLoaderState): void { - globalState = s; -} - -export function logOut(): void { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - setGlobalState("apiShortToken", ""); - setGlobalState("apiFullToken", ""); - setGlobalState("apiTokenExpireDate", undefined); - setGlobalState("apiMeData", undefined); - storeWrite("shortToken", ""); -} - -export async function logInWithShortToken( - shortTokenInterimValue?: string | undefined -): Promise { - const { apiUrl, apiShortToken } = globalState!.getPublicState(); - const shortTokenValue = shortTokenInterimValue ? shortTokenInterimValue : apiShortToken; - const setGlobalState = globalState!.setGlobalState.bind(globalState); - if (shortTokenValue.length === 12) { - return server! - .fetchNoCors(`${apiUrl}/auth/authenticate_token`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ token: shortTokenValue }), - }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } - throw new Error(`Fetch not successful!`); - }) - .then((res) => { - // @ts-ignore - return JSON.parse(res?.body || ""); - }) - .then((json) => { - if (json) { - return json; - } - throw new Error(`No json returned!`); - }) - .then((data) => { - if (data && data?.token) { - storeWrite("shortToken", shortTokenValue); - setGlobalState("apiShortToken", shortTokenValue); - setGlobalState("apiFullToken", data.token); - setGlobalState("apiTokenExpireDate", new Date().valueOf() + 1000 * 60 * 10); - genericGET(`/auth/me_full`, true, data.token).then((meData) => { - if (meData?.username) { - setGlobalState("apiMeData", meData); - toast("Logged In!", `Logged in as ${meData.username}`); - } - }); - } else { - toast("Error Authenticating", JSON.stringify(data)); - } - }) - .catch((err) => { - console.error(`Error authenticating from short token.`, err); - toast("Error Authenticating", JSON.stringify(err)); - }); - } else { - toast("Invalid Token", "Token must be 12 characters long."); - } -} - -// This returns the token that is intended to be used in whatever call -export function refreshToken(onError: () => void = () => {}): Promise { - const { apiFullToken, apiTokenExpireDate, apiUrl } = globalState!.getPublicState(); - const setGlobalState = globalState!.setGlobalState.bind(globalState); - if (!apiFullToken) { - return Promise.resolve(undefined); - } - if (apiTokenExpireDate === undefined) { - return Promise.resolve(apiFullToken); - } - // @ts-ignore - if (new Date().valueOf() < apiTokenExpireDate) { - return Promise.resolve(apiFullToken); - } - return server! - .fetchNoCors(`${apiUrl}/auth/refresh_token`, { - method: "POST", - headers: { - Authorization: `Bearer ${apiFullToken}`, - }, - }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } - throw new Error(`Fetch not successful!`); - }) - .then((res) => { - if (res.status >= 200 && res.status <= 300 && res.body) { - // @ts-ignore - return JSON.parse(res.body || ""); - } - throw new Error(`Res not OK!, code ${res.status}`); - }) - .then((json) => { - if (json.token) { - return json.token; - } - throw new Error(`No token returned!`); - }) - .then((token) => { - setGlobalState("apiFullToken", token); - setGlobalState("apiTokenExpireDate", new Date().valueOf() + 1000 * 10 * 60); - return token; - }) - .catch((err) => { - console.error(`Error Refreshing Token!`, err); - onError(); - }); -} - -export async function genericGET( - fetchPath: string, - requiresAuth: boolean = false, - customAuthToken: string | undefined = undefined, - onError: () => void = () => {}, - failSilently: boolean = false -) { - const { apiUrl } = globalState!.getPublicState(); - function doTheFetching(authToken: string | undefined = undefined) { - return server! - .fetchNoCors(`${apiUrl}${fetchPath}`, { - method: "GET", - headers: authToken - ? { - Authorization: `Bearer ${authToken}`, - } - : {}, - }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } - throw new Error(`Fetch not successful!`); - }) - .then((res) => { - if (res.status >= 200 && res.status <= 300 && res.body) { - // @ts-ignore - return JSON.parse(res.body || ""); - } - throw new Error(`Res not OK!, code ${res.status}`); - }) - .then((json) => { - if (json) { - return json; - } - throw new Error(`No json returned!`); - }) - .catch((err) => { - if (!failSilently) { - console.error(`Error fetching ${fetchPath}`, err); - } - onError(); - }); - } - if (requiresAuth) { - if (customAuthToken) { - return doTheFetching(customAuthToken); - } - return refreshToken(onError).then((token) => { - if (token) { - return doTheFetching(token); - } else { - toast("Error Refreshing Token!", ""); - return; - } - }); - } else { - return doTheFetching(); - } -} - -export function getThemes( - searchOpts: ThemeQueryRequest, - apiPath: string, - globalStateVarName: string, - setSnapIndex: (i: number) => void, - requiresAuth: boolean = false -) { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - // TODO: Refactor, this works now, just jank - const prependString = - // If the user searches for desktop themes, show desktop themes, otherwise only show BPM themes - (searchOpts.filters.includes("Desktop") - ? "-Preset" - : // If the user searches for presets, show presets, otherwise exclude them - searchOpts.filters === "Preset" - ? "BPM-CSS" - : "BPM-CSS.-Preset") + - // If there are other filters after the prepend, add a ".", otherwise don't - (searchOpts.filters !== "All" ? "." : ""); - - const queryStr = generateParamStr( - searchOpts.filters !== "All" ? searchOpts : { ...searchOpts, filters: "" }, - prependString - ); - genericGET(`${apiPath}${queryStr}`, requiresAuth).then((data) => { - if (data.total > 0) { - setGlobalState(globalStateVarName, data); - } else { - setGlobalState(globalStateVarName, { total: 0, items: [] }); - } - setSnapIndex(-1); - }); -} - -export function toggleStar(themeId: string, isStarred: boolean, authToken: string) { - const { apiUrl } = globalState!.getPublicState(); - return server! - .fetchNoCors(`${apiUrl}/users/me/stars/${themeId}`, { - method: isStarred ? "DELETE" : "POST", - headers: { - Authorization: `Bearer ${authToken}`, - }, - }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } - throw new Error(`Fetch not successful!`); - }) - .then((res) => { - if (res.status >= 200 && res.status <= 300) { - // @ts-ignore - return true; - } - throw new Error(`Res not OK!, code ${res.status}`); - }) - .catch((err) => { - console.error(`Error starring theme`, err); - }); -} - -export async function installTheme(themeId: string) { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - setGlobalState("isInstalling", true); - await downloadThemeFromUrl(themeId); - await reloadBackend(); - setGlobalState("isInstalling", false); - return; -} diff --git a/src/backend-impl/decky-backend-repository-impl.ts b/src/backend-impl/decky-backend-repository-impl.ts new file mode 100644 index 0000000..4bb5b79 --- /dev/null +++ b/src/backend-impl/decky-backend-repository-impl.ts @@ -0,0 +1,37 @@ +import { callable, toaster } from "@decky/api"; +import { CallError, FetchError, type IBackendRepository } from "@cssloader/backend"; + +class DeckyBackendRepository implements IBackendRepository { + async call(methodName: string, args: Args) { + const func = callable(methodName); + try { + return await func(...args); + } catch (error: unknown) { + throw new CallError( + "Error Calling Backend", + methodName, + error instanceof Error ? error.message : "No Error Message Provided" + ); + } + } + async fetch(url: string, request: RequestInit) { + try { + const res = await fetch(url, request); + if (!res.ok) { + throw new Error(`Res Not Okay - Code ${res.status}`); + } + return res.json() as Return; + } catch (error: unknown) { + throw new FetchError( + "Error Fetching", + url, + error instanceof Error ? error.message : "No Error Message Provided" + ); + } + } + toast(title: string, body?: string) { + toaster.toast({ title: title, body: body ?? "", duration: 5000 }); + } +} + +export default DeckyBackendRepository; diff --git a/src/backend-impl/decky-backend-service.ts b/src/backend-impl/decky-backend-service.ts new file mode 100644 index 0000000..c228d89 --- /dev/null +++ b/src/backend-impl/decky-backend-service.ts @@ -0,0 +1,4 @@ +import { Backend } from "@cssloader/backend"; +import DeckyBackendRepository from "./decky-backend-repository-impl"; + +export const backend = Backend.getInstance(new DeckyBackendRepository()); diff --git a/src/backend-impl/decky-theme-store.ts b/src/backend-impl/decky-theme-store.ts new file mode 100644 index 0000000..908bc07 --- /dev/null +++ b/src/backend-impl/decky-theme-store.ts @@ -0,0 +1,31 @@ +import { + CSSLoaderStateActions, + CSSLoaderStateValues, + ICSSLoaderState, + createCSSLoaderStore, +} from "@cssloader/backend"; +import { backend } from "./decky-backend-service"; +import { useStore } from "zustand"; + +const cssLoaderStore = createCSSLoaderStore(backend); + +const useCSSLoaderStore = (fn: (state: ICSSLoaderState) => any) => useStore(cssLoaderStore, fn); + +export const useCSSLoaderStateValue = ( + key: T +): ICSSLoaderState[T] => useCSSLoaderStore((state) => state[key]); + +export const useCSSLoaderAction = ( + key: T +): ICSSLoaderState[T] => useCSSLoaderStore((state) => state[key]); + +const useCSSLoaderStoreSetter = + (key: T) => + (value: ICSSLoaderState[T]) => + cssLoaderStore.setState((state) => ({ ...state, [key]: value })); + +export const getCSSLoaderState = () => cssLoaderStore.getState(); +export const setCSSLoaderState = ( + key: T, + value: ICSSLoaderState[T] +) => cssLoaderStore.setState({ [key]: value }); diff --git a/src/backend-impl/index.ts b/src/backend-impl/index.ts new file mode 100644 index 0000000..6a8f304 --- /dev/null +++ b/src/backend-impl/index.ts @@ -0,0 +1,2 @@ +export * from "./decky-theme-store"; +export * from "./decky-backend-service"; diff --git a/src/backend-impl/readme.md b/src/backend-impl/readme.md new file mode 100644 index 0000000..e4593b1 --- /dev/null +++ b/src/backend-impl/readme.md @@ -0,0 +1,6 @@ +# CSS Loader Decky Backend + +The backend folder is meant to be treated as an 'npm package' or external lib, +it contains agnostic code that can be used on both decky and desktop. + +`backend-impl` contains re-exports of the backend code that use deck specific repositories diff --git a/src/backend/apiHelpers/fetchWrappers.ts b/src/backend/apiHelpers/fetchWrappers.ts deleted file mode 100644 index 06d515a..0000000 --- a/src/backend/apiHelpers/fetchWrappers.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { refreshToken } from "../../api"; -import { toast } from "../../python"; -import { globalState, server } from "../pythonRoot"; - -// function createHeadersObj(authToken: string | undefined, request: RequestInit | undefined) { -// const headers = new Headers(); -// if (request && request.headers) { -// for (const [key, value] of Object.entries(request.headers)) { -// headers.append(key, value); -// } -// } -// if (authToken) headers.set("Authorization", `Bearer ${authToken}`); - -// return headers; -// } - -function createHeadersObj(authToken: string | undefined, request: RequestInit | undefined) { - let headers = {}; - if (request && request.headers) { - for (const [key, value] of Object.entries(request.headers)) { - headers[key] = value; - } - } - if (authToken) headers["Authorization"] = `Bearer ${authToken}`; - - return headers; -} -export async function genericApiFetch( - fetchPath: string, - request: RequestInit | undefined = undefined, - options: { - requiresAuth?: boolean; - onError?: () => void; - customAuthToken?: string; - failSilently?: boolean; - } = { - requiresAuth: false, - customAuthToken: undefined, - onError: () => {}, - failSilently: false, - } -) { - const { - requiresAuth = false, - customAuthToken = undefined, - onError = () => {}, - failSilently = false, - } = options; - - const { apiUrl } = globalState!.getPublicState(); - async function doTheFetching(authToken: string | undefined = undefined) { - const headers = createHeadersObj(authToken, request); - try { - const deckyRes = await server!.fetchNoCors(`${apiUrl}${fetchPath}`, { - method: "GET", - // If a custom method is specified in request it will overwrite - ...request, - headers: headers, - }); - if (!deckyRes.success) { - throw new Error(`Fetch not successful!`); - } - const res = deckyRes.result; - if (res.status < 200 || res.status > 300 || !res.body) { - throw new Error(`Res not OK!, code ${res.status} - ${res.body}`); - } - // @ts-ignore - const json = JSON.parse(res.body || ""); - if (!json) { - throw new Error(`No json returned!`); - } - return json; - } catch (err) { - if (!failSilently) { - console.error(`Error fetching ${fetchPath}`, err); - } - onError(); - } - } - if (requiresAuth) { - if (customAuthToken) { - return doTheFetching(customAuthToken); - } - return refreshToken(onError).then((token) => { - if (token) { - return doTheFetching(token); - } else { - toast("Error Refreshing Token!", ""); - return; - } - }); - } else { - return doTheFetching(); - } -} diff --git a/src/backend/apiHelpers/index.ts b/src/backend/apiHelpers/index.ts deleted file mode 100644 index 8113a2a..0000000 --- a/src/backend/apiHelpers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./fetchWrappers"; -export * from "./fetchWrappers"; diff --git a/src/backend/apiHelpers/profileUploadingHelpers.ts b/src/backend/apiHelpers/profileUploadingHelpers.ts deleted file mode 100644 index 3ac19db..0000000 --- a/src/backend/apiHelpers/profileUploadingHelpers.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { refreshToken } from "../../api"; -import { TaskQueryResponse } from "../../apiTypes/SubmissionTypes"; -import { server } from "../pythonRoot"; -import { genericApiFetch } from "./fetchWrappers"; - -type BlobResponse = { - message: { - blobType: string; - downloadCount: number; - id: string; - uploaded: string; - }; -}; - -export async function publishProfile( - profileName: string, - isPublic: boolean, - description: string -): Promise { - const token = await refreshToken(); - - const deckyRes = await server!.callPluginMethod<{}, BlobResponse>("upload_theme", { - name: profileName, - base_url: "https://api.deckthemes.com", - bearer_token: token, - }); - if (!deckyRes.success) { - throw new Error(`Failed To Call Backend ${deckyRes.result.toString()}`); - } - deckyRes.result = deckyRes.result as BlobResponse; - if (!deckyRes.result?.message?.id) { - throw new Error(`No blobId returned!`); - } - const blobId = deckyRes.result.message.id; - try { - const json = await genericApiFetch( - `/submissions/css_zip`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - blob: blobId, - meta: { - imageBlobs: [], - description: description, - privateSubmission: !isPublic, - }, - }), - }, - { - requiresAuth: true, - onError: () => { - throw new Error(`Error Posting Request`); - }, - } - ); - if (!json || !json.task) throw new Error(`No task returned`); - return json.task; - } catch (error) { - throw new Error(`Failed to submit profile: ${error}`); - } -} - -export async function getTaskStatus(taskId: string): Promise { - return await genericApiFetch(`/tasks/${taskId}`, undefined, { requiresAuth: true }); -} diff --git a/src/backend/backendHelpers/index.ts b/src/backend/backendHelpers/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/backend/backendHelpers/toggleTheme.tsx b/src/backend/backendHelpers/toggleTheme.tsx deleted file mode 100644 index 7aa12e0..0000000 --- a/src/backend/backendHelpers/toggleTheme.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Dispatch, SetStateAction } from "react"; -import { Flags, Theme } from "../../ThemeTypes"; -import * as python from "../../python"; -import { OptionalDepsModalRoot } from "../../components"; -import { showModal } from "decky-frontend-lib"; -import { enableNavPatch } from "../../deckyPatches/NavPatch"; -import { NavPatchInfoModalRoot } from "../../deckyPatches/NavPatchInfoModal"; - -// rerender and setCollapsed only apply to the QAM list version of the ThemeToggle, not the one in the fullscreen 'Your Themes' modal -export async function toggleTheme( - data: Theme, - enabled: boolean, - rerender: () => void = () => {}, - setCollapsed: Dispatch> = () => {} -) { - const { selectedPreset, navPatchInstance } = python.globalState!.getPublicState(); - - // Optional Deps Themes - if (enabled && data.flags.includes(Flags.optionalDeps)) { - showModal(); - rerender && rerender(); - } else { - // Actually enabling the theme - await python.setThemeState(data.name, enabled); - await python.getInstalledThemes(); - } - - // Re-collapse menu - setCollapsed && setCollapsed(true); - - // Dependency Toast - if (data.dependencies.length > 0) { - if (enabled) { - python.toast( - `${data.display_name} enabled other themes`, - // This lists out the themes by name, but often overflowed off screen - // @ts-ignore - // `${new Intl.ListFormat().format(data.dependencies)} ${ - // data.dependencies.length > 1 ? "are" : "is" - // } required for this theme` - // This just gives the number of themes - `${ - data.dependencies.length === 1 - ? `1 other theme is required by ${data.display_name}` - : `${data.dependencies.length} other themes are required by ${data.display_name}` - }` - ); - } - if (!enabled && !data.flags.includes(Flags.dontDisableDeps)) { - python.toast( - `${data.display_name} disabled other themes`, - `${ - data.dependencies.length === 1 - ? `1 theme was originally enabled by ${data.display_name}` - : `${data.dependencies.length} themes were originally enabled by ${data.display_name}` - }` - ); - } - } - - // Nav Patch - if (enabled && data.flags.includes(Flags.navPatch) && !navPatchInstance) { - showModal(); - } - - // Preset Updating - if (!selectedPreset) return; - // Fetch this here so that the data is up to date - const { localThemeList } = python.globalState!.getPublicState(); - - // This is copied from the desktop codebase - await python.generatePresetFromThemeNames( - selectedPreset.name, - localThemeList.filter((e) => e.enabled && !e.flags.includes(Flags.isPreset)).map((e) => e.name) - ); - // Getting the new data for the preset - await python.getInstalledThemes(); -} diff --git a/src/backend/errors/call-error.ts b/src/backend/errors/call-error.ts new file mode 100644 index 0000000..392e136 --- /dev/null +++ b/src/backend/errors/call-error.ts @@ -0,0 +1,20 @@ +export class CallError extends Error { + private title: string; + private route: string; + private body: string; + + constructor(title: string, route: string, body: string) { + super(body); + this.title = title; + this.route = route; + this.body = body; + } + + getError() { + return { + title: this.title, + route: this.route, + body: this.body, + }; + } +} diff --git a/src/backend/errors/fetch-error.ts b/src/backend/errors/fetch-error.ts new file mode 100644 index 0000000..420ee58 --- /dev/null +++ b/src/backend/errors/fetch-error.ts @@ -0,0 +1,20 @@ +export class FetchError extends Error { + private title: string; + private route: string; + private body: string; + + constructor(title: string, route: string, body: string) { + super(body); + this.title = title; + this.route = route; + this.body = body; + } + + getError() { + return { + title: this.title, + route: this.route, + body: this.body, + }; + } +} diff --git a/src/backend/errors/index.ts b/src/backend/errors/index.ts new file mode 100644 index 0000000..5751e30 --- /dev/null +++ b/src/backend/errors/index.ts @@ -0,0 +1,2 @@ +export * from "./call-error"; +export * from "./fetch-error"; diff --git a/src/backend/index.ts b/src/backend/index.ts new file mode 100644 index 0000000..4f46195 --- /dev/null +++ b/src/backend/index.ts @@ -0,0 +1,4 @@ +export * from "./errors"; +export * from "./state"; +export * from "./repositories"; +export * from "./services"; diff --git a/src/backend/pythonMethods/pluginSettingsMethods.ts b/src/backend/pythonMethods/pluginSettingsMethods.ts deleted file mode 100644 index 20f35bf..0000000 --- a/src/backend/pythonMethods/pluginSettingsMethods.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { storeRead, toast } from "../../python"; -import { server, globalState } from "../pythonRoot"; -import { booleanStoreRead, stringStoreRead } from "./storeUtils"; - -export function enableServer() { - return server!.callPluginMethod("enable_server", {}); -} - -export async function getServerState() { - const deckyRes = await server!.callPluginMethod<{}, boolean>("get_server_state", {}); - if (!deckyRes.success) { - toast("Error fetching server state", deckyRes.result); - return false; - } - return deckyRes.result; -} - -export async function getWatchState() { - const deckyRes = await server!.callPluginMethod<{}, boolean>("get_watch_state", {}); - if (!deckyRes.success) { - toast("Error fetching watch state", deckyRes.result); - return false; - } - return deckyRes.result; -} - -export async function getBetaTranslationsState() { - return stringStoreRead("beta_translations"); -} - -export function toggleWatchState(bool: boolean, onlyThisSession: boolean = false) { - return server!.callPluginMethod<{ enable: boolean; only_this_session: boolean }, void>( - "toggle_watch_state", - { - enable: bool, - only_this_session: onlyThisSession, - } - ); -} - -// Todo: when i rewrite store interop, move this -export function setHiddenMotd(id: string) { - return server!.callPluginMethod<{ key: string; val: string }>("store_write", { - key: "hiddenMotd", - val: id, - }); -} -export function getHiddenMotd() { - return server!.callPluginMethod<{ key: string }, string>("store_read", { - key: "hiddenMotd", - }); -} - -export function fetchClassMappings() { - return server!.callPluginMethod<{}>("fetch_class_mappings", {}); -} diff --git a/src/backend/pythonMethods/storeUtils.ts b/src/backend/pythonMethods/storeUtils.ts deleted file mode 100644 index 585e75a..0000000 --- a/src/backend/pythonMethods/storeUtils.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { server, toast } from "../../python"; - -export async function booleanStoreRead(key: string) { - const deckyRes = await server!.callPluginMethod<{ key: string }, string>("store_read", { - key, - }); - if (!deckyRes.success) { - toast(`Error fetching ${key}`, deckyRes.result); - return false; - } - return deckyRes.result === "1" || deckyRes.result === "true"; -} - -export async function booleanStoreWrite(key: string, value: boolean) { - const deckyRes = await server!.callPluginMethod<{ key: string; val: string }>("store_write", { - key, - val: value ? "1" : "0", - }); - if (!deckyRes.success) { - toast(`Error setting ${key}`, deckyRes.result); - } -} - -export async function stringStoreRead(key: string) { - const deckyRes = await server!.callPluginMethod<{ key: string }, string>("store_read", { - key, - }); - if (!deckyRes.success) { - toast(`Error fetching ${key}`, deckyRes.result); - return ""; - } - return deckyRes.result; -} -export async function stringStoreWrite(key: string, value: string) { - const deckyRes = await server!.callPluginMethod<{ key: string; val: string }>("store_write", { - key, - val: value, - }); - if (!deckyRes.success) { - toast(`Error setting ${key}`, deckyRes.result); - } -} diff --git a/src/backend/pythonRoot.ts b/src/backend/pythonRoot.ts deleted file mode 100644 index d2edb2c..0000000 --- a/src/backend/pythonRoot.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { server } from "../python"; -import { globalState } from "../python"; - -export { server, globalState }; diff --git a/src/backend/repositories/backend-repository.ts b/src/backend/repositories/backend-repository.ts new file mode 100644 index 0000000..3bf97c3 --- /dev/null +++ b/src/backend/repositories/backend-repository.ts @@ -0,0 +1,5 @@ +export interface IBackendRepository { + call: (methodName: string, args: Args) => Promise; + toast: (title: string, body?: string) => void; + fetch: (url: string, request: RequestInit) => Promise; +} diff --git a/src/backend/repositories/index.ts b/src/backend/repositories/index.ts new file mode 100644 index 0000000..dcaa08f --- /dev/null +++ b/src/backend/repositories/index.ts @@ -0,0 +1 @@ +export * from "./backend-repository"; diff --git a/src/backend/services/backend-service.ts b/src/backend/services/backend-service.ts new file mode 100644 index 0000000..79cbb5f --- /dev/null +++ b/src/backend/services/backend-service.ts @@ -0,0 +1,91 @@ +import { Theme, ThemeError } from "../../types"; +import { IBackendRepository } from "../repositories"; + +export class Backend { + private static instance: Backend; + private static repository: IBackendRepository; + + constructor(repository: IBackendRepository) { + if (!Backend.repository) { + Backend.repository = repository; + } + } + + static getInstance(repository: IBackendRepository) { + if (!Backend.instance) { + Backend.instance = new Backend(repository); + } + return Backend.instance; + } + + async reset(): Promise { + await Backend.repository.call<[], void>("reset", []); + } + async dummyFunction(): Promise { + return await Backend.repository.call<[], boolean>("dummy_function", []); + } + async storeRead(key: string) { + return Backend.repository.call<[string], string>("store_read", [key]); + } + async storeWrite(key: string, value: string) { + await Backend.repository.call<[string, string], void>("store_write", [key, value]); + } + async getThemes() { + return await Backend.repository.call<[], Theme[]>("get_themes", []); + } + async getThemeErrors() { + return await Backend.repository.call<[], { fails: ThemeError[] }>("get_theme_errors", []); + } + async setThemeState( + themeName: string, + state: boolean, + enableDeps: boolean = true, + enableDepValues: boolean = true + ) { + return await Backend.repository.call<[string, boolean, boolean, boolean], void>( + "set_theme_state", + [themeName, state, enableDeps, enableDepValues] + ); + } + async setPatchOfTheme(themeName: string, patchName: string, value: string) { + return await Backend.repository.call<[string, string, string], void>("set_patch_of_theme", [ + themeName, + patchName, + value, + ]); + } + async setComponentOfThemePatch( + themeName: string, + patchName: string, + componentName: string, + value: string + ) { + return await Backend.repository.call<[string, string, string, string], void>( + "set_component_of_theme_patch", + [themeName, patchName, componentName, value] + ); + } + async generatePresetThemeFromThemeNames(presetName: string, dependencies: string[]) { + return await Backend.repository.call<[string, string[]], void>( + "generate_preset_theme_from_theme_names", + [presetName, dependencies] + ); + } + async fetchThemePath() { + return await Backend.repository.call<[], string>("fetch_theme_path", []); + } + async downloadThemeFromUrl(themeId: string, apiUrl: string) { + return Backend.repository.call<[string, string]>("download_theme_from_url", [themeId, apiUrl]); + } + async getBackendVersion() { + return await Backend.repository.call<[], number>("get_backend_version", []); + } + + async fetch(url: string, request: RequestInit = {}) { + return Backend.repository.fetch(url, request); + } + + toast(title: string, body?: string) { + Backend.repository.toast(title, body); + } +} diff --git a/src/backend/services/index.ts b/src/backend/services/index.ts new file mode 100644 index 0000000..c451092 --- /dev/null +++ b/src/backend/services/index.ts @@ -0,0 +1 @@ +export * from "./backend-service"; diff --git a/src/backend/state/index.ts b/src/backend/state/index.ts new file mode 100644 index 0000000..a00293e --- /dev/null +++ b/src/backend/state/index.ts @@ -0,0 +1 @@ +export * from "./theme-store"; diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts new file mode 100644 index 0000000..38b26f0 --- /dev/null +++ b/src/backend/state/theme-store.ts @@ -0,0 +1,418 @@ +import { + Flags, + FullAccountData, + MinimalCSSThemeInfo, + Motd, + Theme, + ThemeError, + UpdateStatus, +} from "../../types"; +import { createStore } from "zustand"; +import type { Backend } from "../services"; +import { FetchError } from "../errors"; + +const apiUrl = "https://api.deckthemes.com"; + +export interface CSSLoaderStateValues { + // Account Data + apiShortToken: string; + apiFullToken: string; + apiMeData: FullAccountData | undefined; + apiTokenExpireDate: number | undefined; + + // Theme Metadata + updateStatuses: UpdateStatus[]; + nextUpdateCheckTime: number; // Unix timestamp; + updateCheckTimeout: NodeJS.Timeout | undefined; + unpinnedThemes: string[]; + isWorking: boolean; + selectedPreset: Theme | undefined; + themeRootPath: string; + themeErrors: ThemeError[]; + themes: Theme[]; + + // Plugin Settings + dummyFunctionResult: boolean; + backendVersion: number; + motd: Motd | undefined; + hiddenMotdId: string; +} + +export interface CSSLoaderStateActions { + initializeStore: () => Promise; + deactivate: () => void; + toast: (message: string) => void; + reloadPlugin: () => Promise; + reloadThemes: () => Promise; + refreshToken: () => Promise; + apiFetch: ( + url: string, + request?: RequestInit, + requiresAuth?: boolean | string + ) => Promise; + getThemes: () => Promise; + changePreset: (presetName: string) => Promise; + testBackend: () => Promise; + bulkThemeUpdateCheck: () => Promise; + scheduleBulkThemeUpdateCheck: () => void; + getMotd: () => Promise; + hideMotd: () => Promise; + regenerateCurrentPreset: () => Promise; + setPatchValue: (themeName: string, patchName: string, value: string) => Promise; + setComponentValue: ( + themeName: string, + patchName: string, + componentName: string, + value: string + ) => Promise; + installTheme: (themeId: string) => Promise; + toggleTheme: ( + theme: Theme, + value: boolean, + enableDeps?: boolean, + enableDepValues?: boolean + ) => Promise; +} + +export interface ICSSLoaderState extends CSSLoaderStateValues, CSSLoaderStateActions {} + +export const createCSSLoaderStore = (backend: Backend) => + createStore((set, get) => { + async function apiFetch( + fetchPath: string, + request?: RequestInit, + // Can be a boolean (to automatically fetch token), or a string (to use a custom token) + requiresAuth?: boolean | string + ) { + try { + const { refreshToken } = get(); + let authToken = undefined; + if (requiresAuth) { + authToken = typeof requiresAuth === "string" ? requiresAuth : await refreshToken(); + } + return await backend.fetch(`${apiUrl}${fetchPath}`, { + method: "GET", + ...request, + headers: { + ...(request?.headers || {}), + Authorization: `Bearer ${authToken}`, + }, + }); + } catch (error) { + if (error instanceof FetchError) { + throw error; + } + throw new FetchError("Fetch Failed", fetchPath, "Unknown Error"); + } + } + + return { + // Account Data + apiShortToken: "", + apiFullToken: "", + apiMeData: undefined, + apiTokenExpireDate: undefined, + + // Theme Metadata + updateStatuses: [], + nextUpdateCheckTime: 0, + updateCheckTimeout: undefined, + isWorking: false, + unpinnedThemes: [], + selectedPreset: undefined, + themeRootPath: "", + themeErrors: [], + themes: [], + + // Plugin Settings + dummyFunctionResult: false, + backendVersion: 9, + motd: undefined, + hiddenMotdId: "", + unminifyModeOn: false, + navPatchInstance: undefined, + + initializeStore: async () => { + try { + const dummyFunctionResult = await backend.dummyFunction(); + set({ dummyFunctionResult }); + // If the backend doesn't work, no point in running the rest + if (!dummyFunctionResult) return; + + const backendVersion = await backend.getBackendVersion(); + set({ backendVersion }); + + const themes = (await backend.getThemes()) ?? []; + set({ themes, selectedPreset: themes.find((e) => e.flags.includes(Flags.isPreset)) }); + + const themePath = await backend.fetchThemePath(); + set({ themeRootPath: themePath }); + + const unpinnedThemesStr = await backend.storeRead("unpinnedThemes"); + const unpinnedThemes: string[] = unpinnedThemesStr ? JSON.parse(unpinnedThemesStr) : []; + const allThemeIds = themes.map((e) => e.id); + // If a theme is in the unpinned store but no longer exists, remove it from the unpinned store + let unpinnedClone = [...unpinnedThemes]; + unpinnedThemes.forEach((e) => { + if (!allThemeIds.includes(e)) { + unpinnedClone = unpinnedClone.filter((id) => id !== e); + } + }); + set({ unpinnedThemes: unpinnedClone }); + backend.storeWrite("unpinnedThemes", JSON.stringify(unpinnedClone)); + + const shortToken = await backend.storeRead("shortToken"); + set({ apiShortToken: shortToken ?? "" }); + const hiddenMotd = await backend.storeRead("hiddenMotd"); + set({ hiddenMotdId: hiddenMotd ?? "" }); + + const { bulkThemeUpdateCheck, scheduleBulkThemeUpdateCheck } = get(); + await bulkThemeUpdateCheck(); + scheduleBulkThemeUpdateCheck(); + } catch (error) {} + }, + deactivate: () => { + const { updateCheckTimeout } = get(); + if (updateCheckTimeout) clearTimeout(updateCheckTimeout); + }, + toast: (message: string) => { + backend.toast("CSS Loader", message); + }, + reloadPlugin: async () => { + try { + const { reloadThemes, bulkThemeUpdateCheck } = get(); + await reloadThemes(); + await bulkThemeUpdateCheck(); + } catch (error) {} + }, + reloadThemes: async () => { + try { + await backend.reset(); + await get().getThemes(); + } catch (error) {} + }, + refreshToken: async (): Promise => { + const { apiFullToken, apiTokenExpireDate } = get(); + if (!apiFullToken) { + return undefined; + } + if (apiTokenExpireDate === undefined || new Date().valueOf() < apiTokenExpireDate) { + return apiFullToken; + } + try { + const json = await backend.fetch<{ token: string }>(`${apiUrl}/auth/refresh_token`, { + method: "POST", + headers: { + Authorization: `Bearer ${apiFullToken}`, + }, + }); + + if (!json.token) { + throw new FetchError( + "Token Refresh Failed", + `${apiUrl}/auth/refresh_token`, + "No Token in Response" + ); + } + set({ + apiFullToken: json.token, + apiTokenExpireDate: new Date().valueOf() + 1000 * 10 * 60, + }); + return json.token; + } catch (error) { + throw error; + } + }, + apiFetch: apiFetch, + getThemes: async () => { + try { + const { fails: themeErrors } = await backend.getThemeErrors(); + set({ themeErrors }); + const themes = await backend.getThemes(); + set({ themes }); + } catch (error) {} + }, + changePreset: async (presetName: string) => { + try { + const { selectedPreset, themes } = get(); + + if (selectedPreset) { + // If you already have a preset enabled, disabling the preset disables all of it's dependencies with it. + await backend.setThemeState(selectedPreset.name, false); + } else { + // If you don't have a preset, you need to disable all currently enabled themes and THEN enable the preset + await Promise.all( + themes.filter((e) => e.enabled).map((e) => backend.setThemeState(e.name, false)) + ); + } + // Actually enabling the preset itself + if (presetName !== "None") { + await backend.setThemeState(presetName, true); + } + await get().getThemes(); + } catch (error) {} + }, + testBackend: async () => { + try { + const dummyFunctionResult = await backend.dummyFunction(); + set({ dummyFunctionResult }); + } catch (error) { + set({ dummyFunctionResult: false }); + } + }, + bulkThemeUpdateCheck: async () => { + const { themes } = get(); + + async function fetchThemeIDS(idsToQuery: string[]): Promise { + const queryStr = "?ids=" + idsToQuery.join("."); + try { + const value = await apiFetch(`/themes/ids${queryStr}`); + if (value) return value; + } catch {} + return []; + } + + let idsToQuery: string[] = themes.map((e) => e.id); + if (idsToQuery.length === 0) set({ updateStatuses: [] }); + + const themeArr = await fetchThemeIDS(idsToQuery); + + if (themeArr.length === 0) set({ updateStatuses: [] }); + + const updateStatusArr: UpdateStatus[] = themes.map((localEntry) => { + const remoteEntry = themeArr.find( + (remote) => remote.id === localEntry.id || remote.name === localEntry.id + ); + if (!remoteEntry) { + return [localEntry.id, "local", false]; + } + if (remoteEntry.version === localEntry.version) { + return [localEntry.id, "installed", remoteEntry]; + } + return [localEntry.id, "outdated", remoteEntry]; + }); + set({ updateStatuses: updateStatusArr }); + }, + scheduleBulkThemeUpdateCheck: () => { + function recursiveCheck() { + const timeout = setTimeout(async () => { + // Putting this in the function as im not sure the value would update otherwise + const { nextUpdateCheckTime } = get(); + if (!(new Date().valueOf() > nextUpdateCheckTime)) { + recursiveCheck(); + return; + } + // After testing, it appears that, if there is no wifi, bulkThemeUpdateCheck returns an empty array, this is okay, the try catch is just for extra safety + try { + const { bulkThemeUpdateCheck } = get(); + await bulkThemeUpdateCheck(); + + set({ nextUpdateCheckTime: new Date().valueOf() + 24 * 60 * 60 * 1000 }); + } catch (err) { + console.log("Error Checking For Theme Updates", err); + } + recursiveCheck(); + }, 5 * 60 * 1000); + set({ updateCheckTimeout: timeout }); + } + set({ nextUpdateCheckTime: new Date().valueOf() + 24 * 60 * 60 * 1000 }); + recursiveCheck(); + }, + getMotd: async () => { + try { + const value = await apiFetch("/motd"); + if (value) { + set({ motd: value }); + } + } catch (error) {} + }, + hideMotd: async () => { + try { + const { motd } = get(); + if (!motd) return; + await backend.storeWrite("hiddenMotd", motd.id); + set({ hiddenMotdId: motd.id }); + } catch (error) {} + }, + regenerateCurrentPreset: async () => { + try { + const { selectedPreset, themes } = get(); + if (!selectedPreset) return; + await backend.generatePresetThemeFromThemeNames( + selectedPreset.name, + // This will handle if you just toggles/un-toggled a theme, as well as if you changed a patch/component + themes.filter((e) => e.enabled && !e.flags.includes(Flags.isPreset)).map((e) => e.name) + ); + } catch (error) {} + }, + setPatchValue: async (themeName: string, patchName: string, value: string) => { + try { + await backend.setPatchOfTheme(themeName, patchName, value); + const { selectedPreset, regenerateCurrentPreset } = get(); + if (selectedPreset && selectedPreset.dependencies.includes(themeName)) { + await regenerateCurrentPreset(); + } + } catch (error) {} + }, + setComponentValue: async ( + themeName: string, + patchName: string, + componentName: string, + value: string + ) => { + try { + await backend.setComponentOfThemePatch(themeName, patchName, componentName, value); + const { selectedPreset, regenerateCurrentPreset, getThemes } = get(); + if (selectedPreset && selectedPreset.dependencies.includes(themeName)) { + await regenerateCurrentPreset(); + } + // TODO: POTENTIALLY NOT NEEDED + await getThemes(); + } catch (error) {} + }, + installTheme: async (themeId: string) => { + try { + await backend.downloadThemeFromUrl(themeId, apiUrl); + const { updateStatuses, reloadThemes } = get(); + await reloadThemes(); + const updateStatusesClone = updateStatuses.filter((e) => e[0] !== themeId); + updateStatusesClone.push([themeId, "installed", false]); + set({ updateStatuses: updateStatusesClone }); + } catch (error) {} + }, + toggleTheme: async ( + theme: Theme, + value: boolean, + enableDeps?: boolean, + enableDepValues?: boolean + ) => { + try { + await backend.setThemeState(theme.name, value, enableDeps, enableDepValues); + await get().getThemes(); + + if (!enableDeps && theme.dependencies.length > 0) { + if (value) { + backend.toast( + `${theme.display_name} enabled other themes`, + `${theme.dependencies.length} other theme${ + theme.dependencies.length === 1 ? " is" : "s are" + } required by ${theme.display_name}` + ); + } else if (!theme.flags.includes(Flags.dontDisableDeps)) { + backend.toast( + `${theme.display_name} disabled other themes`, + `${theme.dependencies.length} other theme${ + theme.dependencies.length === 1 ? " was" : "s were" + } originally enabled by ${theme.display_name}` + ); + } + } + const { selectedPreset } = get(); + if (selectedPreset) { + await get().regenerateCurrentPreset(); + await get().getThemes(); + } + } catch (error) {} + }, + }; + }); diff --git a/src/components/DepsOptionSelector.tsx b/src/components/DepsOptionSelector.tsx deleted file mode 100644 index 5e05bb8..0000000 --- a/src/components/DepsOptionSelector.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { DialogButton, Focusable } from "decky-frontend-lib"; -import * as python from "../python"; - -export function DepsOptionSelector({ - themeName, - closeModal = undefined, -}: { - themeName: string; - closeModal?: any; -}) { - function enableTheme(enableDeps: boolean = true, enableDepValues: boolean = true) { - python.resolve(python.setThemeState(themeName, true, enableDeps, enableDepValues), () => { - python.getInstalledThemes(); - closeModal && closeModal(); - }); - } - return ( - - enableTheme(true, true)} style={{ margin: "0 10px" }}> - Enable with configuration {"(Recommended)"} - - enableTheme(true, false)} style={{ margin: "0 10px" }}> - Enable without configuration - - enableTheme(false, false)} style={{ margin: "0 10px" }}> - Enable only this theme - - - ); -} diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx deleted file mode 100644 index d3f8889..0000000 --- a/src/components/Loading.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ImSpinner5 } from "react-icons/im"; - -export function Loading() { - return ( -
- - - Loading -
- ); -} diff --git a/src/components/Modals/AuthorViewModal.tsx b/src/components/Modals/AuthorViewModal.tsx deleted file mode 100644 index 7f2bf68..0000000 --- a/src/components/Modals/AuthorViewModal.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import * as python from "../../python"; -import { CssLoaderContextProvider, useCssLoaderState } from "../../state"; -import { Focusable, ModalRoot } from "decky-frontend-lib"; -import { genericGET } from "../../api"; -import { PartialCSSThemeInfo, ThemeQueryResponse, UserInfo } from "../../apiTypes"; -import { ImSpinner5 } from "react-icons/im"; -import { VariableSizeCard } from "../ThemeManager"; -import { ThemeBrowserCardStyles } from "../Styles"; -import { SupporterIcon } from "../SupporterIcon"; - -export function AuthorViewModalRoot({ - closeModal, - authorData, -}: { - closeModal?: any; - authorData: UserInfo; -}) { - return ( - <> - - {/* @ts-ignore */} - - - - - - ); -} - -function AuthorViewModal({ - authorData, - closeModal, -}: { - authorData: UserInfo; - closeModal: () => {}; -}) { - const { setGlobalState } = useCssLoaderState(); - - const [loaded, setLoaded] = useState(false); - const [themes, setThemes] = useState([]); - - const firstThemeRef = useRef(); - - async function fetchThemeData() { - const data: ThemeQueryResponse = await genericGET( - `/users/${authorData.id}/themes?page=1&perPage=50&filters=CSS&order=Most Downloaded` - ); - if (data?.total && data.total > 0) { - setThemes(data.items); - setLoaded(true); - } - } - useEffect(() => { - fetchThemeData(); - }, []); - - useEffect(() => { - if (firstThemeRef?.current) { - setTimeout(() => { - firstThemeRef?.current?.focus(); - }, 10); - } - }, [loaded]); - - return ( - - {loaded ? ( - <> - - -
- - {authorData.username} -
- -
-
- - {themes.map((e, i) => { - return ( - { - setGlobalState("currentExpandedTheme", e); - closeModal(); - }} - refPassthrough={i === 0 ? firstThemeRef : null} - cols={4} - data={e} - /> - ); - })} - - - ) : ( - <> - -
- - Loading -
- - )} -
- ); -} diff --git a/src/components/Modals/CreatePresetModal.tsx b/src/components/Modals/CreatePresetModal.tsx deleted file mode 100644 index 3731880..0000000 --- a/src/components/Modals/CreatePresetModal.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ConfirmModal, TextField } from "decky-frontend-lib"; -import { useState } from "react"; -import * as python from "../../python"; -import { CssLoaderContextProvider, useCssLoaderState } from "../../state"; - -export function CreatePresetModalRoot({ closeModal }: { closeModal: any }) { - return ( - <> - {/* @ts-ignore */} - - - - - ); -} - -function CreatePresetModal({ closeModal }: { closeModal: () => void }) { - const { localThemeList, selectedPreset } = useCssLoaderState(); - const [presetName, setPresetName] = useState(""); - const enabledNumber = localThemeList.filter((e) => e.enabled).length; - - return ( - { - if (presetName.length === 0) { - python.toast("No Name!", "Please add a name to your profile."); - return; - } - // TODO: Potentially dont need 2 reloads here, not entirely sure - await python.generatePreset(presetName); - await python.reloadBackend(); - if (selectedPreset) { - await python.setThemeState(selectedPreset?.name, false); - } - await python.setThemeState(presetName + ".profile", true); - await python.getInstalledThemes(); - closeModal(); - }} - > -
- { - setPresetName(e.target.value); - }} - /> - - ); -} diff --git a/src/components/Modals/DeleteConfirmationModal.tsx b/src/components/Modals/DeleteConfirmationModal.tsx deleted file mode 100644 index cd2e48e..0000000 --- a/src/components/Modals/DeleteConfirmationModal.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { CssLoaderContextProvider } from "../../state"; -import * as python from "../../python"; -import { ConfirmModal, ModalRoot } from "decky-frontend-lib"; - -export function DeleteConfirmationModalRoot({ - themesToBeDeleted, - closeModal, - leaveDeleteMode, -}: { - themesToBeDeleted: string[]; - closeModal?: any; - leaveDeleteMode?: () => void; -}) { - async function deleteThemes() { - for (let i = 0; i < themesToBeDeleted.length; i++) { - await python.deleteTheme(themesToBeDeleted[i]); - } - await python.getInstalledThemes(); - leaveDeleteMode && leaveDeleteMode(); - closeModal(); - } - - return ( - - {/* @ts-ignore */} - - - - - ); -} - -function DeleteConfirmationModal({ themesToBeDeleted }: { themesToBeDeleted: string[] }) { - return ( -
- Are you sure you want to delete{" "} - {themesToBeDeleted.length === 1 ? `this theme` : `these ${themesToBeDeleted.length} themes`}? -
- ); -} diff --git a/src/components/Modals/PremiumFeatureModal.tsx b/src/components/Modals/PremiumFeatureModal.tsx deleted file mode 100644 index ddb0e2e..0000000 --- a/src/components/Modals/PremiumFeatureModal.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Focusable, ModalRoot } from "decky-frontend-lib"; - -export function PremiumFeatureModal({ closeModal, blurb }: { closeModal?: any; blurb: string }) { - return ( - - - Premium Feature -

{blurb}

- - To support DeckThemes and unlock premium features, visit https://patreon.com/deckthemes - -
-
- ); -} diff --git a/src/components/Modals/ThemeSettingsModal/ThemeSettingsModal.tsx b/src/components/Modals/ThemeSettingsModal/ThemeSettingsModal.tsx deleted file mode 100644 index 7fe1ef7..0000000 --- a/src/components/Modals/ThemeSettingsModal/ThemeSettingsModal.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { useState, useEffect, useMemo } from "react"; - -import { DialogButton, Focusable, ModalRoot, Toggle } from "decky-frontend-lib"; -import { CssLoaderContextProvider, useCssLoaderState } from "../../../state"; -import { Theme } from "../../../ThemeTypes"; -import { globalState } from "../../../python"; -import { ThemeSettingsModalButtons } from "./ThemeSettingsModalButtons"; -import { toggleTheme } from "../../../backend/backendHelpers/toggleTheme"; -import { ThemePatch } from "../../ThemePatch"; -export function ThemeSettingsModalRoot({ - closeModal, - selectedTheme, -}: { - closeModal?: any; - selectedTheme: string; -}) { - return ( - - {/* @ts-ignore */} - - - - - ); -} - -function ThemeSettingsModal({ - closeModal, - selectedTheme, -}: { - closeModal: any; - selectedTheme: string; -}) { - const { localThemeList, updateStatuses } = useCssLoaderState(); - const [themeData, setThemeData] = useState( - localThemeList.find((e) => e.id === selectedTheme) - ); - - useEffect(() => { - setThemeData(localThemeList.find((e) => e.id === selectedTheme)); - return () => { - setThemeData(undefined); - }; - }, [selectedTheme, localThemeList]); - - return ( - <> - - - {themeData ? ( - <> - -
- {themeData.display_name} - - {themeData.version} | {themeData.author} - -
- { - toggleTheme(themeData, checked); - }} - /> -
- {themeData.enabled && themeData.patches.length > 0 && ( - <> - - {themeData.patches.map((x, i, arr) => ( - - ))} - - - )} - - ) : ( - No Theme Data - )} - - { - closeModal(); - }} - > - Close - - {themeData && } - -
- - ); -} diff --git a/src/components/Modals/ThemeSettingsModal/ThemeSettingsModalButtons.tsx b/src/components/Modals/ThemeSettingsModal/ThemeSettingsModalButtons.tsx deleted file mode 100644 index c123297..0000000 --- a/src/components/Modals/ThemeSettingsModal/ThemeSettingsModalButtons.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { DialogButton, Focusable, showModal } from "decky-frontend-lib"; -import { LocalThemeStatus, Theme } from "../../../ThemeTypes"; -import { FaDownload, FaEye, FaEyeSlash, FaRegStar, FaStar, FaTrashAlt } from "react-icons/fa"; -import { DeleteConfirmationModalRoot } from "../DeleteConfirmationModal"; -import { useCssLoaderState } from "../../../state"; -import * as python from "../../../python"; -import { - genericGET, - logInWithShortToken, - refreshToken, - toggleStar as apiToggleStar, - installTheme, -} from "../../../api"; -import { useState, useEffect } from "react"; - -export function ThemeSettingsModalButtons({ - themeData, - closeModal, -}: { - themeData: Theme; - closeModal: () => void; -}) { - const { unpinnedThemes, apiShortToken, apiFullToken, updateStatuses, setGlobalState } = - useCssLoaderState(); - const isPinned = !unpinnedThemes.includes(themeData.id); - const [starFetchLoaded, setStarFetchLoaded] = useState(false); - const [isStarred, setStarred] = useState(false); - const [blurButtons, setBlurButtons] = useState(false); - - const [updateStatus, setUpdateStatus] = useState("installed"); - useEffect(() => { - if (!themeData) return; - const themeArrPlace = updateStatuses.find((f) => f[0] === themeData.id); - if (themeArrPlace) { - setUpdateStatus(themeArrPlace[1]); - } - }, [themeData]); - - async function toggleStar() { - if (apiFullToken) { - setBlurButtons(true); - const newToken = await refreshToken(); - if (themeData && newToken) { - apiToggleStar(themeData.id, isStarred, newToken).then((bool) => { - if (bool) { - setStarred((cur) => !cur); - setBlurButtons(false); - } - }); - } - } else { - python.toast("Not Logged In!", "You can only star themes if logged in."); - } - } - - async function getStarredStatus() { - if (themeData && apiShortToken) { - if (!apiFullToken) { - await logInWithShortToken(); - } - const data = (await genericGET(`/users/me/stars/${themeData.id}`, true, undefined)) as { - starred: boolean; - }; - if (data) { - setStarFetchLoaded(true); - setStarred(data.starred); - } - } - } - useEffect(() => { - getStarredStatus(); - }, []); - - return ( - <> - - {updateStatus === "outdated" && ( - { - await installTheme(themeData.id); - // This just updates the updateStatuses arr to know that this theme now is up to date, no need to re-fetch the API to know that - setGlobalState( - "updateStatuses", - updateStatuses.map((e) => - e[0] === themeData.id ? [themeData.id, "installed", false] : e - ) - ); - }} - > - - Update - - )} - { - if (isPinned) { - python.unpinTheme(themeData.id); - } else { - python.pinTheme(themeData.id); - } - }} - > - {isPinned ? ( - - ) : ( - - )} - - {starFetchLoaded && ( - - {isStarred ? : } - - )} - { - showModal( - - ); - }} - > - - - - - ); -} diff --git a/src/components/Modals/ThemeSettingsModal/index.ts b/src/components/Modals/ThemeSettingsModal/index.ts deleted file mode 100644 index ae9aacf..0000000 --- a/src/components/Modals/ThemeSettingsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ThemeSettingsModal"; diff --git a/src/components/Modals/UploadProfileModal.tsx b/src/components/Modals/UploadProfileModal.tsx deleted file mode 100644 index b327198..0000000 --- a/src/components/Modals/UploadProfileModal.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { CssLoaderContextProvider, useCssLoaderState } from "../../state"; -import * as python from "../../python"; -import { - ButtonItem, - ConfirmModal, - DialogButton, - DropdownItem, - Focusable, - ModalRoot, - TextField, - ToggleField, -} from "decky-frontend-lib"; -import { useMemo, useState } from "react"; -import { Flags } from "../../ThemeTypes"; -import { publishProfile } from "../../backend/apiHelpers/profileUploadingHelpers"; -import { TaskStatus } from "../TaskStatus"; - -export function UploadProfileModalRoot({ - closeModal, - onUploadFinish, -}: { - closeModal?: any; - onUploadFinish: () => void; -}) { - return ( - - {/* @ts-ignore */} - - - - - ); -} - -function UploadProfileModal({ - closeModal, - onUploadFinish, -}: { - closeModal: () => void; - onUploadFinish: () => void; -}) { - const { localThemeList } = useCssLoaderState(); - const [selectedProfile, setProfile] = useState( - localThemeList.find((e) => e.flags.includes(Flags.isPreset))?.id - ); - const profiles = useMemo(() => { - return localThemeList.filter((e) => e.flags.includes(Flags.isPreset)); - }, [localThemeList]); - const eligibleProfiles = useMemo(() => { - return profiles; - }, [profiles]); - - const [isPublic, setPublic] = useState(false); - const [description, setDescription] = useState(""); - - const [uploadStatus, setUploadStatus] = useState< - "idle" | "submitting" | "taskStatus" | "completed" | "error" - >("idle"); - const [taskId, setTaskId] = useState(undefined); - const [errorData, setErrorData] = useState(undefined); - - async function onUpload() { - if (!selectedProfile) return; - try { - setUploadStatus("submitting"); - const taskId = await publishProfile( - localThemeList.find((e) => e.id === selectedProfile)!.name, - isPublic, - description - ); - setUploadStatus("taskStatus"); - setTaskId(taskId); - } catch (error) { - if (typeof error === "string") setErrorData(error); - setUploadStatus("error"); - } - } - - function onTaskFinish() { - closeModal(); - onUploadFinish(); - } - - if (uploadStatus !== "idle") { - return ( - - ); - } - - return ( - - - Upload Profile - ({ data: e.id, label: e.display_name }))} - onChange={(chosen) => { - setProfile(chosen.data); - }} - label="Profile To Upload" - /> - - setDescription(e.target.value)} - /> - - - Upload - - - Cancel - - - - ); -} diff --git a/src/components/Modals/index.ts b/src/components/Modals/index.ts deleted file mode 100644 index 3b796f0..0000000 --- a/src/components/Modals/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./CreatePresetModal"; -export * from "./ThemeSettingsModal"; -export * from "./UploadProfileModal"; diff --git a/src/components/OptionalDepsModal.tsx b/src/components/OptionalDepsModal.tsx deleted file mode 100644 index 3518822..0000000 --- a/src/components/OptionalDepsModal.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { ModalRoot } from "decky-frontend-lib"; -import { DepsOptionSelector } from "./DepsOptionSelector"; -import { Theme } from "../ThemeTypes"; -export function OptionalDepsModalRoot({ - themeData, - closeModal, -}: { - themeData: Theme; - closeModal?: any; -}) { - return ( - - - - ); -} - -export function OptionalDepsModal({ - themeData, - closeModal, -}: { - themeData: Theme; - closeModal: any; -}) { - return ( - <> -

- Enable dependencies for {themeData.name}? -

- - {themeData.name} enables optional themes to enhance this theme. Disabling these may break - the theme, or make the theme look completely different. Specific optional themes can be - configured and or enabled/disabled anytime via the Quick Access Menu. - - - Enable without configuration will enable optional themes but not overwrite their - configuration, and Enable only this theme will not enable any optional themes. - - - - - ); -} diff --git a/src/components/PatchComponent.tsx b/src/components/PatchComponent.tsx deleted file mode 100644 index 566b1c3..0000000 --- a/src/components/PatchComponent.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { VFC } from "react"; - -import * as python from "../python"; - -import Color from "color"; -import { showModal, ButtonItem, PanelSectionRow } from "decky-frontend-lib"; - -import { ColorPickerModal } from "decky-frontend-lib"; -import { ThemePatchComponent } from "../ThemeTypes"; -import { FaFolder } from "react-icons/fa"; -import { useCssLoaderState } from "../state"; - -export const PatchComponent: VFC<{ - data: ThemePatchComponent; - selectedLabel: string; - themeName: string; - patchName: string; - bottomSeparatorValue: "standard" | "none"; -}> = ({ data, selectedLabel, themeName, patchName, bottomSeparatorValue }) => { - const { selectedPreset } = useCssLoaderState(); - if (selectedLabel === data.on) { - // The only value that changes from component to component is the value, so this can just be re-used - async function setComponentAndReload(value: string) { - await python.setComponentOfThemePatch(themeName, patchName, data.name, value); - if (selectedPreset && selectedPreset.dependencies.includes(themeName)) { - python.generatePresetFromThemeNames(selectedPreset.name, selectedPreset.dependencies); - } - python.getInstalledThemes(); - } - switch (data.type) { - case "image-picker": - // This makes things compatible with people using HoloISO or who don't have the user /deck/ - function getRootPath() { - python.resolve(python.fetchThemePath(), (path: string) => pickImage(path)); - } - // These have to - async function pickImage(rootPath: string) { - const res = await python.openFilePicker(rootPath); - if (!res.path.includes(rootPath)) { - python.toast("Invalid File", "Images must be within themes folder"); - return; - } - if (!/\.(jpg|jpeg|png|webp|avif|gif|svg)$/.test(res.path)) { - python.toast("Invalid File", "Must be an image file"); - return; - } - const relativePath = res.path.split(`${rootPath}/`)[1]; - setComponentAndReload(relativePath); - } - return ( - - getRootPath()} - layout="below" - > -
- Open {data.name} -
- -
-
-
-
- ); - case "color-picker": - const colorObj = Color(data.value).hsl(); - const curColorHSLArray = colorObj.array(); - - return ( - <> - - - showModal( - // @ts-ignore -- showModal passes the closeModal function to this, but for some reason it's giving me a typescript error because I didn't explicitly pass it myself - { - setComponentAndReload(HSLString); - }} - defaultH={curColorHSLArray[0]} - defaultS={curColorHSLArray[1]} - defaultL={curColorHSLArray[2]} - defaultA={curColorHSLArray[3] ?? 1} - title={data.name} - /> - ) - } - layout={"below"} - > -
- Open {data.name} -
-
-
-
- - - - ); - } - } - return null; -}; diff --git a/src/components/QAMTab/PresetSelectionDropdown.tsx b/src/components/QAMTab/PresetSelectionDropdown.tsx deleted file mode 100644 index d1528fd..0000000 --- a/src/components/QAMTab/PresetSelectionDropdown.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { DropdownItem, PanelSectionRow, showModal } from "decky-frontend-lib"; -import { useCssLoaderState } from "../../state"; -import { Flags } from "../../ThemeTypes"; -import { useMemo } from "react"; -import { changePreset, getInstalledThemes } from "../../python"; -import { CreatePresetModalRoot } from "../Modals/CreatePresetModal"; -import { FiPlusCircle } from "react-icons/fi"; -import { useRerender } from "../../hooks"; - -export function PresetSelectionDropdown() { - const { localThemeList, selectedPreset } = useCssLoaderState(); - const presets = useMemo( - () => localThemeList.filter((e) => e.flags.includes(Flags.isPreset)), - [localThemeList] - ); - const [render, rerender] = useRerender(); - return ( - <> - {render && ( - - e.enabled && e.flags.includes(Flags.isPreset)).length > 1 - ? "Invalid State" - : selectedPreset?.name || "None" - } - rgOptions={[ - ...(localThemeList.filter((e) => e.enabled && e.flags.includes(Flags.isPreset)) - .length > 1 - ? [{ data: "Invalid State", label: "Invalid State" }] - : []), - { data: "None", label: "None" }, - ...presets.map((e) => ({ label: e.display_name, data: e.name })), - // This is a jank way of only adding it if creatingNewProfile = false - { - data: "New Profile", - label: ( -
- - New Profile -
- ), - }, - ]} - onChange={async ({ data }) => { - if (data === "New Profile") { - showModal( - // @ts-ignore - - ); - rerender(); - return; - } - await changePreset(data, localThemeList); - getInstalledThemes(); - }} - /> -
- )} - - ); -} diff --git a/src/components/QAMTab/QAMThemeToggleList.tsx b/src/components/QAMTab/QAMThemeToggleList.tsx deleted file mode 100644 index ed0d608..0000000 --- a/src/components/QAMTab/QAMThemeToggleList.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { DialogButton, Focusable } from "decky-frontend-lib"; -import { useCssLoaderState } from "../../state"; -import { ThemeToggle } from "../ThemeToggle"; -import { Flags } from "../../ThemeTypes"; -import { ThemeErrorCard } from "../ThemeErrorCard"; -import { BsArrowDown } from "react-icons/bs"; -import { FaEyeSlash } from "react-icons/fa"; -import { publishProfile } from "../../backend/apiHelpers/profileUploadingHelpers"; - -export function QAMThemeToggleList() { - const { localThemeList, unpinnedThemes } = useCssLoaderState(); - - if (localThemeList.length === 0) { - return ( - <> - You have no themes installed. Get started by selecting the download icon above! - - ); - } - - return ( - <> - {/* This styles the collapse buttons, putting it here just means it only needs to be rendered once instead of like 20 times */} - - - <> - {localThemeList - .filter((e) => !unpinnedThemes.includes(e.id) && !e.flags.includes(Flags.isPreset)) - .map((x) => ( - - ))} - - - {unpinnedThemes.length > 0 && ( -
- -
- {unpinnedThemes.length} theme{unpinnedThemes.length > 1 ? "s are" : "is"} hidden. -
-
- )} - - ); -} diff --git a/src/components/QAMTab/index.ts b/src/components/QAMTab/index.ts deleted file mode 100644 index c63a957..0000000 --- a/src/components/QAMTab/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./QAMThemeToggleList"; -export * from "./PresetSelectionDropdown"; -export * from "./MOTDDisplay"; diff --git a/src/components/Styles/ExpandedViewStyles.tsx b/src/components/Styles/ExpandedViewStyles.tsx deleted file mode 100644 index 52779b8..0000000 --- a/src/components/Styles/ExpandedViewStyles.tsx +++ /dev/null @@ -1,214 +0,0 @@ -export function ExpandedViewStyles({ - gapBetweenCarouselAndImage, - imageAreaPadding, - imageAreaWidth, - selectedImageHeight, - selectedImageWidth, - imageCarouselEntryHeight, - imageCarouselEntryWidth, -}: { - gapBetweenCarouselAndImage: number; - imageAreaPadding: number; - imageAreaWidth: number; - selectedImageHeight: number; - selectedImageWidth: number; - imageCarouselEntryHeight: number; - imageCarouselEntryWidth: number; -}) { - return ( - - ); -} diff --git a/src/components/Styles/ThemeBrowserCardStyles.tsx b/src/components/Styles/ThemeBrowserCardStyles.tsx deleted file mode 100644 index 00d1f8c..0000000 --- a/src/components/Styles/ThemeBrowserCardStyles.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useCssLoaderState } from "../../state"; - -const width = { 3: "260px", 4: "195px", 4.5: "183.33px", 5: "152px" }; -const imgheightFactory = (size: number) => (Number(width[size].slice(0, -2)) / 16) * 10 + "px"; -const fontsize = { 3: "1em", 4: "0.75em", 4.5: "0.7em", 5: "0.5em" }; -const bubblesize = { 3: "40px", 4: "30px", 4.5: "25px", 5: "20px" }; - -export function ThemeBrowserCardStyles({ customCardSize }: { customCardSize?: number }) { - const { browserCardSize } = customCardSize - ? { browserCardSize: customCardSize } - : useCssLoaderState(); - - return ( - - ); -} diff --git a/src/components/Styles/index.ts b/src/components/Styles/index.ts deleted file mode 100644 index 3d66ae3..0000000 --- a/src/components/Styles/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./ExpandedViewStyles"; -export * from "./ThemeBrowserCardStyles"; diff --git a/src/components/SupporterIcon.tsx b/src/components/SupporterIcon.tsx deleted file mode 100644 index 0efd4b0..0000000 --- a/src/components/SupporterIcon.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { RiMedalFill } from "react-icons/ri"; -import { UserInfo } from "../apiTypes/CSSThemeTypes"; - -export function SupporterIcon({ author }: { author: UserInfo }) { - const randId = Math.trunc(Math.random() * 69420); - return ( - <> - {author?.premiumTier && author?.premiumTier !== "None" && ( -
- - - - - - - - {`Tier ${author?.premiumTier?.slice(-1)} Patreon Supporter`} -
- )} - - ); -} diff --git a/src/components/TaskStatus.tsx b/src/components/TaskStatus.tsx deleted file mode 100644 index 89f6788..0000000 --- a/src/components/TaskStatus.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { TaskQueryResponse } from "../apiTypes/SubmissionTypes"; -import { getTaskStatus } from "../backend/apiHelpers/profileUploadingHelpers"; -import { DialogButton, Focusable } from "decky-frontend-lib"; -import { Loading } from "./Loading"; - -export function TaskStatus({ - uploadStatus, - setUploadStatus, - errorData, - taskId, - onFinish, -}: { - errorData: string | undefined; - uploadStatus: "submitting" | "taskStatus" | "completed" | "error"; - setUploadStatus: (status: "submitting" | "taskStatus" | "completed" | "error") => void; - taskId: string | undefined; - onFinish: () => void; -}) { - const [apiStatus, setStatus] = useState(null); - - async function getStatus() { - if (taskId) { - const data = await getTaskStatus(taskId); - setStatus(data); - } - } - useEffect(() => { - if (!apiStatus) return; - - if (apiStatus.completed) { - setUploadStatus("completed"); - return; - } - - setTimeout(() => { - getStatus(); - }, 1000); - }, [apiStatus]); - - useEffect(() => { - getStatus(); - }, [taskId]); - - if (["submitting", "taskStatus"].includes(uploadStatus)) { - return ( -
- -
- ); - } - - if (uploadStatus === "error") { - return ( - - Error Submitting Theme! - {errorData} - - ); - } - - return ; -} - -// This was split out essentially just so the ref works -function TaskStatusFinishedDisplay({ - apiStatus, - onFinish, -}: { - apiStatus: TaskQueryResponse | null; - onFinish: () => void; -}) { - const closeButtonRef = useRef(null); - useEffect(() => { - closeButtonRef?.current && closeButtonRef.current.focus(); - }, [closeButtonRef]); - - return ( - <> - - - Profile Upload {apiStatus?.success ? "Succeeded" : "Failed"} - - {apiStatus?.status} - - - Close - - - - - ); -} diff --git a/src/components/ThemeErrorCard.tsx b/src/components/ThemeErrorCard.tsx deleted file mode 100644 index 48e5fad..0000000 --- a/src/components/ThemeErrorCard.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Focusable, PanelSectionRow } from "decky-frontend-lib"; -import { ThemeError } from "../ThemeTypes"; - -export function ThemeErrorCard({ errorData }: { errorData: ThemeError }) { - return ( - {}} - style={{ - width: "100%", - margin: 0, - padding: 0, - }} - > -
- - {errorData[0]} - - {errorData[1]} -
-
- ); -} diff --git a/src/components/ThemeManager/BrowserItemCard.tsx b/src/components/ThemeManager/BrowserItemCard.tsx deleted file mode 100644 index e9c0ff8..0000000 --- a/src/components/ThemeManager/BrowserItemCard.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { FC, ReactNode } from "react"; -import { useCssLoaderState } from "../../state"; -import { Theme } from "../../ThemeTypes"; -import { Focusable, Navigation } from "decky-frontend-lib"; -import { AiOutlineDownload } from "react-icons/ai"; -import { PartialCSSThemeInfo, ThemeQueryRequest } from "../../apiTypes"; -import { FaBullseye, FaDownload, FaStar } from "react-icons/fa"; -import { shortenNumber } from "../../logic/numbers"; - -const cardWidth = { - 5: 152, - 4.5: 183.33, - 4: 195, - 3: 260, -}; - -export const VariableSizeCard: FC<{ - data: PartialCSSThemeInfo; - cols: number; - searchOpts?: ThemeQueryRequest; - prevSearchOptsVarName?: string; - refPassthrough?: any; - onClick?: () => void; - CustomBubbleIcon?: ReactNode; -}> = ({ - data: e, - cols: size, - refPassthrough = undefined, - searchOpts, - prevSearchOptsVarName, - onClick, - CustomBubbleIcon, -}) => { - const { localThemeList, apiUrl, setGlobalState } = useCssLoaderState(); - function checkIfThemeInstalled(themeObj: PartialCSSThemeInfo) { - const filteredArr: Theme[] = localThemeList.filter( - (e: Theme) => e.name === themeObj.name && e.author === themeObj.specifiedAuthor - ); - if (filteredArr.length > 0) { - if (filteredArr[0].version === themeObj.version) { - return "installed"; - } else { - return "outdated"; - } - } else { - return "uninstalled"; - } - } - function imageURLCreator(): string { - if (e?.images[0]?.id && e.images[0].id !== "MISSING") { - return `url(${apiUrl}/blobs/${e?.images[0].id})`; - } else { - return `url(https://upload.wikimedia.org/wikipedia/commons/thumb/8/81/Steam_Deck_logo_%28blue_background%29.svg/2048px-Steam_Deck_logo_%28blue_background%29.svg.png)`; - } - } - - const installStatus = checkIfThemeInstalled(e); - - return ( - <> -
- {installStatus === "outdated" && ( -
- -
- )} - {CustomBubbleIcon && ( -
{CustomBubbleIcon}
- )} - { - if (onClick) { - onClick(); - return; - } - if (searchOpts && prevSearchOptsVarName) { - setGlobalState(prevSearchOptsVarName, searchOpts); - } - setGlobalState("currentExpandedTheme", e); - Navigation.Navigate("/cssloader/expanded-view"); - }} - > -
- -
-
-
- - {shortenNumber(e.download.downloadCount) ?? e.download.downloadCount} -
-
- - {shortenNumber(e.starCount) ?? e.starCount} -
-
- - {e.target} -
-
-
-
- {e.displayName} - - {e.version} - Last Updated {new Date(e.updated).toLocaleDateString()} - - By {e.specifiedAuthor} -
- -
- - ); -}; diff --git a/src/components/ThemeManager/BrowserSearchFields.tsx b/src/components/ThemeManager/BrowserSearchFields.tsx deleted file mode 100644 index ede5102..0000000 --- a/src/components/ThemeManager/BrowserSearchFields.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { - DialogButton, - Dropdown, - DropdownOption, - Focusable, - gamepadDialogClasses, - gamepadSliderClasses, - PanelSectionRow, - SliderField, - TextField, -} from "decky-frontend-lib"; -import { useEffect, useMemo, memo } from "react"; -import { TiRefreshOutline } from "react-icons/ti"; -import { FaRotate } from "react-icons/fa6"; -import { ThemeQueryRequest } from "../../apiTypes"; -import { genericGET } from "../../api"; -import { useCssLoaderState } from "../../state"; -import { FilterDropdownCustomLabel } from "./FilterDropdownCustomLabel"; - -export function BrowserSearchFields({ - searchOpts, - searchOptsVarName, - prevSearchOptsVarName, - unformattedFilters, - unformattedFiltersVarName, - onReload, - requiresAuth = false, - getTargetsPath, -}: { - searchOpts: ThemeQueryRequest; - searchOptsVarName: string; - prevSearchOptsVarName: string; - unformattedFilters: { filters: string[]; order: string[] }; - unformattedFiltersVarName: string; - getTargetsPath: string; - requiresAuth?: boolean; - onReload: () => void; -}) { - const { browserCardSize, setGlobalState } = useCssLoaderState(); - - async function getThemeTargets() { - genericGET(`${getTargetsPath}`, requiresAuth).then((data) => { - if (data?.filters) { - setGlobalState(unformattedFiltersVarName, { - filters: data.filters, - order: data.order, - }); - } - }); - } - - const formattedFilters = useMemo<{ filters: DropdownOption[]; order: DropdownOption[] }>( - () => ({ - filters: [ - { - data: "All", - label: ( - prev + Number(cur), - 0 - ) || "" - } - /> - ), - }, - ...Object.entries(unformattedFilters.filters) - .filter(([_, itemCount]) => Number(itemCount) > 0) - .map(([filterValue, itemCount]) => ({ - data: filterValue, - label: , - })), - ], - order: unformattedFilters.order.map((e) => ({ data: e, label: e })), - }), - [unformattedFilters] - ); - useEffect(() => { - if (unformattedFilters.filters.length < 2) { - getThemeTargets(); - } - }, []); - - const repoOptions: never[] = []; - return ( - <> - - -
- Sort - { - setGlobalState(prevSearchOptsVarName, searchOpts); - setGlobalState(searchOptsVarName, { ...searchOpts, order: e.data }); - }} - /> -
-
- Filter - { - setGlobalState(prevSearchOptsVarName, searchOpts); - setGlobalState(searchOptsVarName, { ...searchOpts, filters: e.data }); - }} - /> - -
-
-
-
- -
- { - setGlobalState(prevSearchOptsVarName, searchOpts); - setGlobalState(searchOptsVarName, { ...searchOpts, search: e.target.value }); - }} - /> -
- - - Refresh - -
- { - setGlobalState("browserCardSize", num); - }} - /> -
- -
-
- - ); -} - -export const MemoizedSearchFields = memo(BrowserSearchFields); diff --git a/src/components/ThemeManager/FilterDropdownCustomLabel.tsx b/src/components/ThemeManager/FilterDropdownCustomLabel.tsx deleted file mode 100644 index 4ff5d25..0000000 --- a/src/components/ThemeManager/FilterDropdownCustomLabel.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export function FilterDropdownCustomLabel({ - filterValue, - itemCount, -}: { - filterValue: string; - itemCount: number | string; -}) { - return ( -
- {filterValue} - {itemCount} -
- ); -} diff --git a/src/components/ThemeManager/LoadMoreButton.tsx b/src/components/ThemeManager/LoadMoreButton.tsx deleted file mode 100644 index cab5ba6..0000000 --- a/src/components/ThemeManager/LoadMoreButton.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { DialogButton } from "decky-frontend-lib"; -import { useState, Dispatch, SetStateAction, useEffect } from "react"; -import { ThemeQueryRequest, ThemeQueryResponse } from "../../apiTypes"; -import { generateParamStr } from "../../logic"; -import { genericGET } from "../../api"; -import { useCssLoaderState } from "../../state"; - -export function LoadMoreButton({ - fetchPath = "/themes", - origSearchOpts, - themeArr, - themeArrVarName, - paramStrFilterPrepend = "", - setSnapIndex = undefined, -}: { - fetchPath: string; - origSearchOpts: ThemeQueryRequest; - themeArrVarName: string; - themeArr: ThemeQueryResponse; - paramStrFilterPrepend: string; - setSnapIndex?: Dispatch>; -}) { - const { setGlobalState } = useCssLoaderState(); - const [loadMoreCurPage, setLoadMorePage] = useState(1); - const [loading, setLoading] = useState(false); - - function loadMore() { - setLoading(true); - // This just changes "All" to "", as that is what the backend looks for - let searchOptClone = { ...origSearchOpts }; - searchOptClone.page = loadMoreCurPage + 1; - const searchOpts = generateParamStr( - searchOptClone.filters !== "All" ? searchOptClone : { ...searchOptClone, filters: "" }, - paramStrFilterPrepend - ); - genericGET(`${fetchPath}${searchOpts}`).then((data) => { - if (data) { - setGlobalState(themeArrVarName, { - total: themeArr.total, - items: [...themeArr.items, ...data.items], - }); - if (setSnapIndex) { - setSnapIndex(origSearchOpts.perPage * loadMoreCurPage - 1); - } - setLoadMorePage((curPage) => curPage + 1); - } - setLoading(false); - }); - } - - useEffect(() => { - setLoadMorePage(1); - }, [origSearchOpts]); - return ( - <> - {themeArr.items.length < themeArr.total ? ( - <> - - Load More - - - ) : null} - - ); -} diff --git a/src/components/ThemeManager/index.ts b/src/components/ThemeManager/index.ts deleted file mode 100644 index 7820595..0000000 --- a/src/components/ThemeManager/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./LoadMoreButton"; -export * from "./FilterDropdownCustomLabel"; -export * from "./BrowserItemCard"; -export * from "./BrowserSearchFields"; diff --git a/src/components/ThemePatch.tsx b/src/components/ThemePatch.tsx deleted file mode 100644 index 4437e53..0000000 --- a/src/components/ThemePatch.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { DropdownItem, PanelSectionRow, SliderField, ToggleField } from "decky-frontend-lib"; -import * as python from "../python"; -import { useState, VFC } from "react"; -import { Patch } from "../ThemeTypes"; -import { PatchComponent } from "./PatchComponent"; -import { useCssLoaderState } from "../state"; - -export const ThemePatch: VFC<{ - data: Patch; - index: number; - fullArr: Patch[]; - themeName: string; - modal?: boolean; -}> = ({ data, index, fullArr, themeName, modal = false }) => { - const { selectedPreset } = useCssLoaderState(); - const [selectedIndex, setIndex] = useState(data.options.indexOf(data.value)); - - const [selectedLabel, setLabel] = useState(data.value); - - const bottomSeparatorValue = fullArr.length - 1 === index ? "standard" : "none"; - - async function setPatchValue(value: string) { - await python.setPatchOfTheme(themeName, data.name, value); - // This was before all currently toggled themes were part of a dependency, this (and probably lots of the other preset code) can be changed to assume that by default - if (selectedPreset && selectedPreset.dependencies.includes(themeName)) { - return python.generatePresetFromThemeNames(selectedPreset.name, selectedPreset.dependencies); - } - return; - } - - function ComponentWrapper() { - return ( - <> - {data.components.length > 0 ? ( - <> - {data.components.map((e) => ( - - ))} - - ) : null} - - ); - } - - switch (data.type) { - case "slider": - return ( - <> - - } - min={0} - max={data.options.length - 1} - value={selectedIndex} - onChange={(value) => { - setPatchValue(data.options[value]); - setIndex(value); - setLabel(data.options[value]); - data.value = data.options[value]; - }} - notchCount={data.options.length} - notchLabels={data.options.map((e, i) => ({ - notchIndex: i, - label: e, - value: i, - }))} - /> - - - - ); - case "checkbox": - return ( - <> - - } - checked={data.value === "Yes"} - onChange={(bool) => { - const newValue = bool ? "Yes" : "No"; - setPatchValue(newValue); - setLabel(newValue); - setIndex(data.options.findIndex((e) => e === newValue)); - data.value = newValue; - }} - /> - - - - ); - case "dropdown": - return ( - <> - - } - menuLabel={`${data.name}`} - rgOptions={data.options.map((x, i) => { - return { data: i, label: x }; - })} - selectedOption={selectedIndex} - onChange={(index) => { - setIndex(index.data); - data.value = index.label as string; - setLabel(data.value); - setPatchValue(data.value); - }} - /> - - - - ); - case "none": - return ( - <> - - {modal ? ( - {data.name} - ) : ( - - )} - - - - ); - default: - return null; - } -}; - -const PatchLabel = ({ name }: { name: string }) => { - return ( -
- {name} -
- ); -}; diff --git a/src/components/ThemeSettings/DeleteMenu.tsx b/src/components/ThemeSettings/DeleteMenu.tsx deleted file mode 100644 index ba5c747..0000000 --- a/src/components/ThemeSettings/DeleteMenu.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { - DialogButton, - DialogCheckbox, - Focusable, - PanelSection, - showModal, -} from "decky-frontend-lib"; -import { Theme } from "../../ThemeTypes"; -import { useState } from "react"; -import { DeleteConfirmationModalRoot } from "../Modals/DeleteConfirmationModal"; - -export function DeleteMenu({ - themeList, - leaveDeleteMode, -}: { - themeList: Theme[]; - leaveDeleteMode: () => void; -}) { - let [choppingBlock, setChoppingBlock] = useState([]); // name arr - return ( - - - {themeList.map((e) => ( -
- { - if (checked) { - setChoppingBlock([...choppingBlock, e.name]); - } else { - setChoppingBlock(choppingBlock.filter((f) => f !== e.name)); - } - }} - checked={choppingBlock.includes(e.name)} - label={e.name} - /> -
- ))} - { - showModal( - - ); - }} - > - Delete - -
-
- ); -} diff --git a/src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx b/src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx deleted file mode 100644 index 774a033..0000000 --- a/src/components/ThemeSettings/FullscreenCloudProfileEntry.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { DialogButton, Focusable, PanelSectionRow } from "decky-frontend-lib"; -import { LocalThemeStatus, Theme } from "../../ThemeTypes"; -import { useCssLoaderState } from "../../state"; -import { AiOutlineDownload } from "react-icons/ai"; -import { FaTrash } from "react-icons/fa"; -import { PartialCSSThemeInfo } from "../../apiTypes"; -import { FaCheck, FaCloud } from "react-icons/fa6"; - -export function FullscreenCloudProfileEntry({ - data: e, - handleUninstall, - isInstalling, - handleUpdate, -}: { - data: PartialCSSThemeInfo & { isPrivate?: boolean }; - handleUninstall: (e: Theme | PartialCSSThemeInfo) => void; - handleUpdate: (e: Theme | PartialCSSThemeInfo) => void; - isInstalling: boolean; -}) { - const { updateStatuses } = useCssLoaderState(); - - // If it's null, that means it's cloud-only, not installed - let updateStatus: LocalThemeStatus | null = null; - const themeArrPlace = updateStatuses.find((f) => f[0] === e.id); - if (themeArrPlace) { - updateStatus = themeArrPlace[1]; - } - - const isOutdated = updateStatus === "outdated"; - const isInstalled = updateStatus !== null; - - return ( - -
- {e.displayName} - {!isInstalled ? : } - - {/* Update Button */} - {(isOutdated || !isInstalled) && ( - handleUpdate(e)} - disabled={isInstalling} - > - - - )} - {/* Only show delete button if it IS installed */} - {isInstalled && ( - handleUninstall(e)} - disabled={isInstalling} - > - - - )} - -
-
- ); -} diff --git a/src/components/ThemeSettings/FullscreenProfileEntry.tsx b/src/components/ThemeSettings/FullscreenProfileEntry.tsx deleted file mode 100644 index e7d633a..0000000 --- a/src/components/ThemeSettings/FullscreenProfileEntry.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { DialogButton, Focusable, PanelSectionRow } from "decky-frontend-lib"; -import { LocalThemeStatus, Theme } from "../../ThemeTypes"; -import { useCssLoaderState } from "../../state"; -import { AiOutlineDownload } from "react-icons/ai"; -import { FaTrash } from "react-icons/fa"; - -export function FullscreenProfileEntry({ - data: e, - handleUninstall, - isInstalling, - handleUpdate, -}: { - data: Theme; - handleUninstall: (e: Theme) => void; - handleUpdate: (e: Theme) => void; - isInstalling: boolean; -}) { - const { updateStatuses } = useCssLoaderState(); - let updateStatus: LocalThemeStatus = "installed"; - const themeArrPlace = updateStatuses.find((f) => f[0] === e.id); - if (themeArrPlace) { - updateStatus = themeArrPlace[1]; - } - return ( - -
- {e.display_name} - - {/* Update Button */} - {updateStatus === "outdated" && ( - handleUpdate(e)} - disabled={isInstalling} - > - - - )} - handleUninstall(e)} - disabled={isInstalling} - > - - - -
-
- ); -} diff --git a/src/components/ThemeSettings/FullscreenSingleThemeEntry.tsx b/src/components/ThemeSettings/FullscreenSingleThemeEntry.tsx deleted file mode 100644 index 7b3ec10..0000000 --- a/src/components/ThemeSettings/FullscreenSingleThemeEntry.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { DialogButton, Focusable, ToggleField, showModal } from "decky-frontend-lib"; -import { LocalThemeStatus, Theme } from "../../ThemeTypes"; -import { useCssLoaderState } from "../../state"; -import * as python from "../../python"; -import { ImCog } from "react-icons/im"; -import { toggleTheme } from "../../backend/backendHelpers/toggleTheme"; -import { ThemeSettingsModalRoot } from "../Modals/ThemeSettingsModal"; -import { FaEye, FaEyeSlash, FaTrash } from "react-icons/fa"; -import { BsGearFill } from "react-icons/bs"; - -export function FullscreenSingleThemeEntry({ - data: e, - showModalButtonPrompt = false, - handleUninstall, - handleUpdate, - isInstalling, -}: { - data: Theme; - showModalButtonPrompt?: boolean; - handleUninstall: (e: Theme) => void; - handleUpdate: (e: Theme) => void; - isInstalling: boolean; -}) { - const { unpinnedThemes, updateStatuses } = useCssLoaderState(); - const isPinned = !unpinnedThemes.includes(e.id); - - let [updateStatus]: [LocalThemeStatus] = ["installed"]; - const themeArrPlace = updateStatuses.find((f) => f[0] === e.id); - if (themeArrPlace) { - updateStatus = themeArrPlace[1]; - } - - // I extracted these here as doing conditional props inline sucks - const modalButtonProps = showModalButtonPrompt - ? { - onOptionsActionDescription: "Expand Settings", - onOptionsButton: () => { - showModal(); - }, - } - : {}; - - const updateButtonProps = - updateStatus === "outdated" - ? { - onSecondaryButton: () => { - handleUpdate(e); - }, - onSecondaryActionDescription: "Update Theme", - } - : {}; - - return ( - <> -
- {updateStatus === "outdated" && ( -
- )} - - {e.display_name}} - checked={e.enabled} - onChange={(switchValue: boolean) => { - toggleTheme(e, switchValue); - }} - /> - - { - if (isPinned) { - python.unpinTheme(e.id); - } else { - python.pinTheme(e.id); - } - }} - > - {isPinned ? ( - - ) : ( - - )} - - { - showModal(); - }} - > - - -
- - ); -} diff --git a/src/components/ThemeSettings/UpdateAllThemesButton.tsx b/src/components/ThemeSettings/UpdateAllThemesButton.tsx deleted file mode 100644 index 414589e..0000000 --- a/src/components/ThemeSettings/UpdateAllThemesButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { DialogButton } from "decky-frontend-lib"; -import { useCssLoaderState } from "../../state"; -import { Theme } from "../../ThemeTypes"; -import { FaDownload } from "react-icons/fa"; - -export function UpdateAllThemesButton({ - handleUpdate, -}: { - handleUpdate: (entry: Theme) => Promise; -}) { - const { updateStatuses, localThemeList } = useCssLoaderState(); - - async function updateAll() { - const themesToBeUpdated = updateStatuses.filter((e) => e[1] === "outdated"); - for (let i = 0; i < themesToBeUpdated.length; i++) { - const entry = localThemeList.find((f) => f.id === themesToBeUpdated[i][0]); - if (!entry) break; - await handleUpdate(entry); - } - } - return ( - <> - {updateStatuses.filter((e) => e[1] === "outdated").length > 0 && ( - - - Update All Themes - - )} - - ); -} diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx deleted file mode 100644 index 711a8cb..0000000 --- a/src/components/ThemeToggle.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { ButtonItem, Focusable, PanelSectionRow, ToggleField, showModal } from "decky-frontend-lib"; -import { VFC, useState, useMemo } from "react"; -import { Flags, LocalThemeStatus, Theme, UpdateStatus } from "../ThemeTypes"; - -import { ThemePatch } from "./ThemePatch"; -import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; -import { useCssLoaderState } from "../state"; -import { useRerender } from "../hooks"; -// This has to be a direct import to avoid the circular dependency -import { ThemeSettingsModalRoot } from "./Modals/ThemeSettingsModal"; -import { installTheme } from "../api"; -import { toggleTheme } from "../backend/backendHelpers/toggleTheme"; - -export const ThemeToggle: VFC<{ - data: Theme; - collapsible?: boolean; - showModalButtonPrompt?: boolean; - isFullscreen?: boolean; -}> = ({ data, collapsible = false, showModalButtonPrompt = false, isFullscreen = false }) => { - const { updateStatuses, setGlobalState, isInstalling } = useCssLoaderState(); - const [collapsed, setCollapsed] = useState(true); - - const [render, rerender] = useRerender(); - - const isPreset = useMemo(() => { - if (data.flags.includes(Flags.isPreset)) { - return true; - } - return false; - // This might not actually memoize it as data.flags is an array, so idk if it deep checks the values here - }, [data.flags]); - - let [updateStatus]: [LocalThemeStatus] = ["installed"]; - const themeArrPlace = updateStatuses.find((f) => f[0] === data.id); - if (themeArrPlace) { - updateStatus = themeArrPlace[1]; - } - - // I extracted these here as doing conditional props inline sucks - const modalButtonProps = showModalButtonPrompt - ? { - onOptionsActionDescription: "Expand Settings", - onOptionsButton: () => { - showModal( - // @ts-ignore - - ); - }, - } - : {}; - - const updateButtonProps = - updateStatus === "outdated" - ? { - onSecondaryButton: async () => { - await installTheme(data.id); - // This just updates the updateStatuses arr to know that this theme now is up to date, no need to re-fetch the API to know that - setGlobalState( - "updateStatuses", - updateStatuses.map((e) => (e[0] === data.id ? [data.id, "installed", false] : e)) - ); - }, - onSecondaryActionDescription: "Update Theme", - } - : {}; - - return ( - <> - {render && ( - <> - - - {updateStatus === "outdated" && ( -
- )} - 0 ? "none" : "standard"} - checked={data.enabled} - label={data.display_name} - description={ - isPreset - ? `Preset` - : `${updateStatus === "outdated" ? "Update Available" : data.version} | ${ - data.author - }` - } - onChange={async (switchValue: boolean) => { - toggleTheme(data, switchValue, rerender, setCollapsed); - }} - /> -
-
- {data.enabled && data.patches.length > 0 && ( - <> - {collapsible && ( -
- - setCollapsed(!collapsed)} - > - {collapsed ? ( - - ) : ( - - )} - - -
- )} - {!collapsible || !collapsed - ? data.patches.map((x, i, arr) => ( - - )) - : null} - - )} - - )} - - ); -}; diff --git a/src/components/TitleView.tsx b/src/components/TitleView.tsx deleted file mode 100644 index c6d5212..0000000 --- a/src/components/TitleView.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { DialogButton, Navigation, staticClasses, Focusable } from "decky-frontend-lib"; -import { BsGearFill } from "react-icons/bs"; -import { FaDownload } from "react-icons/fa"; -import { useCssLoaderState } from "../state"; - -export function TitleView({ onDocsClick }: { onDocsClick?: () => {} }) { - const { localThemeList } = useCssLoaderState(); - - const onSettingsClick = () => { - Navigation.CloseSideMenus(); - Navigation.Navigate("/cssloader/settings"); - }; - - const onStoreClick = () => { - Navigation.CloseSideMenus(); - Navigation.Navigate("/cssloader/theme-manager"); - }; - - return ( - - -
CSS Loader
- - - - - - -
- ); -} diff --git a/src/components/index.ts b/src/components/index.ts deleted file mode 100644 index c97e1d8..0000000 --- a/src/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./ThemeToggle"; -export * from "./ThemePatch"; -export * from "./PatchComponent"; -export * from "./TitleView"; -export * from "./ThemeManager"; -export * from "./OptionalDepsModal"; -export * from "./QAMTab"; -export * from "./Modals"; -export * from "./Loading"; diff --git a/src/decky-patches/decky-patch-store.ts b/src/decky-patches/decky-patch-store.ts new file mode 100644 index 0000000..ccb6690 --- /dev/null +++ b/src/decky-patches/decky-patch-store.ts @@ -0,0 +1,81 @@ +import { Patch } from "@decky/ui"; +import type { Backend } from "@cssloader/backend"; +import { createStore, useStore } from "zustand"; +import { disableNavPatch, enableNavPatch } from "./nav-patch"; +import { disableUnminifyMode, enableUnminifyMode } from "./unminify-mode"; +import { backend } from "@/backend"; + +interface DeckyPatchStoreValues { + unminifyModeOn: boolean; + navPatchInstance: Patch | undefined; +} +interface DeckyPatchStoreActions { + initializeStore: () => Promise; + deactivate: () => Promise; + setNavPatchState: (value: boolean, shouldToast?: boolean) => void; + setUnminifyModeState: (value: boolean, shouldToast?: boolean) => void; +} + +export interface IDeckyPatchState extends DeckyPatchStoreActions, DeckyPatchStoreValues {} + +const createDeckyPatchStore = (backend: Backend) => + createStore((set, get) => ({ + unminifyModeOn: false, + navPatchInstance: undefined, + initializeStore: async () => { + try { + const shouldEnable = await backend.storeRead("enableNavPatch"); + if (shouldEnable) { + const patch = enableNavPatch(); + set({ navPatchInstance: patch }); + } + } catch (error) {} + }, + deactivate: async () => { + const { navPatchInstance, unminifyModeOn } = get(); + if (navPatchInstance) { + disableNavPatch(navPatchInstance); + set({ navPatchInstance: undefined }); + } + if (unminifyModeOn) { + disableUnminifyMode(); + } + }, + setNavPatchState: (enabled: boolean, shouldToast: boolean = false) => { + const { navPatchInstance } = get(); + if (enabled) { + // Don't Patch Twice + if (!navPatchInstance) { + const patch = enableNavPatch(); + set({ navPatchInstance: patch }); + } + } else { + disableNavPatch(navPatchInstance); + set({ navPatchInstance: undefined }); + } + shouldToast && backend.toast("Nav Patch", enabled ? "Enabled" : "Disabled"); + backend.storeWrite("enableNavPatch", enabled + ""); + }, + setUnminifyModeState: (enabled: boolean, shouldToast: boolean = false) => { + if (enabled) { + enableUnminifyMode(); + } else { + disableUnminifyMode(); + } + shouldToast && backend.toast("Unminify Mode", enabled ? "Enabled" : "Disabled"); + }, + })); + +const deckyPatchState = createDeckyPatchStore(backend); + +export const getDeckyPatchState = () => deckyPatchState.getState(); + +const useDeckyPatchState = (fn: (state: IDeckyPatchState) => any) => useStore(deckyPatchState, fn); + +export const useDeckyPatchStateValue = ( + key: T +): IDeckyPatchState[T] => useDeckyPatchState((state) => state[key]); + +export const useDeckyPatchStateAction = ( + key: T +): IDeckyPatchState[T] => useDeckyPatchState((state) => state[key]); diff --git a/src/decky-patches/index.ts b/src/decky-patches/index.ts new file mode 100644 index 0000000..cd716ab --- /dev/null +++ b/src/decky-patches/index.ts @@ -0,0 +1 @@ +export * from "./decky-patch-store"; diff --git a/src/decky-patches/nav-patch/index.ts b/src/decky-patches/nav-patch/index.ts new file mode 100644 index 0000000..ed95419 --- /dev/null +++ b/src/decky-patches/nav-patch/index.ts @@ -0,0 +1 @@ +export * from "./nav-patch"; diff --git a/src/decky-patches/nav-patch/nav-patch.ts b/src/decky-patches/nav-patch/nav-patch.ts new file mode 100644 index 0000000..3efb62a --- /dev/null +++ b/src/decky-patches/nav-patch/nav-patch.ts @@ -0,0 +1,30 @@ +import { Patch, findModuleExport, replacePatch } from "@decky/ui"; + +export const NavController = findModuleExport( + (e) => e?.prototype?.FindNextFocusableChildInDirection +); +export function enableNavPatch(): Patch { + const patch = replacePatch( + NavController.prototype, + "FindNextFocusableChildInDirection", + function (args) { + const e = args[0]; + const t = args[1]; + const r = args[2]; + let n = t == 1 ? 1 : -1; + // @ts-ignore + for (let t = e + n; t >= 0 && t < this.m_rgChildren.length; t += n) { + // @ts-ignore + const e = this.m_rgChildren[t].FindFocusableNode(r); + if (e && window.getComputedStyle(e.m_element).display !== "none") return e; + } + return null; + } + ); + return patch; +} + +export function disableNavPatch(navPatchInstance: Patch | undefined) { + if (!navPatchInstance) return; + navPatchInstance.unpatch(); +} diff --git a/src/deckyPatches/ClassHashMap.tsx b/src/decky-patches/unminify-mode/class-hash-map.ts similarity index 96% rename from src/deckyPatches/ClassHashMap.tsx rename to src/decky-patches/unminify-mode/class-hash-map.ts index 156e395..c82c870 100644 --- a/src/deckyPatches/ClassHashMap.tsx +++ b/src/decky-patches/unminify-mode/class-hash-map.ts @@ -1,4 +1,4 @@ -import { classMap } from "decky-frontend-lib"; +import { classMap } from "@decky/ui"; export var classHashMap = new Map(); diff --git a/src/decky-patches/unminify-mode/index.ts b/src/decky-patches/unminify-mode/index.ts new file mode 100644 index 0000000..1e9c4c3 --- /dev/null +++ b/src/decky-patches/unminify-mode/index.ts @@ -0,0 +1 @@ +export * from "./unminify-mode"; diff --git a/src/deckyPatches/SteamTabElementsFinder.tsx b/src/decky-patches/unminify-mode/steam-tab-elements-finder.ts similarity index 89% rename from src/deckyPatches/SteamTabElementsFinder.tsx rename to src/decky-patches/unminify-mode/steam-tab-elements-finder.ts index 3bdeda0..292a82e 100644 --- a/src/deckyPatches/SteamTabElementsFinder.tsx +++ b/src/decky-patches/unminify-mode/steam-tab-elements-finder.ts @@ -1,4 +1,4 @@ -import { getGamepadNavigationTrees } from "decky-frontend-lib"; +import { getGamepadNavigationTrees } from "@decky/ui"; export function getElementFromNavID(navID: string) { const all = getGamepadNavigationTrees(); diff --git a/src/deckyPatches/UnminifyMode.tsx b/src/decky-patches/unminify-mode/unminify-mode.ts similarity index 93% rename from src/deckyPatches/UnminifyMode.tsx rename to src/decky-patches/unminify-mode/unminify-mode.ts index d104ead..dde18a3 100644 --- a/src/deckyPatches/UnminifyMode.tsx +++ b/src/decky-patches/unminify-mode/unminify-mode.ts @@ -1,5 +1,5 @@ -import { classHashMap, initializeClassHashMap } from "./ClassHashMap"; -import { getRootElements } from "./SteamTabElementsFinder"; +import { classHashMap, initializeClassHashMap } from "./class-hash-map"; +import { getRootElements } from "./steam-tab-elements-finder"; export function unminifyElement(element: Element) { if (element.classList.length === 0) return; diff --git a/src/deckyPatches/NavControllerFinder.tsx b/src/deckyPatches/NavControllerFinder.tsx deleted file mode 100644 index 7b95239..0000000 --- a/src/deckyPatches/NavControllerFinder.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Module, findModuleChild } from "decky-frontend-lib"; - -export const NavController = findModuleChild((m: Module) => { - if (typeof m !== "object") return undefined; - - // Pre Chromium-109 - if (m?.CFocusNavNode) { - return m.CFocusNavNode; - } - - for (let prop in m) { - if (m[prop]?.prototype?.FindNextFocusableChildInDirection) { - return m[prop]; - } - } - - return undefined; -}); diff --git a/src/deckyPatches/NavPatch.tsx b/src/deckyPatches/NavPatch.tsx deleted file mode 100644 index 9c48b08..0000000 --- a/src/deckyPatches/NavPatch.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { replacePatch } from "decky-frontend-lib"; -import { NavController } from "./NavControllerFinder"; -import { globalState, toast, storeWrite } from "../python"; - -export function enableNavPatch(shouldToast: boolean = false) { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - const { navPatchInstance } = globalState!.getPublicState(); - // Don't patch twice - if (navPatchInstance) return; - const patch = replacePatch( - NavController.prototype, - "FindNextFocusableChildInDirection", - function (args) { - const e = args[0]; - const t = args[1]; - const r = args[2]; - let n = t == 1 ? 1 : -1; - // @ts-ignore - for (let t = e + n; t >= 0 && t < this.m_rgChildren.length; t += n) { - // @ts-ignore - const e = this.m_rgChildren[t].FindFocusableNode(r); - if (e && window.getComputedStyle(e.m_element).display !== "none") return e; - } - return null; - } - ); - setGlobalState("navPatchInstance", patch); - shouldToast && toast("CSS Loader", "Nav Patch Enabled"); - return; -} - -export function disableNavPatch(shouldToast: boolean = false) { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - const { navPatchInstance } = globalState!.getPublicState(); - // Don't unpatch something that doesn't exist - // Probably the closest thing JS can get to null dereference - if (!navPatchInstance) return; - navPatchInstance.unpatch(); - setGlobalState("navPatchInstance", undefined); - shouldToast && toast("CSS Loader", "Nav Patch Disabled"); - return; -} - -export function setNavPatch(value: boolean, shouldToast: boolean = false) { - value ? enableNavPatch(shouldToast) : disableNavPatch(shouldToast); - storeWrite("enableNavPatch", value + ""); -} diff --git a/src/deckyPatches/NavPatchInfoModal.tsx b/src/deckyPatches/NavPatchInfoModal.tsx deleted file mode 100644 index 13c1d53..0000000 --- a/src/deckyPatches/NavPatchInfoModal.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { DialogButton, Focusable, ConfirmModal } from "decky-frontend-lib"; -import { Theme } from "../ThemeTypes"; -import { setNavPatch } from "./NavPatch"; -export function NavPatchInfoModalRoot({ - themeData, - closeModal, -}: { - themeData: Theme; - closeModal?: any; -}) { - function onButtonClick() { - setNavPatch(true, true); - closeModal(); - } - return ( - - - {themeData.name} hides elements that can be selected using a controller. For this to work - correctly, CSS Loader needs to patch controller navigation. Not enabling this feature will - cause visually hidden elements to be able to be selected using a controller. - - - ); -} diff --git a/src/hooks/index.ts b/src/hooks/index.ts deleted file mode 100644 index d8e5bf3..0000000 --- a/src/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./useRerender"; diff --git a/src/index.tsx b/src/index.tsx index 4cd407c..f073050 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,209 +1,31 @@ -import { - ButtonItem, - definePlugin, - PanelSection, - PanelSectionRow, - ServerAPI, -} from "decky-frontend-lib"; -import { useEffect, useState } from "react"; -import * as python from "./python"; -import * as api from "./api"; +import { StyleProvider, TitleView } from "@/lib"; import { RiPaintFill } from "react-icons/ri"; +import { QamTabPage } from "@/modules/qam-tab-page"; +import { definePlugin } from "@decky/api"; +import { getCSSLoaderState } from "@/backend"; +import { getDeckyPatchState } from "./decky-patches"; -import { ThemeManagerRouter } from "./pages/theme-manager"; -import { CssLoaderContextProvider, CssLoaderState, useCssLoaderState } from "./state"; -import { MOTDDisplay, PresetSelectionDropdown, QAMThemeToggleList, TitleView } from "./components"; -import { ExpandedViewPage } from "./pages/theme-manager/ExpandedView"; -import { Flags, Theme } from "./ThemeTypes"; -import { dummyFunction, getInstalledThemes, reloadBackend } from "./python"; -import { bulkThemeUpdateCheck } from "./logic/bulkThemeUpdateCheck"; -import { disableNavPatch, enableNavPatch } from "./deckyPatches/NavPatch"; -import { SettingsPageRouter } from "./pages/settings/SettingsPageRouter"; -import { disableUnminifyMode } from "./deckyPatches/UnminifyMode"; - -function Content() { - const { localThemeList, setGlobalState } = useCssLoaderState(); - - const [dummyFuncResult, setDummyResult] = useState(false); - - function dummyFuncTest() { - dummyFunction().then((res) => { - if (res.success) { - setDummyResult(res.result); - return; - } - setDummyResult(false); - }); - } - - function reload() { - reloadBackend(); - dummyFuncTest(); - bulkThemeUpdateCheck().then((data) => setGlobalState("updateStatuses", data)); - } - - useEffect(() => { - setGlobalState( - "selectedPreset", - localThemeList.find((e) => e.flags.includes(Flags.isPreset) && e.enabled) - ); - }, [localThemeList]); - - useEffect(() => { - dummyFuncTest(); - getInstalledThemes(); - }, []); - - return ( - <> - - - {dummyFuncResult ? ( - <> - - {localThemeList.length > 0 && } - - - ) : ( - - - CssLoader failed to initialize, try reloading, and if that doesn't work, try - restarting your deck. - - - )} - - - reload()}> - Refresh - - - - - ); -} - -export default definePlugin((serverApi: ServerAPI) => { - const state: CssLoaderState = new CssLoaderState(); - python.setServer(serverApi); - python.setStateClass(state); - api.setServer(serverApi); - api.setStateClass(state); - - python.resolve(python.getThemes(), async (allThemes: Theme[]) => { - // Set selectedPreset - state.setGlobalState( - "selectedPreset", - allThemes.find((e) => e.flags.includes(Flags.isPreset) && e.enabled) - ); - - // Check for updates, and schedule a check 24 hours from now - bulkThemeUpdateCheck(allThemes).then((data) => { - state.setGlobalState("updateStatuses", data); - }); - python.scheduleCheckForUpdates(); - - // If a user has magically deleted a theme in the unpinnedList and the store wasn't updated, this fixes that - python.resolve(python.storeRead("unpinnedThemes"), (unpinnedJsonStr: string) => { - const unpinnedThemes: string[] = unpinnedJsonStr ? JSON.parse(unpinnedJsonStr) : []; - const allIds = allThemes.map((e) => e.id); - - // If a theme is in the unpinned store but no longer exists, remove it from the unpinned store - let unpinnedClone = [...unpinnedThemes]; - unpinnedThemes.forEach((e) => { - if (!allIds.includes(e)) { - unpinnedClone = unpinnedClone.filter((id) => id !== e); - } - }); - - state.setGlobalState("unpinnedThemes", unpinnedClone); - if (JSON.stringify(unpinnedClone) !== unpinnedJsonStr) { - python.storeWrite("unpinnedThemes", JSON.stringify(unpinnedClone)); - } - }); - }); - - // Api Token - python.resolve(python.storeRead("shortToken"), (token: string) => { - if (token) { - state.setGlobalState("apiShortToken", token); - } - }); - - // Hidden MOTD - python.resolve(python.storeRead("hiddenMotd"), (id: string) => { - if (id) { - state.setGlobalState("hiddenMotd", id); - } - }); - - // Nav Patch - python.resolve(python.storeRead("enableNavPatch"), (value: string) => { - if (value === "true") { - enableNavPatch(); - } - }); - - serverApi.routerHook.addRoute("/cssloader/theme-manager", () => ( - - - - )); - - serverApi.routerHook.addRoute("/cssloader/settings", () => ( - - - - )); - - serverApi.routerHook.addRoute("/cssloader/expanded-view", () => ( - - - - )); +export default definePlugin(() => { + getCSSLoaderState().initializeStore(); + getDeckyPatchState().initializeStore(); return { titleView: ( - + - + ), title:
CSSLoader
, - alwaysRender: true, + icon: , content: ( - - - + + + ), - icon: , + alwaysRender: true, onDismount: () => { - const { updateCheckTimeout } = state.getPublicState(); - if (updateCheckTimeout) clearTimeout(updateCheckTimeout); - disableUnminifyMode(); - disableNavPatch(); + getCSSLoaderState().deactivate(); + getDeckyPatchState().deactivate(); }, }; }); diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts new file mode 100644 index 0000000..3d3b960 --- /dev/null +++ b/src/lib/components/index.ts @@ -0,0 +1,7 @@ +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"; diff --git a/src/components/QAMTab/MOTDDisplay.tsx b/src/lib/components/motd-display/MOTDDisplay.tsx similarity index 58% rename from src/components/QAMTab/MOTDDisplay.tsx rename to src/lib/components/motd-display/MOTDDisplay.tsx index c586651..5943f3e 100644 --- a/src/components/QAMTab/MOTDDisplay.tsx +++ b/src/lib/components/motd-display/MOTDDisplay.tsx @@ -1,49 +1,37 @@ -import { DialogButton, Focusable, PanelSection } from "decky-frontend-lib"; -import { useEffect, useState, useMemo } from "react"; -import { Motd } from "../../apiTypes/Motd"; -import { genericGET } from "../../api"; +import { DialogButton, Focusable, PanelSection } from "@decky/ui"; +import { useEffect, useMemo } from "react"; +import { Motd } from "@/types"; import { FaTimes } from "react-icons/fa"; -import { useCssLoaderState } from "../../state"; -import { getHiddenMotd, setHiddenMotd } from "../../backend/pythonMethods/pluginSettingsMethods"; +import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; + +const SEVERITIES = { + High: { + color: "#bb1414", + text: "#fff", + }, + Medium: { + color: "#bbbb14", + text: "#fff", + }, + Low: { + color: "#1488bb", + text: "#fff", + }, +}; export function MOTDDisplay() { - const [motd, setMotd] = useState(); - const { hiddenMotd, setGlobalState } = useCssLoaderState(); + const getMotd = useCSSLoaderAction("getMotd"); + const hideMotd = useCSSLoaderAction("hideMotd"); + const motd = useCSSLoaderStateValue("motd"); + const hiddenMotdId = useCSSLoaderStateValue("hiddenMotdId"); + useEffect(() => { - async function getMotd() { - const res = await genericGET("/motd", false, undefined, () => {}, true); - setMotd(res); - } - getMotd(); + void getMotd(); }, []); - async function dismiss() { - if (motd) { - await setHiddenMotd(motd.id); - const res = await getHiddenMotd(); - if (res.success) { - setGlobalState("hiddenMotd", res.result); - } - } - } - - const SEVERITIES = { - High: { - color: "#bb1414", - text: "#fff", - }, - Medium: { - color: "#bbbb14", - text: "#fff", - }, - Low: { - color: "#1488bb", - text: "#fff", - }, - }; const hidden = useMemo(() => { - return hiddenMotd === motd?.id; - }, [hiddenMotd, motd]); + return hiddenMotdId === motd?.id; + }, [hiddenMotdId, motd]); const severity = SEVERITIES[motd?.severity || "Low"]; @@ -79,7 +67,7 @@ export function MOTDDisplay() { top: ".75em", right: ".75em", }} - onClick={dismiss} + onClick={hideMotd} > void }) { + const setNavPatchState = useDeckyPatchStateAction("setNavPatchState"); + return ( + { + setNavPatchState(true, true); + closeModal?.(); + }} + > +

+ This theme hides elements that can be selected using a controller. For this to work + correctly, CSS Loader needs to patch controller navigation. Not enabling this feature will + cause visually hidden elements to be able to be selected using a controller. +

+
+ ); +} diff --git a/src/lib/components/nav-patch-info-modal/index.ts b/src/lib/components/nav-patch-info-modal/index.ts new file mode 100644 index 0000000..3073a34 --- /dev/null +++ b/src/lib/components/nav-patch-info-modal/index.ts @@ -0,0 +1 @@ +export * from "./NavPatchInfoModal"; diff --git a/src/lib/components/optional-deps-modal/OptionalDepsModal.tsx b/src/lib/components/optional-deps-modal/OptionalDepsModal.tsx new file mode 100644 index 0000000..ff114f8 --- /dev/null +++ b/src/lib/components/optional-deps-modal/OptionalDepsModal.tsx @@ -0,0 +1,45 @@ +import { Theme } from "@/types"; +import { Modal } from "../../primitives"; +import { DialogButton, Focusable } from "@decky/ui"; + +export function OptionalDepsModal({ + closeModal, + theme, + onSelect, +}: { + closeModal?: () => void; + theme: Theme; + onSelect: (enableDeps: boolean, enableDepValues: boolean) => void; +}) { + function handleChoice(enableDeps: boolean, enableDepValues: boolean) { + onSelect(enableDeps, enableDepValues); + closeModal?.(); + } + + return ( + +
+

+ {theme.name} enables optional themes to enhance this theme. Disabling these may break the + theme, or make the theme look completely different. Specific optional themes can be + configured and or enabled/disabled anytime via the Quick Access Menu. +

+

+ Enable without configuration will enable optional themes but not overwrite their + configuration, and Enable only this theme will not enable any optional themes. +

+
+ + handleChoice(true, true)}> + Enable with configuration {"(Recommended)"} + + handleChoice(true, false)}> + Enable without configuration + + handleChoice(false, false)}> + Enable only this theme + + +
+ ); +} diff --git a/src/lib/components/optional-deps-modal/index.ts b/src/lib/components/optional-deps-modal/index.ts new file mode 100644 index 0000000..dbf87ce --- /dev/null +++ b/src/lib/components/optional-deps-modal/index.ts @@ -0,0 +1 @@ +export * from "./OptionalDepsModal"; diff --git a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx new file mode 100644 index 0000000..8313709 --- /dev/null +++ b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx @@ -0,0 +1,60 @@ +import { DropdownItem, PanelSectionRow } from "@decky/ui"; +import { Flags } from "@/types"; +import { useMemo } from "react"; +import { FiPlusCircle } from "react-icons/fi"; +import { useForcedRerender } from "../../hooks"; +import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; + +export function PresetSelectionDropdown() { + const themes = useCSSLoaderStateValue("themes"); + const selectedPreset = useCSSLoaderStateValue("selectedPreset"); + const changePreset = useCSSLoaderAction("changePreset"); + const presets = useMemo(() => themes.filter((e) => e.flags.includes(Flags.isPreset)), [themes]); + const hasInvalidPresetState = presets.length > 1; + + const [render, rerender] = useForcedRerender(); + + return ( + <> + {render && ( + + ({ label: e.display_name, data: e.name })), + { + data: "New Profile", + label: ( +
+ + New Profile +
+ ), + }, + ]} + onChange={async ({ data }) => { + if (data === "New Profile") { + // showModal(); + rerender(); + return; + } + await changePreset(data); + }} + /> +
+ )} + + ); +} diff --git a/src/lib/components/preset-selection-dropdown/index.ts b/src/lib/components/preset-selection-dropdown/index.ts new file mode 100644 index 0000000..8313d47 --- /dev/null +++ b/src/lib/components/preset-selection-dropdown/index.ts @@ -0,0 +1 @@ +export * from "./PresetSelectionDropdown"; diff --git a/src/lib/components/style-provider/StyleProvider.tsx b/src/lib/components/style-provider/StyleProvider.tsx new file mode 100644 index 0000000..0be318c --- /dev/null +++ b/src/lib/components/style-provider/StyleProvider.tsx @@ -0,0 +1,10 @@ +import { styles } from "@/styles"; + +export function StyleProvider({ children }: { children: React.ReactNode }) { + return ( + <> + + {children} + + ); +} diff --git a/src/lib/components/style-provider/index.ts b/src/lib/components/style-provider/index.ts new file mode 100644 index 0000000..5f834cd --- /dev/null +++ b/src/lib/components/style-provider/index.ts @@ -0,0 +1 @@ +export * from "./StyleProvider"; diff --git a/src/lib/components/theme-patch/ThemePatch.tsx b/src/lib/components/theme-patch/ThemePatch.tsx new file mode 100644 index 0000000..2d7ed3e --- /dev/null +++ b/src/lib/components/theme-patch/ThemePatch.tsx @@ -0,0 +1,103 @@ +import { Patch } from "@/types"; +import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useState } from "react"; +import { DropdownItem, PanelSectionRow, SliderField, ToggleField } from "@decky/ui"; +import { ThemePatchComponent } from "./ThemePatchComponent"; + +const modal = false; + +export function ThemePatch({ + patch, + shouldHaveBottomSeparator, + themeName, +}: { + patch: Patch; + shouldHaveBottomSeparator: boolean; + themeName: string; +}) { + const bottomSeparatorValue = shouldHaveBottomSeparator ? "standard" : "none"; + + const setPatchValue = useCSSLoaderAction("setPatchValue"); + + const [selectedValueIndex, setSelectedValueIndex] = useState(patch.options.indexOf(patch.value)); + + function onValueChange(value: string) { + const index = patch.options.indexOf(value); + setSelectedValueIndex(index); + // I vaguely remember this fixing some optimistic state update issue + patch.value = value; + void setPatchValue(themeName, patch.name, value); + } + + return ( + <> + + {patch.type === "slider" && ( + } + min={0} + max={patch.options.length - 1} + value={selectedValueIndex} + notchCount={patch.options.length} + notchLabels={patch.options.map((option, index) => ({ + notchIndex: index, + label: option, + value: index, + }))} + onChange={(index) => { + onValueChange(patch.options[index]); + }} + /> + )} + {patch.type === "checkbox" && ( + } + checked={patch.value === "Yes"} + onChange={(value) => { + // TODO: TEST THIS + const newValue = value ? "Yes" : "No"; + onValueChange(newValue); + }} + /> + )} + {patch.type === "dropdown" && ( + } + menuLabel={patch.name} + rgOptions={patch.options.map((option, index) => ({ label: option, data: index }))} + selectedOption={selectedValueIndex} + onChange={(value) => { + onValueChange(value.label as string); + }} + /> + )} + {patch.type === "none" && ( + <> + {modal ? ( + {patch.name} + ) : ( + + )} + + )} + + {patch.components.map((component) => ( + + ))} + + ); +} + +// TODO: IS THIS NEEDED? +function PatchLabel({ name }: { name: string }) { + return
{name}
; +} diff --git a/src/lib/components/theme-patch/ThemePatchComponent.tsx b/src/lib/components/theme-patch/ThemePatchComponent.tsx new file mode 100644 index 0000000..08ba845 --- /dev/null +++ b/src/lib/components/theme-patch/ThemePatchComponent.tsx @@ -0,0 +1,93 @@ +import { ThemePatchComponent } from "@/types"; +import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { ButtonItem, ColorPickerModal, PanelSectionRow, showModal } from "@decky/ui"; +import { FaFolder } from "react-icons/fa"; +import { FileSelectionType, openFilePicker } from "@decky/api"; +import Color from "color"; + +export function ThemePatchComponent({ + component, + currentPatchValue, + patchName, + themeName, + shouldHaveBottomSeparator, +}: { + component: ThemePatchComponent; + currentPatchValue?: string; + patchName: string; + themeName: string; + shouldHaveBottomSeparator: boolean; +}) { + const bottomSeparatorValue = shouldHaveBottomSeparator ? "standard" : "none"; + + const setComponentValue = useCSSLoaderAction("setComponentValue"); + const themeRootPath = useCSSLoaderStateValue("themeRootPath"); + const toast = useCSSLoaderAction("toast"); + if (currentPatchValue !== component.on) return null; + + function onValueChange(value: string) { + void setComponentValue(themeName, patchName, component.name, value); + } + + async function handleImagePicker() { + // TODO: GATE BY FILE EXTENSION + try { + const filePickerRes = await openFilePicker(FileSelectionType.FILE, themeRootPath); + if (!filePickerRes.path.includes(themeRootPath)) { + throw new Error("Images must be within themes folder"); + } + if (!/\.(jpg|jpeg|png|webp|avif|gif|svg)$/.test(filePickerRes.path)) { + throw new Error("Invalid File Type"); + } + const relativePath = filePickerRes.path.split(`${themeRootPath}/`)[1]; + onValueChange(relativePath); + } catch (error) { + if (error instanceof Error) { + toast(error.message); + } + } + } + + function handleColorPicker() { + const curColorHSLArray = Color(component.value).hsl().array(); + showModal( + // @ts-expect-error + { + onValueChange(hslString); + }} + /> + ); + } + + return ( + + +
+ Open {component.name} +
+ {component.type === "color-picker" ? ( +
+ ) : ( + + )} +
+
+ + + ); +} diff --git a/src/lib/components/theme-patch/index.ts b/src/lib/components/theme-patch/index.ts new file mode 100644 index 0000000..a0c404e --- /dev/null +++ b/src/lib/components/theme-patch/index.ts @@ -0,0 +1 @@ +export * from "./ThemePatch"; diff --git a/src/lib/components/title-view/TitleView.tsx b/src/lib/components/title-view/TitleView.tsx new file mode 100644 index 0000000..2d7ed63 --- /dev/null +++ b/src/lib/components/title-view/TitleView.tsx @@ -0,0 +1,39 @@ +import { DialogButton, Navigation, Focusable, quickAccessMenuClasses } from "@decky/ui"; +import { BsGearFill } from "react-icons/bs"; +import { FaDownload } from "react-icons/fa"; +import { useCSSLoaderStateValue } from "@/backend"; +import { cn } from "../../utils"; + +export function TitleView() { + const themes = useCSSLoaderStateValue("themes"); + + const onSettingsClick = () => { + Navigation.CloseSideMenus(); + Navigation.Navigate("/cssloader/settings"); + }; + + const onStoreClick = () => { + Navigation.CloseSideMenus(); + Navigation.Navigate("/cssloader/theme-manager"); + }; + + return ( + +
CSS Loader
+ 0 && "cl-animate-onboarding")} + onClick={onStoreClick} + > + + + + + +
+ ); +} diff --git a/src/lib/components/title-view/index.ts b/src/lib/components/title-view/index.ts new file mode 100644 index 0000000..0ad5366 --- /dev/null +++ b/src/lib/components/title-view/index.ts @@ -0,0 +1 @@ +export * from "./TitleView"; diff --git a/src/lib/hooks/index.ts b/src/lib/hooks/index.ts new file mode 100644 index 0000000..eda6231 --- /dev/null +++ b/src/lib/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useForcedRerender"; diff --git a/src/hooks/useRerender.ts b/src/lib/hooks/useForcedRerender.ts similarity index 88% rename from src/hooks/useRerender.ts rename to src/lib/hooks/useForcedRerender.ts index de6cd4b..64b7d78 100644 --- a/src/hooks/useRerender.ts +++ b/src/lib/hooks/useForcedRerender.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; // This should only be used to fix weird bugs with how valve's toggles/dropdowns/etc don't update state // If used, state why in a comment next to the invocation -export function useRerender(): [boolean, () => void] { +export function useForcedRerender(): [boolean, () => void] { const [render, setRender] = useState(true); useEffect(() => { if (render === false) { diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..fc82a00 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1,3 @@ +export * from "./components"; +export * from "./hooks"; +export * from "./utils"; diff --git a/src/lib/primitives/ConfirmModal.tsx b/src/lib/primitives/ConfirmModal.tsx new file mode 100644 index 0000000..4734daf --- /dev/null +++ b/src/lib/primitives/ConfirmModal.tsx @@ -0,0 +1,31 @@ +import { ConfirmModal as CM } from "@decky/ui"; +import { StyleProvider } from "../components"; + +export function ConfirmModal({ + closeModal, + children, + onConfirm, + title, + confirmText, + cancelText, +}: { + closeModal?: () => void; + children: React.ReactNode; + onConfirm: () => void; + title?: string; + confirmText?: string; + cancelText?: string; +}) { + return ( + + {children} + + ); +} diff --git a/src/lib/primitives/Modal.tsx b/src/lib/primitives/Modal.tsx new file mode 100644 index 0000000..2375e89 --- /dev/null +++ b/src/lib/primitives/Modal.tsx @@ -0,0 +1,23 @@ +import { ModalRoot } from "@decky/ui"; +import { StyleProvider } from "../components"; + +export function Modal({ + closeModal, + children, + title, +}: { + closeModal?: () => void; + title?: string; + children: React.ReactNode; +}) { + return ( + + +
+

{title}

+ {children} +
+
+
+ ); +} diff --git a/src/lib/primitives/index.ts b/src/lib/primitives/index.ts new file mode 100644 index 0000000..bbb92a4 --- /dev/null +++ b/src/lib/primitives/index.ts @@ -0,0 +1,2 @@ +export * from "./Modal"; +export * from "./ConfirmModal"; diff --git a/src/lib/utils/classname-merger.ts b/src/lib/utils/classname-merger.ts new file mode 100644 index 0000000..ab65735 --- /dev/null +++ b/src/lib/utils/classname-merger.ts @@ -0,0 +1,6 @@ +import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...args: any[]) { + return twMerge(clsx(args)); +} diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts new file mode 100644 index 0000000..58c2257 --- /dev/null +++ b/src/lib/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./classname-merger"; +export * from "./toggleThemeWithModals"; diff --git a/src/lib/utils/toggleThemeWithModals.tsx b/src/lib/utils/toggleThemeWithModals.tsx new file mode 100644 index 0000000..b9b7d7f --- /dev/null +++ b/src/lib/utils/toggleThemeWithModals.tsx @@ -0,0 +1,27 @@ +import { Flags, Theme } from "@/types"; +import { showModal } from "@decky/ui"; +import { getCSSLoaderState } from "@/backend"; +import { getDeckyPatchState } from "../../decky-patches"; +import { NavPatchInfoModal, OptionalDepsModal } from "../components"; + +export async function toggleThemeWithModals(theme: Theme, value: boolean, rerender?: () => void) { + const { toggleTheme } = getCSSLoaderState(); + const { navPatchInstance } = getDeckyPatchState(); + if (value && theme.flags.includes(Flags.optionalDeps)) { + showModal( + { + toggleTheme(theme, value, enableDeps, enableDepValues); + }} + /> + ); + rerender?.(); + } else { + await toggleTheme(theme, value); + } + + if (value && theme.flags.includes(Flags.navPatch) && !navPatchInstance) { + showModal(); + } +} diff --git a/src/logic/bulkThemeUpdateCheck.ts b/src/logic/bulkThemeUpdateCheck.ts deleted file mode 100644 index 7c4ec45..0000000 --- a/src/logic/bulkThemeUpdateCheck.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Theme, UpdateStatus } from "../ThemeTypes"; -import { genericGET } from "../api"; -import { MinimalCSSThemeInfo } from "../apiTypes"; -import { globalState } from "../python"; -const apiUrl = "https://api.deckthemes.com"; - -async function fetchThemeIDS(idsToQuery: string[]): Promise { - const queryStr = "?ids=" + idsToQuery.join("."); - return genericGET(`/themes/ids${queryStr}`) - .then((data) => { - if (data) return data; - return []; - }) - .catch((err) => { - console.error("Error Fetching Theme Updates!", err); - return []; - }); -} - -export async function bulkThemeUpdateCheck(customThemeArr?: Theme[]) { - const localThemeList = customThemeArr || globalState!.getPublicState().localThemeList; - let idsToQuery: string[] = localThemeList.map((e) => e.id); - - if (idsToQuery.length === 0) return []; - - const themeArr = await fetchThemeIDS(idsToQuery); - - if (themeArr.length === 0) return []; - - const updateStatusArr: UpdateStatus[] = localThemeList.map((localEntry) => { - const remoteEntry = themeArr.find( - (remote) => remote.id === localEntry.id || remote.name === localEntry.id - ); - if (!remoteEntry) return [localEntry.id, "local", false]; - if (remoteEntry.version === localEntry.version) - return [localEntry.id, "installed", remoteEntry]; - return [localEntry.id, "outdated", remoteEntry]; - }); - - return updateStatusArr; -} diff --git a/src/logic/calcButtonColor.ts b/src/logic/calcButtonColor.ts deleted file mode 100644 index 5b12215..0000000 --- a/src/logic/calcButtonColor.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function calcButtonColor(installStatus: string) { - let filterCSS = ""; - switch (installStatus) { - case "outdated": - filterCSS = "invert(6%) sepia(90%) saturate(200%) hue-rotate(160deg) contrast(122%)"; - break; - default: - filterCSS = ""; - break; - } - return filterCSS; -} diff --git a/src/logic/generateParamStr.ts b/src/logic/generateParamStr.ts deleted file mode 100644 index cd0efd3..0000000 --- a/src/logic/generateParamStr.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function generateParamStr(origSearchOpts: any, filterPrepend: string = "") { - // This can be done with 'new URLSearchParams(obj)' but I want more control - const searchOpts = Object.assign({}, origSearchOpts); - if (filterPrepend) { - searchOpts.filters = filterPrepend + searchOpts.filters; - } - let paramString = "?"; - Object.keys(searchOpts).forEach((key, i) => { - // @ts-ignore typescript doesn't know how object.keys works 🙄 - if (searchOpts[key]) { - // @ts-ignore - paramString += `${i !== 0 ? "&" : ""}${key}=${searchOpts[key]}`; - } - }); - return paramString; -} diff --git a/src/logic/index.ts b/src/logic/index.ts deleted file mode 100644 index 837d353..0000000 --- a/src/logic/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./calcButtonColor"; -export * from "./generateParamStr"; diff --git a/src/logic/numbers.ts b/src/logic/numbers.ts deleted file mode 100644 index d2e8fce..0000000 --- a/src/logic/numbers.ts +++ /dev/null @@ -1,47 +0,0 @@ -// 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) { - if (exponent < suffixes[suffix]) { - shortNumber += suffix; - break; - } - } - - return sign + shortNumber; -} diff --git a/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx b/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx new file mode 100644 index 0000000..73d491a --- /dev/null +++ b/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx @@ -0,0 +1,21 @@ +import { useCSSLoaderStateValue } from "@/backend"; +import { PanelSectionRow } from "@decky/ui"; + +export function QamDummyFunctionBoundary({ children }: { children: React.ReactNode }) { + const dummyFunctionResult = useCSSLoaderStateValue("dummyFunctionResult"); + + if (!dummyFunctionResult) { + return ( + <> + + + CSS Loader failed to initialize, try reloading, and if that doesn't work, try restarting + your deck. + + + + ); + } + + return <>{children}; +} diff --git a/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx b/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx new file mode 100644 index 0000000..364bcbd --- /dev/null +++ b/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx @@ -0,0 +1,19 @@ +import { useCSSLoaderStateValue } from "@/backend"; +import { FaEyeSlash } from "react-icons/fa"; + +export function QamHiddenThemesDisplay() { + const unpinnedThemes = useCSSLoaderStateValue("unpinnedThemes"); + + if (unpinnedThemes.length === 0) { + return null; + } + + return ( +
+ + + {unpinnedThemes.length} theme{unpinnedThemes.length > 1 ? "s are" : "is"} hidden. + +
+ ); +} diff --git a/src/modules/qam-tab-page/components/QamRefreshButton.tsx b/src/modules/qam-tab-page/components/QamRefreshButton.tsx new file mode 100644 index 0000000..abf6588 --- /dev/null +++ b/src/modules/qam-tab-page/components/QamRefreshButton.tsx @@ -0,0 +1,18 @@ +import { ButtonItem, PanelSectionRow } from "@decky/ui"; +import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; + +export function QamRefreshButton() { + const reloadPlugin = useCSSLoaderAction("reloadPlugin"); + return ( + + { + void reloadPlugin(); + }} + layout="below" + > + Refresh + + + ); +} diff --git a/src/modules/qam-tab-page/components/QamThemeList.tsx b/src/modules/qam-tab-page/components/QamThemeList.tsx new file mode 100644 index 0000000..3520dcb --- /dev/null +++ b/src/modules/qam-tab-page/components/QamThemeList.tsx @@ -0,0 +1,25 @@ +import { Focusable } from "@decky/ui"; +import { useCSSLoaderStateValue } from "../../../backend-impl/decky-theme-store"; +import { Flags } from "@/types"; +import { QamThemeToggle } from "./QamThemeToggle"; + +export function QamThemeList() { + const themes = useCSSLoaderStateValue("themes"); + const unpinnedThemes = useCSSLoaderStateValue("unpinnedThemes"); + + if (themes.length === 0) { + return You have no themes, visit the theme store to download some!; + } + + return ( + + {themes + .filter( + (theme) => !unpinnedThemes.includes(theme.id) && !theme.flags.includes(Flags.isPreset) + ) + .map((theme) => ( + + ))} + + ); +} diff --git a/src/modules/qam-tab-page/components/QamThemeToggle.tsx b/src/modules/qam-tab-page/components/QamThemeToggle.tsx new file mode 100644 index 0000000..07ad32a --- /dev/null +++ b/src/modules/qam-tab-page/components/QamThemeToggle.tsx @@ -0,0 +1,92 @@ +import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { ThemePatch, toggleThemeWithModals, useForcedRerender } from "@/lib"; +import { useEffect, useState } from "react"; +import { LocalThemeStatus, Theme } from "@/types"; +import { ButtonItem, Focusable, PanelSectionRow, ToggleField, showModal } from "@decky/ui"; +import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; + +export function QamThemeToggle({ theme }: { theme: Theme }) { + const updateStatuses = useCSSLoaderStateValue("updateStatuses"); + const isWorking = useCSSLoaderStateValue("isWorking"); + const installTheme = useCSSLoaderAction("installTheme"); + + const [collapsed, setCollapsed] = useState(true); + const [render, rerender] = useForcedRerender(); + + let updateStatus: LocalThemeStatus = "installed"; + const themeArrPlace = updateStatuses.find((f) => f[0] === theme.id); + if (themeArrPlace) updateStatus = themeArrPlace[1]; + const isOutdated = updateStatus === "outdated"; + + // Re-collapse the theme when the theme is updated + useEffect(() => { + setCollapsed(true); + }, [theme.enabled]); + + if (!render) return null; + return ( + <> + + { + // @ts-ignore + showModal(); + }} + onSecondaryActionDescription={isOutdated ? "Update Theme" : undefined} + onSecondaryButton={ + isOutdated + ? async () => { + await installTheme(theme.id); + } + : undefined + } + > + {isOutdated &&
} + 0 ? "none" : "standard"} + checked={theme.enabled} + label={theme.display_name} + description={`${updateStatus === "outdated" ? "Update Available" : theme.version} | ${ + theme.author + }`} + onChange={async (switchValue: boolean) => { + toggleThemeWithModals(theme, switchValue, rerender); + }} + /> + + + {theme.enabled && theme.patches.length > 0 && ( + <> +
+ + setCollapsed(!collapsed)} + > + {collapsed ? ( + + ) : ( + + )} + + +
+ {!collapsed && + theme.patches.map((patch, index) => ( + + ))} + + )} + + ); +} diff --git a/src/modules/qam-tab-page/components/index.ts b/src/modules/qam-tab-page/components/index.ts new file mode 100644 index 0000000..9118669 --- /dev/null +++ b/src/modules/qam-tab-page/components/index.ts @@ -0,0 +1,5 @@ +export * from "./QamDummyFunctionBoundary"; +export * from "./QamRefreshButton"; +export * from "./QamThemeList"; +export * from "./QamHiddenThemesDisplay"; +export * from "./QamThemeToggle"; diff --git a/src/modules/qam-tab-page/index.ts b/src/modules/qam-tab-page/index.ts new file mode 100644 index 0000000..c4e34b2 --- /dev/null +++ b/src/modules/qam-tab-page/index.ts @@ -0,0 +1 @@ +export * from "./pages"; diff --git a/src/modules/qam-tab-page/pages/QamTabPage.tsx b/src/modules/qam-tab-page/pages/QamTabPage.tsx new file mode 100644 index 0000000..b93a535 --- /dev/null +++ b/src/modules/qam-tab-page/pages/QamTabPage.tsx @@ -0,0 +1,27 @@ +import { PanelSection } from "@decky/ui"; +import { MOTDDisplay, PresetSelectionDropdown } from "@/lib"; +import { + QamDummyFunctionBoundary, + QamHiddenThemesDisplay, + QamRefreshButton, + QamThemeList, +} from "../components"; +import { useCSSLoaderStateValue } from "@/backend"; + +export function QamTabPage() { + const themes = useCSSLoaderStateValue("themes"); + + return ( + <> + + + + {themes.length > 0 && } + + + + + + + ); +} diff --git a/src/modules/qam-tab-page/pages/index.ts b/src/modules/qam-tab-page/pages/index.ts new file mode 100644 index 0000000..a7e9004 --- /dev/null +++ b/src/modules/qam-tab-page/pages/index.ts @@ -0,0 +1 @@ +export * from "./QamTabPage"; diff --git a/src/modules/theme-store/components/BrowserSearchFields.tsx b/src/modules/theme-store/components/BrowserSearchFields.tsx new file mode 100644 index 0000000..2d9161f --- /dev/null +++ b/src/modules/theme-store/components/BrowserSearchFields.tsx @@ -0,0 +1,106 @@ +import { useMemo } from "react"; +import { + useThemeBrowserSharedStateAction, + useThemeBrowserSharedStateValue, + useThemeBrowserStoreAction, + useThemeBrowserStoreValue, +} from "../context"; +import { + DialogButton, + Dropdown, + DropdownOption, + Focusable, + PanelSectionRow, + SliderField, + TextField, +} from "@decky/ui"; +import { FilterOptionLabel } from "./FilterOptionLabel"; +import { FaRotate } from "react-icons/fa6"; + +export function BrowserSearchFields() { + const { filters, order } = useThemeBrowserStoreValue("filterOptions"); + const searchOpts = useThemeBrowserStoreValue("searchOpts"); + const setSearchOpts = useThemeBrowserStoreAction("setSearchOpts"); + const refreshThemes = useThemeBrowserStoreAction("refreshThemes"); + + const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize"); + const setBrowserCardSize = useThemeBrowserSharedStateAction("setBrowserCardSize"); + + const formattedFilters: DropdownOption[] = useMemo(() => { + const totalNumOptions = Object.values(filters).reduce((acc, cur) => acc + Number(cur), 0); + return [ + { data: "All", label: }, + ...Object.entries(filters) + .filter(([_, itemCount]) => Number(itemCount) > 0) + .map(([name, itemCount]) => ({ + data: name, + label: , + })), + ]; + }, [filters]); + + return ( + <> + + +
+ Sort + ({ data: e, label: e }))} + strDefaultLabel="Last Updated" + selectedOption={searchOpts.order} + onChange={(value) => { + const newSearchOpts = { ...searchOpts, order: value.data }; + setSearchOpts(newSearchOpts); + }} + /> +
+
+ Filter + { + const newSearchOpts = { ...searchOpts, filters: value.data }; + setSearchOpts(newSearchOpts); + }} + /> +
+
+
+ + +
+ { + const newSearchOpts = { ...searchOpts, search: event.target.value }; + setSearchOpts(newSearchOpts); + }} + /> +
+ + + Refresh + +
+ +
+
+
+ + ); +} diff --git a/src/modules/theme-store/components/FilterOptionLabel.tsx b/src/modules/theme-store/components/FilterOptionLabel.tsx new file mode 100644 index 0000000..3fde8a8 --- /dev/null +++ b/src/modules/theme-store/components/FilterOptionLabel.tsx @@ -0,0 +1,14 @@ +export function FilterOptionLabel({ + text, + itemCount, +}: { + text: string; + itemCount: number | string; +}) { + return ( +
+ {text} + {itemCount} +
+ ); +} diff --git a/src/modules/theme-store/components/ThemeBrowserPage.tsx b/src/modules/theme-store/components/ThemeBrowserPage.tsx new file mode 100644 index 0000000..e0538f7 --- /dev/null +++ b/src/modules/theme-store/components/ThemeBrowserPage.tsx @@ -0,0 +1,11 @@ +import { Focusable } from "@decky/ui"; +import { BrowserSearchFields } from "./BrowserSearchFields"; + +export function ThemeBrowserPage() { + return ( + <> + + + + ); +} diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx new file mode 100644 index 0000000..dcce9f9 --- /dev/null +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -0,0 +1,15 @@ +import { PartialCSSThemeInfo } from "@/types"; +import { useThemeBrowserSharedStateValue } from "../context"; +import { forwardRef } from "react"; + +interface ThemeCardProps { + theme: PartialCSSThemeInfo; + size?: number; +} + +export const ThemeCard = forwardRef(({ theme, size }, ref) => { + const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize"); + const cols = size ?? browserCardSize; + + return null; +}); diff --git a/src/modules/theme-store/components/ThemeGridDisplay.tsx b/src/modules/theme-store/components/ThemeGridDisplay.tsx new file mode 100644 index 0000000..eb7bd94 --- /dev/null +++ b/src/modules/theme-store/components/ThemeGridDisplay.tsx @@ -0,0 +1,13 @@ +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/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx new file mode 100644 index 0000000..6cc71b2 --- /dev/null +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -0,0 +1,33 @@ +// This is for things that are shared across the entire Theme Browser page and all tabs. + +import { createStore, useStore } from "zustand"; + +interface ThemeBrowserSharedStoreValues { + browserCardSize: number; +} + +interface ThemeBrowserSharedStoreActions { + setBrowserCardSize: (value: number) => void; +} + +interface IThemeBrowserSharedStore + extends ThemeBrowserSharedStoreValues, + ThemeBrowserSharedStoreActions {} + +const themeBrowserSharedStore = createStore((set) => { + return { + browserCardSize: 3, + setBrowserCardSize: (value: number) => set({ browserCardSize: value }), + }; +}); + +const useThemeBrowserSharedState = (fn: (state: IThemeBrowserSharedStore) => any) => + useStore(themeBrowserSharedStore, fn); + +export const useThemeBrowserSharedStateValue = ( + key: T +): IThemeBrowserSharedStore[T] => useThemeBrowserSharedState((state) => state[key]); + +export const useThemeBrowserSharedStateAction = ( + key: T +): IThemeBrowserSharedStore[T] => useThemeBrowserSharedState((state) => state[key]); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx new file mode 100644 index 0000000..4fe8da8 --- /dev/null +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -0,0 +1,142 @@ +import { createContext, useContext, useRef } from "react"; +import { FilterQueryResponse, ThemeQueryRequest, ThemeQueryResponse } from "@/types"; +import { StoreApi, createStore, useStore } from "zustand"; +import { getCSSLoaderState } from "@/backend"; + +interface ThemeBrowserStoreValues { + themes: ThemeQueryResponse; + searchOpts: ThemeQueryRequest; + prevSearchOpts: ThemeQueryRequest; + filterOptions: FilterQueryResponse; + indexToSnapToOnLoad: number; +} + +interface ThemeBrowserStoreActions { + initializeStore: () => Promise; + getFilters: () => Promise; + setSearchOpts: (searchOpts: ThemeQueryRequest) => void; + refreshThemes: () => Promise; + getThemes: () => Promise; +} + +interface IThemeBrowserStore extends ThemeBrowserStoreValues, ThemeBrowserStoreActions {} + +const ThemeBrowserStoreContext = createContext | null>(null); + +function generateParamStr(searchOpts: ThemeQueryRequest) { + let prependString = "BPM-CSS.-Preset"; + switch (searchOpts.filters) { + // If it's desktop themes, remove the "BPM Only" filter in the default + case "Desktop": + prependString = "-Preset"; + break; + // If it's presets, remove the preset exclusion in the default + case "Preset": + prependString = "BPM-CSS"; + break; + } + searchOpts.filters === "All" ? (searchOpts.filters = "") : (prependString += "."); + prependString && (searchOpts.filters = prependString + searchOpts.filters); + + // @ts-expect-error + const paramStr = new URLSearchParams(searchOpts).toString(); + return paramStr; +} + +export function ThemeBrowserStoreProvider({ + children, + filterPath, + themePath, + requiresAuth = false, +}: { + children: React.ReactNode; + filterPath: string; + themePath: string; + requiresAuth?: boolean; +}) { + const storeRef = useRef | null>(null); + + if (!storeRef.current) { + storeRef.current = createStore((set, get) => ({ + themes: { total: 0, items: [] }, + searchOpts: { + page: 1, + perPage: 50, + filters: "All", + order: "Last Updated", + search: "", + }, + prevSearchOpts: { + page: 1, + perPage: 50, + filters: "All", + order: "Last Updated", + search: "", + }, + filterOptions: { + filters: [], + order: ["Last Updated"], + }, + indexToSnapToOnLoad: -1, + initializeStore: async () => { + try { + await get().getFilters(); + } catch (error) {} + }, + getFilters: async () => { + const { apiFetch } = getCSSLoaderState(); + try { + const response = await apiFetch( + `${filterPath}?type=CSS`, + {}, + requiresAuth + ); + if (response.filters) { + set({ filterOptions: response }); + } + } catch (error) {} + }, + setSearchOpts(searchOpts) { + const prevSearchOpts = get().searchOpts; + set({ searchOpts, prevSearchOpts }); + }, + refreshThemes: async () => {}, + getThemes: async () => { + try { + const { searchOpts } = get(); + + const { apiFetch } = getCSSLoaderState(); + const response = await apiFetch( + `${themePath}?${generateParamStr(searchOpts)}`, + {}, + requiresAuth + ); + if (response.items) { + set({ themes: response, indexToSnapToOnLoad: -1 }); + } + } catch (error) {} + }, + })); + } + + return ( + + {children} + + ); +} + +export const useThemeBrowserStore = (selector: (state: IThemeBrowserStore) => T) => { + const store = useContext(ThemeBrowserStoreContext); + if (!store) { + throw new Error("Missing StoreProvider"); + } + return useStore(store, selector); +}; +export const useThemeBrowserStoreValue = ( + key: T +): IThemeBrowserStore[T] => useThemeBrowserStore((state) => state[key]); + +export const useThemeBrowserStoreAction = ( + key: T +): IThemeBrowserStore[T] => useThemeBrowserStore((state) => state[key]); diff --git a/src/modules/theme-store/context/index.ts b/src/modules/theme-store/context/index.ts new file mode 100644 index 0000000..22277fe --- /dev/null +++ b/src/modules/theme-store/context/index.ts @@ -0,0 +1,2 @@ +export * from "./ThemeBrowserStore"; +export * from "./ThemeBrowserSharedStore"; diff --git a/src/pages/settings/Credits.tsx b/src/pages/settings/Credits.tsx deleted file mode 100644 index fadf189..0000000 --- a/src/pages/settings/Credits.tsx +++ /dev/null @@ -1,40 +0,0 @@ -export function Credits() { - return ( -
-
-
-

Developers

-
    -
  • - SuchMeme - github.com/suchmememanyskill -
  • -
  • - Beebles - github.com/beebls -
  • -
  • - EMERALD - github.com/EMERALD0874 -
  • -
-
-
-

Support

- - See the DeckThemes Discord server for support. -
- discord.gg/HsU72Kfnpf -
-
-
-

- Create and Submit Your Own Theme -

- - Instructions for theme creation/submission are available DeckThemes' docs website. -
- docs.deckthemes.com -
-
-
-
- ); -} diff --git a/src/pages/settings/DonatePage.tsx b/src/pages/settings/DonatePage.tsx deleted file mode 100644 index 6d60556..0000000 --- a/src/pages/settings/DonatePage.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { - DialogButton, - Focusable, - Navigation, - Panel, - PanelSection, - ScrollPanelGroup, -} from "decky-frontend-lib"; -import { useEffect, useMemo, useState } from "react"; -import { SiKofi, SiPatreon } from "react-icons/si"; -import { server } from "../../python"; - -export function DonatePage() { - const [loaded, setLoaded] = useState(false); - const [supporters, setSupporters] = useState(""); - - const formattedSupporters = useMemo(() => { - const numOfNamesPerPage = 10; - const supportersArr = supporters.split("\n"); - const newArr = []; - for (let i = 0; i < supportersArr.length; i += numOfNamesPerPage) { - newArr.push(supportersArr.slice(i, i + numOfNamesPerPage).join("\n")); - } - return newArr; - }, [supporters]); - - function fetchSupData() { - server! - .fetchNoCors("https://api.deckthemes.com/patrons", { method: "GET" }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } - throw new Error("unsuccessful"); - }) - .then((res) => { - if (res.status === 200) { - return res.body; - } - throw new Error("Res not OK"); - }) - .then((text) => { - if (text) { - setLoaded(true); - setSupporters(text); - } - }) - .catch((err) => { - console.error("CSS Loader - Error Fetching Supporter Data", err); - }); - } - useEffect(() => { - fetchSupData(); - }, []); - return ( -
- -

- Donations help to cover the costs of hosting the store, as well as funding development for - CSS Loader and its related projects. -

- - Navigation.NavigateToExternalWeb("https://patreon.com/deckthemes")} - focusWithinClassName="gpfocuswithin" - className="patreon-or-kofi-container patreon" - > -
- - Patreon -
- Recurring Donation - patreon.com/deckthemes - Perks: -
    -
  • - {/* Potentially could expand this to add it to deckthemes and audioloader */} - Your name in CSS Loader -
  • -
  • Patreon badge on deckthemes.com
  • -
  • - {/* Could also impl. this on deck store to make it more meaningful */} - Colored name + VIP channel in the DeckThemes Discord -
  • -
-
- Navigation.NavigateToExternalWeb("https://ko-fi.com/suchmememanyskill")} - focusWithinClassName="gpfocuswithin" - className="patreon-or-kofi-container" - > -
- - Ko-Fi -
- One-time Donation - ko-fi.com/suchmememanyskill -
-
- {loaded ? ( -
- - {formattedSupporters.map((e) => { - return ( - {}} focusWithinClassName="gpfocuswithin"> -

{e}

-
- ); - })} -
-
- ) : null} -
- ); -} diff --git a/src/pages/settings/PluginSettings.tsx b/src/pages/settings/PluginSettings.tsx deleted file mode 100644 index a929ded..0000000 --- a/src/pages/settings/PluginSettings.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { DropdownItem, Focusable, ToggleField } from "decky-frontend-lib"; -import { useMemo, useState, useEffect } from "react"; -import { useCssLoaderState } from "../../state"; -import { toast } from "../../python"; -import { setNavPatch } from "../../deckyPatches/NavPatch"; -import { - getWatchState, - getServerState, - enableServer, - toggleWatchState, - getBetaTranslationsState, - fetchClassMappings, -} from "../../backend/pythonMethods/pluginSettingsMethods"; -import { booleanStoreWrite, stringStoreWrite } from "../../backend/pythonMethods/storeUtils"; -import { disableUnminifyMode, enableUnminifyMode } from "../../deckyPatches/UnminifyMode"; - -export function PluginSettings() { - const { navPatchInstance, unminifyModeOn, setGlobalState } = useCssLoaderState(); - const [serverOn, setServerOn] = useState(false); - const [watchOn, setWatchOn] = useState(false); - const [betaTranslationsOn, setBetaTranslationsOn] = useState("-1"); - - const navPatchEnabled = useMemo(() => !!navPatchInstance, [navPatchInstance]); - - async function fetchServerState() { - const value = await getServerState(); - setServerOn(value); - } - async function fetchWatchState() { - const value = await getWatchState(); - setWatchOn(value); - } - async function fetchBetaTranslationsState() { - const value = await getBetaTranslationsState(); - if (!["0", "1", "-1"].includes(value)) { - setBetaTranslationsOn("-1"); - return; - } - setBetaTranslationsOn(value); - } - - useEffect(() => { - void fetchServerState(); - void fetchWatchState(); - void fetchBetaTranslationsState(); - }, []); - - function setUnminify(enabled: boolean) { - setGlobalState("unminifyModeOn", enabled); - if (enabled) { - enableUnminifyMode(); - return; - } - disableUnminifyMode(); - } - - async function setWatch(enabled: boolean) { - await toggleWatchState(enabled, false); - await fetchWatchState(); - } - - async function setServer(enabled: boolean) { - if (enabled) await enableServer(); - await booleanStoreWrite("server", enabled); - await fetchServerState(); - } - - async function setBetaTranslations(value: string) { - await stringStoreWrite("beta_translations", value); - await fetchClassMappings(); - await fetchBetaTranslationsState(); - } - - return ( -
- - setBetaTranslations(data.data)} - /> - - - { - setServer(value); - }} - /> - - - setNavPatch(value, true)} - /> - - - - - - - -
- ); -} diff --git a/src/pages/settings/PresetSettings.tsx b/src/pages/settings/PresetSettings.tsx deleted file mode 100644 index 15b6bed..0000000 --- a/src/pages/settings/PresetSettings.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { useCssLoaderState } from "../../state"; -import { PartialCSSThemeInfo } from "../../apiTypes"; -import { genericGET, installTheme, logInWithShortToken } from "../../api"; -import * as python from "../../python"; -import { PresetSelectionDropdown, UploadProfileModalRoot } from "../../components"; -import { Flags, Theme } from "../../ThemeTypes"; -import { DialogButton, Focusable, showModal } from "decky-frontend-lib"; -import { FullscreenCloudProfileEntry } from "../../components/ThemeSettings/FullscreenCloudProfileEntry"; -import { FullscreenProfileEntry } from "../../components/ThemeSettings/FullscreenProfileEntry"; -import { PremiumFeatureModal } from "../../components/Modals/PremiumFeatureModal"; - -export function PresetSettings() { - const { localThemeList, setGlobalState, updateStatuses } = useCssLoaderState(); - - const [isInstalling, setInstalling] = useState(false); - - const { apiFullToken, apiShortToken, apiMeData } = useCssLoaderState(); - - const [uploadedProfiles, setUploadedProfiles] = useState< - (PartialCSSThemeInfo & { isPrivate?: boolean })[] - >([]); - - const [profilesLoaded, setLoaded] = useState(false); - - async function getUserProfiles() { - setLoaded(false); - if (!apiFullToken) { - await logInWithShortToken(); - } - - // Since the short token could be invalid, we still have to re-check for if the log in actually worked. - // The react value doesn't update mid function, so we re-grab it. - const upToDateFullToken = python.globalState?.getGlobalState("apiFullToken"); - if (!upToDateFullToken) return; - - let profileArray: PartialCSSThemeInfo[] = []; - const publicProfileData = await genericGET("/users/me/themes?filters=Profile", true); - if (publicProfileData && publicProfileData.total > 0) { - profileArray.push(...publicProfileData.items); - } - const privateProfileData = await genericGET("/users/me/themes/private?filters=Profile", true); - if (privateProfileData && privateProfileData.total > 0) { - profileArray.push( - ...privateProfileData.items.map((e: PartialCSSThemeInfo) => ({ ...e, isPrivate: true })) - ); - } - profileArray.sort((a, b) => (a.name > b.name ? 1 : -1)); - setUploadedProfiles(profileArray); - - setLoaded(true); - } - - useEffect(() => { - if (apiShortToken) void getUserProfiles(); - }, []); - - type MergedCloudProfile = { isCloud: true; data: PartialCSSThemeInfo & { isPrivate: boolean } }; - type MergedNormalProfile = { isCloud: false; data: Theme }; - - const mergedProfileList: (MergedCloudProfile | MergedNormalProfile)[] = useMemo(() => { - const filteredLocalProfiles: MergedNormalProfile[] = localThemeList - .filter((e) => e.flags.includes(Flags.isPreset)) - .filter((e) => { - if (uploadedProfiles.some((f) => f.id === e.id)) return false; - return true; - }) - .map((e) => ({ isCloud: false, data: e })); - - const filteredCloudProfiles = uploadedProfiles.map((e) => ({ - isCloud: true, - data: e, - })) as MergedCloudProfile[]; - - return [ - ...filteredLocalProfiles.sort((a, b) => (a.data.name > b.data.name ? 1 : -1)), - ...filteredCloudProfiles.sort((a, b) => (a.data.name > b.data.name ? 1 : -1)), - ]; - }, [uploadedProfiles, localThemeList]); - - function onUploadFinish() { - void getUserProfiles(); - } - - async function handleUpdate(e: Theme | PartialCSSThemeInfo) { - setInstalling(true); - await installTheme(e.id); - // This just updates the updateStatuses arr to know that this theme now is up to date, no need to re-fetch the API to know that - setGlobalState( - "updateStatuses", - updateStatuses.map((f) => (f[0] === e.id ? [e.id, "installed", false] : f)) - ); - setInstalling(false); - } - - async function handleUninstall(listEntry: Theme | PartialCSSThemeInfo) { - setInstalling(true); - await python.deleteTheme(listEntry.name); - await python.reloadBackend(); - setGlobalState( - "updateStatuses", - updateStatuses.filter((f) => f[0] !== listEntry.id) - ); - setInstalling(false); - } - - return ( -
- - - {mergedProfileList.map((e) => { - if (e.isCloud) { - return ( - - ); - } - return ( - - ); - })} - - - {apiMeData ? ( - <> - { - if (apiMeData.premiumTier && apiMeData.premiumTier !== "None") { - showModal(); - return; - } - showModal( - - ); - return; - }} - > - Upload Profile - - - ) : ( - <> - {apiShortToken ? ( - Loading - ) : ( - - You are not logged in. Log In with your DeckThemes account to view your uploaded - profiles. - - )} - - )} - -
- ); -} diff --git a/src/pages/settings/SettingsPageRouter.tsx b/src/pages/settings/SettingsPageRouter.tsx deleted file mode 100644 index 365e32a..0000000 --- a/src/pages/settings/SettingsPageRouter.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { SidebarNavigation } from "decky-frontend-lib"; -import { BsFolderFill, BsGearFill } from "react-icons/bs"; -import { RiPaintFill, RiSettings2Fill } from "react-icons/ri"; -import { ThemeSettings } from "./ThemeSettings"; -import { PresetSettings } from "./PresetSettings"; -import { PluginSettings } from "./PluginSettings"; -import { Credits } from "./Credits"; -import { DonatePage } from "./DonatePage"; -import { FaFolder, FaGithub, FaHeart } from "react-icons/fa"; -import { ThemeBrowserCardStyles } from "../../components/Styles"; - -export function SettingsPageRouter() { - return ( - <> - - - , - route: "/cssloader/settings/themes", - content: , - }, - { - title: "Profiles", - icon: , - route: "/cssloader/settings/profiles", - - content: , - }, - { - title: "Settings", - icon: , - route: "/cssloader/settings/plugin", - - content: , - }, - { - title: "Donate", - icon: , - route: "/cssloader/settings/donate", - - content: , - }, - { - title: "Credits", - icon: , - route: "/cssloader/settings/credits", - - content: , - }, - ]} - > - - ); -} diff --git a/src/pages/settings/ThemeSettings.tsx b/src/pages/settings/ThemeSettings.tsx deleted file mode 100644 index 9e95784..0000000 --- a/src/pages/settings/ThemeSettings.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { DialogButton, DialogCheckbox, Focusable, PanelSection } from "decky-frontend-lib"; -import { useCssLoaderState } from "../../state"; -import { useMemo, useState } from "react"; -import { Flags, Theme } from "../../ThemeTypes"; -import { FullscreenSingleThemeEntry } from "../../components/ThemeSettings/FullscreenSingleThemeEntry"; -import { ThemeErrorCard } from "../../components/ThemeErrorCard"; -import { installTheme } from "../../api"; -import * as python from "../../python"; -import { DeleteMenu } from "../../components/ThemeSettings/DeleteMenu"; -import { UpdateAllThemesButton } from "../../components/ThemeSettings/UpdateAllThemesButton"; - -export function ThemeSettings() { - const { localThemeList, unpinnedThemes, themeErrors, setGlobalState, updateStatuses } = - useCssLoaderState(); - - const [isInstalling, setInstalling] = useState(false); - const [mode, setMode] = useState<"view" | "delete">("view"); - - const sortedList = useMemo(() => { - return localThemeList - .filter((e) => !e.flags.includes(Flags.isPreset)) - .sort((a, b) => { - const aPinned = !unpinnedThemes.includes(a.id); - const bPinned = !unpinnedThemes.includes(b.id); - // This sorts the pinned themes alphabetically, then the non-pinned alphabetically - if (aPinned === bPinned) { - return a.name.localeCompare(b.name); - } - return Number(bPinned) - Number(aPinned); - }); - }, [localThemeList.length]); - - async function handleUpdate(e: Theme) { - setInstalling(true); - const unpinned = unpinnedThemes.includes(e.id); - await installTheme(e.id); - - // This just updates the updateStatuses arr to know that this theme now is up to date, no need to re-fetch the API to know that - setGlobalState( - "updateStatuses", - updateStatuses.map((f) => (f[0] === e.id ? [e.id, "installed", false] : e)) - ); - // Remove duplicate theme from unpinned list. - if (unpinned) python.pinTheme(e.id); - setInstalling(false); - } - - async function handleUninstall(listEntry: Theme) { - setInstalling(true); - await python.deleteTheme(listEntry.name); - if (unpinnedThemes.includes(listEntry.id)) { - // This isn't really pinning it, it's just removing its name from the unpinned list. - python.pinTheme(listEntry.id); - } - await python.reloadBackend(); - setInstalling(false); - } - - return ( -
- - - - - (mode === "delete" ? setMode("view") : setMode("delete"))} - > - {mode === "delete" ? "Go Back" : "Delete Themes"} - - - - {mode === "view" && ( - <> - - {sortedList.map((e) => ( - - ))} - - - )} - {mode === "delete" && ( - setMode("view")} themeList={sortedList} /> - )} - - - {themeErrors.length > 0 && ( - - - {themeErrors.map((e) => { - return ; - })} - - - )} -
- ); -} diff --git a/src/pages/theme-manager/ExpandedView.tsx b/src/pages/theme-manager/ExpandedView.tsx deleted file mode 100644 index b2170f1..0000000 --- a/src/pages/theme-manager/ExpandedView.tsx +++ /dev/null @@ -1,398 +0,0 @@ -import { - DialogButton, - Focusable, - Navigation, - showModal, - ScrollPanelGroup, -} from "decky-frontend-lib"; -import { useEffect, useRef, useState, VFC } from "react"; -import { ImCog } from "react-icons/im"; - -import * as python from "../../python"; -import { genericGET, refreshToken, toggleStar as apiToggleStar, installTheme } from "../../api"; - -import { useCssLoaderState } from "../../state"; -import { Theme } from "../../ThemeTypes"; -import { FullCSSThemeInfo, PartialCSSThemeInfo } from "../../apiTypes"; -import { ThemeSettingsModalRoot } from "../../components/Modals/ThemeSettingsModal"; -import { AuthorViewModalRoot } from "../../components/Modals/AuthorViewModal"; -import { ExpandedViewStyles } from "../../components/Styles"; -import { shortenNumber } from "../../logic/numbers"; -import { FaRegStar, FaStar } from "react-icons/fa"; -import { Loading } from "../../components"; - -export const ExpandedViewPage: VFC = () => { - const { - localThemeList: installedThemes, - currentExpandedTheme, - isInstalling, - apiFullToken, - themeSearchOpts, - setGlobalState, - } = useCssLoaderState(); - - const [fullThemeData, setFullData] = useState(); - const [loaded, setLoaded] = useState(false); - const [isStarred, setStarred] = useState(false); - const [blurStarButton, setBlurStar] = useState(false); - - async function getStarredStatus() { - if (fullThemeData) { - genericGET(`/users/me/stars/${fullThemeData.id}`, true).then((data) => { - if (data.starred) { - setStarred(data.starred); - } - if (data.starred && fullThemeData?.starCount === 0) { - setFullData({ - ...fullThemeData, - starCount: 1, - }); - } - }); - } - } - - async function toggleStar() { - if (apiFullToken) { - setBlurStar(true); - const newToken = await refreshToken(); - if (fullThemeData && newToken) { - apiToggleStar(fullThemeData.id, isStarred, newToken).then((bool) => { - if (bool) { - setFullData({ - ...fullThemeData, - starCount: isStarred - ? fullThemeData.starCount === 0 - ? // This stops it from going below 0 - fullThemeData.starCount - : fullThemeData.starCount - 1 - : fullThemeData.starCount + 1, - }); - setStarred((cur) => !cur); - setBlurStar(false); - } - }); - } - } else { - python.toast("Not Logged In!", "You can only star themes if logged in."); - } - } - - function checkIfThemeInstalled(themeObj: PartialCSSThemeInfo) { - const filteredArr: Theme[] = installedThemes.filter( - (e: Theme) => e.name === themeObj.name && e.author === themeObj.specifiedAuthor - ); - if (filteredArr.length > 0) { - if (filteredArr[0].version === themeObj.version) { - return "installed"; - } else { - return "outdated"; - } - } else { - return "uninstalled"; - } - } - // These are just switch statements I use to determine text/css for the buttons - // I put them up here just because I find it clearer to read when they aren't inline - function calcButtonText(installStatus: string) { - let buttonText = ""; - switch (installStatus) { - case "installed": - buttonText = "Reinstall"; - break; - case "outdated": - buttonText = "Update"; - break; - default: - buttonText = "Install"; - break; - } - return buttonText; - } - - // For some reason, setting the ref as the useEffect dependency didn't work... - const downloadButtonRef = useRef(null); - const [hasBeenFocused, setHasFocused] = useState(false); - useEffect(() => { - if (downloadButtonRef?.current && !hasBeenFocused) { - downloadButtonRef.current.focus(); - setHasFocused(true); - } - }); - - useEffect(() => { - if (currentExpandedTheme?.id) { - setLoaded(false); - setFocusedImage(0); - genericGET(`/themes/${currentExpandedTheme.id}`).then((data) => { - setFullData(data); - setLoaded(true); - }); - } - }, [currentExpandedTheme]); - - useEffect(() => { - if (apiFullToken && fullThemeData) { - getStarredStatus(); - } - }, [apiFullToken, fullThemeData]); - - const [focusedImage, setFocusedImage] = useState(0); - - if (!loaded) - return ( -
- -
- ); - - // if theres no theme in the detailed view - if (fullThemeData) { - const imageAreaWidth = 556; - const imageAreaPadding = 16; - const gapBetweenCarouselAndImage = 8; - const selectedImageWidth = - fullThemeData.images.length > 1 ? 434.8 : imageAreaWidth - imageAreaPadding * 2; - const selectedImageHeight = (selectedImageWidth / 16) * 10; - const imageCarouselEntryWidth = - imageAreaWidth - imageAreaPadding * 2 - selectedImageWidth - gapBetweenCarouselAndImage; - const imageCarouselEntryHeight = (imageCarouselEntryWidth / 16) * 10; - - // This returns 'installed', 'outdated', or 'uninstalled' - const installStatus = checkIfThemeInstalled(fullThemeData); - return ( - <> - - - { - if (!evt?.detail?.button) return; - if (evt.detail.button === 2) { - Navigation.NavigateBack(); - } - }} - > - {/* Img + Info */} - - {/* Images */} - - {/* Vertical Image Carousel */} - {fullThemeData.images.length > 1 && ( - - {fullThemeData.images.map((e, id) => { - return ( - { - setFocusedImage(id); - }} - className="image-carousel-entry" - focusWithinClassName="gpfocuswithin" - onActivate={() => {}} - > - - - ); - })} - - )} - - {/* Selected Image Display */} - {}} - > - 0 - ? `https://api.deckthemes.com/blobs/${fullThemeData.images?.[focusedImage]?.id}` - : `https://share.deckthemes.com/cssplaceholder.png` - } - /> - {fullThemeData.images.length > 1 && ( -
- - {focusedImage + 1}/{fullThemeData.images.length} - -
- )} -
-
- - - {/* Info */} -
-
- {fullThemeData.displayName} - {fullThemeData.version} -
-
- { - showModal(); - }} - > - By {fullThemeData.specifiedAuthor} - - Last Updated {new Date(fullThemeData.updated).toLocaleDateString()} -
-
- {/* Description */} - {}} - > - Description - 400 ? "text-sm" : ""}> - {fullThemeData?.description || ( - - No description provided. - - )} - - - {/* Targets */} - - Targets - - {fullThemeData.targets.map((e) => ( - { - setGlobalState("themeSearchOpts", { ...themeSearchOpts, filters: e }); - setGlobalState("currentTab", "ThemeBrowser"); - setGlobalState("forceScrollBackUp", true); - Navigation.NavigateBack(); - }} - className="target-text" - > - {e} - - ))} - - -
-
-
- {/* Buttons */} - -
-
- {isStarred ? : } - {/* Need to make the text size smaller or else it wraps */} - = 100 ? "0.75em" : "1em" }}> - {shortenNumber(fullThemeData.starCount) ?? fullThemeData.starCount} Star - {fullThemeData.starCount === 1 ? "" : "s"} - -
- -
- - {!apiFullToken ? "Log In to Star" : isStarred ? "Unstar Theme" : "Star Theme"} - -
-
-
-
- Install {fullThemeData.displayName} - - {shortenNumber(fullThemeData.download.downloadCount) ?? - fullThemeData.download.downloadCount}{" "} - Download - {fullThemeData.download.downloadCount === 1 ? "" : "s"} - - - { - installTheme(fullThemeData.id); - }} - > - - {calcButtonText(installStatus)} - - - {installStatus === "installed" && ( - { - showModal( - e.id === fullThemeData.id)?.id || - // using name here because in submissions id is different - installedThemes.find((e) => e.name === fullThemeData.name)!.id - } - /> - ); - }} - className="configure-button" - > - - - )} - -
-
-
- - ); - } - return ( - <> -
- Error fetching selected theme, please go back and retry. -
- - ); -}; diff --git a/src/pages/theme-manager/LogInPage.tsx b/src/pages/theme-manager/LogInPage.tsx deleted file mode 100644 index 7c2a6a9..0000000 --- a/src/pages/theme-manager/LogInPage.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { DialogButton, Focusable, TextField, ToggleField } from "decky-frontend-lib"; -import { SiWebauthn } from "react-icons/si"; -import { useEffect, useMemo, useState, VFC } from "react"; -import { logInWithShortToken, logOut } from "../../api"; -import { useCssLoaderState } from "../../state"; -import { enableServer, getServerState, storeWrite } from "../../python"; -import { disableNavPatch, enableNavPatch } from "../../deckyPatches/NavPatch"; -import { FaArrowRightToBracket } from "react-icons/fa6"; - -export const LogInPage: VFC = () => { - const { apiShortToken, apiFullToken, apiMeData } = useCssLoaderState(); - const [shortTokenInterimValue, setShortTokenIntValue] = useState(apiShortToken); - - return ( - // The outermost div is to push the content down into the visible area -
-
- {apiFullToken ? ( -

Your Account

- ) : ( -

- Log In -

- )} - {apiFullToken ? ( - <> - -
- {apiMeData ? ( - <> - Logged In As {apiMeData.username} - - ) : ( - Loading... - )} -
- - Unlink My Deck - -
- - ) : ( - <> - -
- setShortTokenIntValue(e.target.value)} - /> -
- { - logInWithShortToken(shortTokenInterimValue); - }} - style={{ - maxWidth: "30%", - height: "50%", - marginLeft: "auto", - display: "flex", - alignItems: "center", - justifyContent: "center", - gap: "0.5em", - }} - > - - Log In - -
- - )} -

- Logging in gives you access to star themes, saving them to their own page where you can - quickly find them. -
- Create an account on deckthemes.com and generate an account key on your profile page. -
-

-
-
- ); -}; diff --git a/src/pages/theme-manager/StarredThemesPage.tsx b/src/pages/theme-manager/StarredThemesPage.tsx deleted file mode 100644 index 1f96d8f..0000000 --- a/src/pages/theme-manager/StarredThemesPage.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Focusable } from "decky-frontend-lib"; -import { useCssLoaderState } from "../../state"; -import * as python from "../../python"; -import { BrowserSearchFields, LoadMoreButton, VariableSizeCard } from "../../components"; -import { useEffect, useRef, useState } from "react"; -import { isEqual } from "lodash"; -import { getThemes } from "../../api"; - -export function StarredThemesPage() { - const { - apiFullToken, - apiMeData, - starredSearchOpts: searchOpts, - starredServerFilters: serverFilters, - starredThemeList: themeArr, - browserCardSize, - prevStarSearchOpts: prevSearchOpts, - backendVersion, - } = useCssLoaderState(); - - function reloadThemes() { - getThemes(searchOpts, "/users/me/stars", "starredThemeList", setSnapIndex, true); - python.reloadBackend(); - } - - useEffect(() => { - if (!isEqual(prevSearchOpts, searchOpts) || themeArr.total === 0) { - getThemes(searchOpts, "/users/me/stars", "starredThemeList", setSnapIndex, true); - } - }, [searchOpts, prevSearchOpts, apiMeData]); - - const endOfPageRef = useRef(); - const [indexToSnapTo, setSnapIndex] = useState(-1); - useEffect(() => { - if (endOfPageRef?.current) { - endOfPageRef?.current?.focus(); - } - }, [indexToSnapTo]); - - if (!apiFullToken) { - return ( - <> -
- You Are Not Logged In! - Link your deck to your deckthemes.com account to sync Starred Themes -
- - ); - } - return ( - <> - - - {themeArr.items - .filter((e) => e.manifestVersion <= backendVersion) - .map((e, i) => ( - - ))} - -
-
- -
-
- - ); -} diff --git a/src/pages/theme-manager/SubmissionBrowserPage.tsx b/src/pages/theme-manager/SubmissionBrowserPage.tsx deleted file mode 100644 index 1b89cb1..0000000 --- a/src/pages/theme-manager/SubmissionBrowserPage.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Focusable } from "decky-frontend-lib"; -import { useCssLoaderState } from "../../state"; -import * as python from "../../python"; -import { BrowserSearchFields, LoadMoreButton, VariableSizeCard } from "../../components"; -import { useEffect, useRef, useState } from "react"; -import { isEqual } from "lodash"; -import { getThemes } from "../../api"; - -export function SubmissionsPage() { - const { - apiFullToken, - submissionSearchOpts: searchOpts, - submissionServerFilters: serverFilters, - submissionThemeList: themeArr, - browserCardSize, - prevSubSearchOpts: prevSearchOpts, - apiMeData, - backendVersion, - } = useCssLoaderState(); - - function reloadThemes() { - getThemes(searchOpts, "/themes/awaiting_approval", "submissionThemeList", setSnapIndex, true); - python.reloadBackend(); - } - - useEffect(() => { - if (!isEqual(prevSearchOpts, searchOpts) || themeArr.total === 0) { - getThemes(searchOpts, "/themes/awaiting_approval", "submissionThemeList", setSnapIndex, true); - } - }, [searchOpts, prevSearchOpts, apiMeData]); - - const endOfPageRef = useRef(); - const [indexToSnapTo, setSnapIndex] = useState(-1); - useEffect(() => { - if (endOfPageRef?.current) { - endOfPageRef?.current?.focus(); - } - }, [indexToSnapTo]); - - if (!apiFullToken) { - return ( - <> -
- You Are Not Logged In! - Link your deck to your deckthemes.com account to sync Starred Themes -
- - ); - } - return ( - <> - - - {themeArr.items - .filter((e) => e.manifestVersion <= backendVersion) - .map((e, i) => ( - - ))} - -
-
- -
-
- - ); -} diff --git a/src/pages/theme-manager/ThemeBrowserPage.tsx b/src/pages/theme-manager/ThemeBrowserPage.tsx deleted file mode 100644 index 1cfe69f..0000000 --- a/src/pages/theme-manager/ThemeBrowserPage.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Focusable } from "decky-frontend-lib"; -import { useLayoutEffect, useState, FC, useEffect, useRef } from "react"; -import * as python from "../../python"; -import { getThemes } from "../../api"; -import { logInWithShortToken } from "../../api"; -import { isEqual } from "lodash"; - -// Interfaces for the JSON objects the lists work with -import { useCssLoaderState } from "../../state"; -import { BrowserSearchFields, VariableSizeCard, LoadMoreButton } from "../../components"; - -export const ThemeBrowserPage: FC = () => { - const { - browseThemeList: themeArr, - themeSearchOpts: searchOpts, - apiShortToken, - apiFullToken, - serverFilters, - browserCardSize = 3, - prevSearchOpts, - backendVersion, - forceScrollBackUp, - setGlobalState, - } = useCssLoaderState(); - - function reloadThemes() { - getThemes(searchOpts, "/themes", "browseThemeList", setSnapIndex); - python.reloadBackend(); - } - - useEffect(() => { - if (!isEqual(prevSearchOpts, searchOpts) || themeArr.total === 0) { - getThemes(searchOpts, "/themes", "browseThemeList", setSnapIndex); - } - }, [searchOpts, prevSearchOpts]); - - // Runs upon opening the page every time - useLayoutEffect(() => { - python.getBackendVersion(); - if (apiShortToken && !apiFullToken) { - logInWithShortToken(); - } - // Installed themes aren't used on this page, but they are used on other pages, so fetching them here means that as you navigate to the others they will be already loaded - python.getInstalledThemes(); - }, []); - - const endOfPageRef = useRef(); - const firstCardRef = useRef(); - useLayoutEffect(() => { - if (forceScrollBackUp) { - // Valve would RE FOCUS THE ONE YOU LAST CLICKED ON after this ran, so i had to add a delay - setTimeout(() => { - firstCardRef?.current && firstCardRef.current?.focus(); - setGlobalState("forceScrollBackUp", false); - }, 100); - } - }, []); - - const [indexToSnapTo, setSnapIndex] = useState(-1); - useEffect(() => { - if (endOfPageRef?.current) { - endOfPageRef?.current?.focus(); - } - }, [indexToSnapTo]); - - return ( - <> - { - reloadThemes(); - }} - /> - {/* I wrap everything in a Focusable, because that ensures that the dpad/stick navigation works correctly */} - - {themeArr.items - .filter((e) => e.manifestVersion <= backendVersion) - .map((e, i) => ( - - ))} - -
-
- -
-
- - ); -}; diff --git a/src/pages/theme-manager/ThemeManagerRouter.tsx b/src/pages/theme-manager/ThemeManagerRouter.tsx deleted file mode 100644 index 24d1204..0000000 --- a/src/pages/theme-manager/ThemeManagerRouter.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Tabs } from "decky-frontend-lib"; -import { Permissions } from "../../apiTypes"; -import { useCssLoaderState } from "../../state"; -import { LogInPage } from "./LogInPage"; -import { StarredThemesPage } from "./StarredThemesPage"; -import { SubmissionsPage } from "./SubmissionBrowserPage"; -import { ThemeBrowserPage } from "./ThemeBrowserPage"; -import { ThemeBrowserCardStyles } from "../../components/Styles"; -export function ThemeManagerRouter() { - const { apiMeData, currentTab, setGlobalState } = useCssLoaderState(); - return ( -
- - { - setGlobalState("currentTab", tabID); - }} - tabs={[ - { - title: "All Themes", - content: , - id: "ThemeBrowser", - }, - ...(!!apiMeData - ? [ - { - title: "Starred Themes", - content: , - id: "StarredThemes", - }, - ...(apiMeData.permissions.includes(Permissions.viewSubs) - ? [ - { - title: "Submissions", - content: , - id: "SubmissionsPage", - }, - ] - : []), - ] - : []), - { - title: "DeckThemes Account", - content: , - id: "LogInPage", - }, - ]} - /> -
- ); -} diff --git a/src/pages/theme-manager/index.ts b/src/pages/theme-manager/index.ts deleted file mode 100644 index f16d7eb..0000000 --- a/src/pages/theme-manager/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./ThemeBrowserPage"; -export * from "./ExpandedView"; -export * from "./LogInPage"; -export * from "./StarredThemesPage"; -export * from "./SubmissionBrowserPage"; -export * from "./ThemeManagerRouter"; diff --git a/src/python.ts b/src/python.ts deleted file mode 100644 index 74f0d3b..0000000 --- a/src/python.ts +++ /dev/null @@ -1,274 +0,0 @@ -// Code from https://github.com/NGnius/PowerTools/blob/dev/src/python.ts -import { ServerAPI } from "decky-frontend-lib"; -import { CssLoaderState } from "./state"; -import { Theme, ThemeError } from "./ThemeTypes"; -import { bulkThemeUpdateCheck } from "./logic/bulkThemeUpdateCheck"; - -export var server: ServerAPI | undefined = undefined; -export var globalState: CssLoaderState | undefined = undefined; - -export function setServer(s: ServerAPI) { - server = s; -} -export function setStateClass(s: CssLoaderState): void { - globalState = s; -} - -export async function openFilePicker(path: string) { - return await server!.openFilePicker(path, true); -} - -export function fetchThemePath() { - return server!.callPluginMethod("fetch_theme_path", {}); -} - -export function resolve(promise: Promise, setter: any) { - (async function () { - let data = await promise; - if (data.success) { - console.debug("Got resolved", data, "promise", promise); - setter(data.result); - } else { - console.warn("Resolve failed:", data, "promise", promise); - } - })(); -} - -export function execute(promise: Promise) { - (async function () { - let data = await promise; - if (data.success) { - console.debug("Got executed", data, "promise", promise); - } else { - console.warn("Execute failed:", data, "promise", promise); - } - })(); -} - -export async function scheduleCheckForUpdates() { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - function recursiveCheck() { - const timeout = setTimeout(async () => { - // Putting this in the function as im not sure the value would update otherwise - const { nextUpdateCheckTime } = globalState!.getPublicState(); - if (!(new Date().valueOf() > nextUpdateCheckTime)) { - recursiveCheck(); - return; - } - // After testing, it appears that, if there is no wifi, bulkThemeUpdateCheck returns an empty array, this is okay, the try catch is just for extra safety - try { - const data = await bulkThemeUpdateCheck(); - if (data) { - // 24hrs from now - setGlobalState("updateStatuses", data); - } - setGlobalState("nextUpdateCheckTime", new Date().valueOf() + 24 * 60 * 60 * 1000); - } catch (err) { - console.log("Error Checking For Theme Updates", err); - } - recursiveCheck(); - }, 5 * 60 * 1000); - setGlobalState("updateCheckTimeout", timeout); - } - // Initially setting it - // 24hrs from now - setGlobalState("nextUpdateCheckTime", new Date().valueOf() + 24 * 60 * 60 * 1000); - recursiveCheck(); -} - -export async function changePreset(themeName: string, themeList: Theme[]) { - return new Promise(async (resolve) => { - const { selectedPreset } = globalState!.getPublicState(); - - if (selectedPreset) { - // If you already have a preset enabled, since all currently enabled themes are part of that preset, you only need to disable it, not every theme - await setThemeState(selectedPreset!.name, false); - } else { - // On the contrary, if you have no preset, you still do have to disable the current themes and then enable the preset - await Promise.all( - themeList.filter((e) => e.enabled).map((e) => setThemeState(e.name, false)) - ); - } - - if (themeName !== "None") { - await setThemeState(themeName, true); - } - resolve(true); - }); -} - -export async function getInstalledThemes(): Promise { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - const errorRes = await server!.callPluginMethod<{}, { fails: ThemeError[] }>( - "get_last_load_errors", - {} - ); - if (errorRes.success) { - setGlobalState("themeErrors", errorRes.result.fails); - } - const themeRes = await server!.callPluginMethod<{}, Theme[]>("get_themes", {}); - if (themeRes.success) { - setGlobalState("localThemeList", themeRes.result); - return themeRes.result; - } -} - -export async function reloadBackend(): Promise { - return server!.callPluginMethod<{}, { fails: ThemeError[] }>("reset", {}).then((res) => { - return getInstalledThemes(); - }); -} - -export function getThemes() { - return server!.callPluginMethod<{}, Theme[]>("get_themes", {}); -} - -export function setThemeState( - name: string, - state: boolean, - set_deps?: boolean, - set_deps_value?: boolean -): Promise { - return server!.callPluginMethod("set_theme_state", { - name: name, - state: state, - set_deps: set_deps ?? true, - set_deps_value: set_deps_value ?? true, - }); -} - -export function setPatchOfTheme(themeName: string, patchName: string, value: string): Promise { - return server!.callPluginMethod("set_patch_of_theme", { - themeName: themeName, - patchName: patchName, - value: value, - }); -} - -export function setComponentOfThemePatch( - themeName: string, - patchName: string, - componentName: string, - value: string -): Promise { - return server!.callPluginMethod("set_component_of_theme_patch", { - themeName: themeName, - patchName: patchName, - componentName: componentName, - value: value, - }); -} - -export function toast(title: string, message: string) { - // This is a weirdo self-invoking function, but it works. - return (() => { - try { - return server?.toaster.toast({ - title: title, - body: message, - duration: 8000, - }); - } catch (e) { - console.log("CSSLoader Toaster Error", e); - } - })(); -} - -export function downloadThemeFromUrl(themeId: string): Promise { - const { apiUrl } = globalState!.getPublicState(); - return server!.callPluginMethod("download_theme_from_url", { id: themeId, url: apiUrl }); -} - -export function deleteTheme(themeName: string): Promise { - return server!.callPluginMethod("delete_theme", { themeName: themeName }); -} - -export function storeRead(key: string) { - return server!.callPluginMethod("store_read", { key: key }); -} - -export function storeWrite(key: string, value: string) { - return server!.callPluginMethod("store_write", { key: key, val: value }); -} - -export function enableServer() { - return server!.callPluginMethod("enable_server", {}); -} -export function getServerState() { - return server!.callPluginMethod<{}, boolean>("get_server_state", {}); -} - -export function getBackendVersion(): Promise { - const setGlobalState = globalState!.setGlobalState.bind(globalState); - return server!.callPluginMethod<{}, Theme[]>("get_backend_version", {}).then((data) => { - if (data.success) { - setGlobalState("backendVersion", data.result); - } - return; - }); -} - -export function dummyFunction() { - return server!.callPluginMethod<{}, boolean>("dummy_function", {}); -} - -export function genericGET(fetchUrl: string, authToken?: string | undefined) { - return server! - .fetchNoCors(`${fetchUrl}`, { - method: "GET", - headers: authToken - ? { - Authorization: `Bearer ${authToken}`, - } - : {}, - }) - .then((deckyRes) => { - if (deckyRes.success) { - return deckyRes.result; - } - throw new Error(`Fetch not successful!`); - }) - .then((res) => { - if (res.status >= 200 && res.status <= 300 && res.body) { - // @ts-ignore - return JSON.parse(res.body || ""); - } - throw new Error(`Res not OK!, code ${res.status}`); - }) - .then((json) => { - if (json) { - return json; - } - throw new Error(`No json returned!`); - }) - .catch((err) => { - console.error(`Error fetching ${fetchUrl}`, err); - }); -} - -export function unpinTheme(id: string) { - const { unpinnedThemes } = globalState!.getPublicState(); - const setGlobalState = globalState!.setGlobalState.bind(globalState); - const newArr = [...unpinnedThemes, id]; - setGlobalState("unpinnedThemes", newArr); - return storeWrite("unpinnedThemes", JSON.stringify(newArr)); -} - -export function pinTheme(id: string) { - const { unpinnedThemes } = globalState!.getPublicState(); - const setGlobalState = globalState!.setGlobalState.bind(globalState); - const newArr = unpinnedThemes.filter((e) => e !== id); - setGlobalState("unpinnedThemes", newArr); - return storeWrite("unpinnedThemes", JSON.stringify(newArr)); -} - -export function generatePreset(name: string) { - return server!.callPluginMethod("generate_preset_theme", { name: name }); -} - -export function generatePresetFromThemeNames(name: string, themeNames: string[]) { - return server!.callPluginMethod("generate_preset_theme_from_theme_names", { - name: name, - themeNames: themeNames, - }); -} diff --git a/src/state/CssLoaderState.tsx b/src/state/CssLoaderState.tsx deleted file mode 100644 index c9882a3..0000000 --- a/src/state/CssLoaderState.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import { Patch, SingleDropdownOption } from "decky-frontend-lib"; -import { createContext, FC, useContext, useEffect, useState } from "react"; -import { - AccountData, - FilterQueryResponse, - FullAccountData, - PartialCSSThemeInfo, - ThemeQueryRequest, - ThemeQueryResponse, -} from "../apiTypes"; -import { Theme, ThemeError, UpdateStatus } from "../ThemeTypes"; - -interface PublicCssLoaderState { - // Browse Page - serverFilters: FilterQueryResponse; - prevSearchOpts: ThemeQueryRequest; - browseThemeList: ThemeQueryResponse; - themeSearchOpts: ThemeQueryRequest; - - // Starred Themes Page - starredSearchOpts: ThemeQueryRequest; - starredServerFilters: FilterQueryResponse; - starredThemeList: ThemeQueryResponse; - prevStarSearchOpts: ThemeQueryRequest; - - // Submission Page - prevSubSearchOpts: ThemeQueryRequest; - submissionSearchOpts: ThemeQueryRequest; - submissionServerFilters: FilterQueryResponse; - submissionThemeList: ThemeQueryResponse; - - currentTab: string; - forceScrollBackUp: boolean; - - // Api - selectedRepo: SingleDropdownOption; - apiUrl: string; - apiShortToken: string; - apiFullToken: string; - apiTokenExpireDate: Date | number | undefined; - apiMeData: FullAccountData | undefined; - // This is a unix timestamp - nextUpdateCheckTime: number; - updateCheckTimeout: NodeJS.Timeout | undefined; - - unminifyModeOn: boolean; - navPatchInstance: Patch | undefined; - updateStatuses: UpdateStatus[]; - selectedPreset: Theme | undefined; - localThemeList: Theme[]; - themeErrors: ThemeError[]; - currentSettingsPageTheme: string | undefined; - unpinnedThemes: string[]; - isInstalling: boolean; - currentExpandedTheme: PartialCSSThemeInfo | undefined; - browserCardSize: number; - backendVersion: number; - hiddenMotd: string; -} - -interface PublicCssLoaderContext extends PublicCssLoaderState { - setGlobalState(key: string, data: any): void; - getGlobalState(key: string): any; -} - -// This class creates the getter and setter functions for all of the global state data. -export class CssLoaderState { - private currentTab: string = "ThemeBrowser"; - private forceScrollBackUp: boolean = false; - private nextUpdateCheckTime: number = 0; - private updateCheckTimeout: NodeJS.Timeout | undefined = undefined; - private navPatchInstance: Patch | undefined = undefined; - - private updateStatuses: UpdateStatus[] = []; - private selectedPreset: Theme | undefined = undefined; - private apiUrl: string = "https://api.deckthemes.com"; - private apiShortToken: string = ""; - private apiFullToken: string = ""; - private apiTokenExpireDate: Date | number | undefined = undefined; - private apiMeData: FullAccountData | undefined = undefined; - private localThemeList: Theme[] = []; - private themeErrors: ThemeError[] = []; - private selectedRepo: SingleDropdownOption = { - data: 1, - label: "All", - }; - private isInstalling: boolean = false; - private currentExpandedTheme: PartialCSSThemeInfo | undefined = undefined; - private browserCardSize: number = 3; - - private browseThemeList: ThemeQueryResponse = { total: 0, items: [] }; - private prevSearchOpts: ThemeQueryRequest = { - page: 1, - perPage: 50, - filters: "All", - order: "Last Updated", - search: "", - }; - private serverFilters: FilterQueryResponse = { - filters: ["All"], - order: ["Last Updated"], - }; - private themeSearchOpts: ThemeQueryRequest = { - page: 1, - perPage: 50, - filters: "All", - order: "Last Updated", - search: "", - }; - - // Stars - private prevStarSearchOpts: ThemeQueryRequest = { - page: 1, - perPage: 50, - filters: "All", - order: "Last Updated", - search: "", - }; - private currentSettingsPageTheme: string | undefined = undefined; - private unpinnedThemes: string[] = []; - - private starredSearchOpts: ThemeQueryRequest = { - page: 1, - perPage: 50, - filters: "All", - order: "Last Updated", - search: "", - }; - private starredServerFilters: FilterQueryResponse = { - filters: ["All"], - order: ["Last Updated"], - }; - private starredThemeList: ThemeQueryResponse = { total: 0, items: [] }; - - // Submissions - private prevSubSearchOpts: ThemeQueryRequest = { - page: 1, - perPage: 50, - filters: "All", - order: "Last Updated", - search: "", - }; - private submissionSearchOpts: ThemeQueryRequest = { - page: 1, - perPage: 50, - filters: "All", - order: "Last Updated", - search: "", - }; - private submissionServerFilters: FilterQueryResponse = { - filters: ["All"], - order: ["Last Updated"], - }; - private submissionThemeList: ThemeQueryResponse = { total: 0, items: [] }; - private backendVersion: number = 6; - private hiddenMotd: string = ""; - private unminifyModeOn: boolean = false; - - // You can listen to this eventBus' 'stateUpdate' event and use that to trigger a useState or other function that causes a re-render - public eventBus = new EventTarget(); - - getPublicState() { - return { - currentTab: this.currentTab, - forceScrollBackUp: this.forceScrollBackUp, - nextUpdateCheckTime: this.nextUpdateCheckTime, - updateCheckTimeout: this.updateCheckTimeout, - apiUrl: this.apiUrl, - apiShortToken: this.apiShortToken, - apiFullToken: this.apiFullToken, - apiTokenExpireDate: this.apiTokenExpireDate, - apiMeData: this.apiMeData, - updateStatuses: this.updateStatuses, - selectedPreset: this.selectedPreset, - localThemeList: this.localThemeList, - themeErrors: this.themeErrors, - currentSettingsPageTheme: this.currentSettingsPageTheme, - unpinnedThemes: this.unpinnedThemes, - isInstalling: this.isInstalling, - hiddenMotd: this.hiddenMotd, - unminifyModeOn: this.unminifyModeOn, - - navPatchInstance: this.navPatchInstance, - selectedRepo: this.selectedRepo, - currentExpandedTheme: this.currentExpandedTheme, - browserCardSize: this.browserCardSize, - - // Browse Page - themeSearchOpts: this.themeSearchOpts, - serverFilters: this.serverFilters, - browseThemeList: this.browseThemeList, - prevSearchOpts: this.prevSearchOpts, - - // Starred - prevStarSearchOpts: this.prevStarSearchOpts, - starredSearchOpts: this.starredSearchOpts, - starredServerFilters: this.starredServerFilters, - starredThemeList: this.starredThemeList, - - // Submissions - prevSubSearchOpts: this.prevSubSearchOpts, - submissionSearchOpts: this.submissionSearchOpts, - submissionServerFilters: this.submissionServerFilters, - submissionThemeList: this.submissionThemeList, - backendVersion: this.backendVersion, - }; - } - - getGlobalState(key: string) { - return this[key]; - } - - setGlobalState(key: string, data: any) { - this[key] = data; - this.forceUpdate(); - } - - private forceUpdate() { - this.eventBus.dispatchEvent(new Event("stateUpdate")); - } -} - -const CssLoaderContext = createContext(null as any); -export const useCssLoaderState = () => useContext(CssLoaderContext); - -interface ProviderProps { - cssLoaderStateClass: CssLoaderState; -} - -// This is a React Component that you can wrap multiple separate things in, as long as they both have used the same instance of the CssLoaderState class, they will have synced state -export const CssLoaderContextProvider: FC = ({ children, cssLoaderStateClass }) => { - const [publicState, setPublicState] = useState({ - ...cssLoaderStateClass.getPublicState(), - }); - - useEffect(() => { - function onUpdate() { - setPublicState({ ...cssLoaderStateClass.getPublicState() }); - } - - cssLoaderStateClass.eventBus.addEventListener("stateUpdate", onUpdate); - - return () => cssLoaderStateClass.eventBus.removeEventListener("stateUpdate", onUpdate); - }, []); - - const getGlobalState = (key: string) => cssLoaderStateClass.getGlobalState(key); - const setGlobalState = (key: string, data: any) => cssLoaderStateClass.setGlobalState(key, data); - - return ( - - {children} - - ); -}; diff --git a/src/state/index.ts b/src/state/index.ts deleted file mode 100644 index 5bffe45..0000000 --- a/src/state/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CssLoaderState"; diff --git a/src/styles/index.ts b/src/styles/index.ts new file mode 100644 index 0000000..3f89b5b --- /dev/null +++ b/src/styles/index.ts @@ -0,0 +1 @@ +export * from "./stylesAsString"; diff --git a/src/styles/styles.css b/src/styles/styles.css new file mode 100644 index 0000000..434792f --- /dev/null +++ b/src/styles/styles.css @@ -0,0 +1,195 @@ +/* THIS FILE IS NOT USED IN BUILD */ +/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO stylesAsString.ts */ +/* THAT IS NEEDED FOR STATIC CLASS INJECTIOn */ + +.flex { + display: flex !important; +} + +.flex-col { + flex-direction: column !important; +} + +.gap-4 { + gap: 1rem !important; +} + +.gap-8 { + gap: 2rem !important; +} + +.items-center { + align-items: center !important; +} + +.items-stretch { + align-items: stretch !important; +} + +.justify-center { + justify-content: center !important; +} + +.justify-between { + justify-content: space-between !important; +} + +.p-0 { + padding: 0 !important; +} + +.m-0 { + margin: 0 !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.w-full { + width: 100% !important; +} + +.relative { + position: relative !important; +} + +.font-bold { + font-weight: bold !important; +} + +/* TitleView */ + +.cl-title-view-button { + height: 28px !important; + width: 40px !important; + min-width: 0 !important; + padding: 10px 12px !important; +} + +.cl-title-view-button-icon { + margin-top: -4px !important; + display: block !important; +} + +@keyframes onboardingButton { + 0% { + transform: scale(1) !important; + } + 50% { + transform: scale(1.1) !important; + } + 100% { + transform: scale(1) !important; + } +} + +.cl-animate-onboarding { + animation: onboardingButton 1s infinite ease-in-out !important; +} + +/* QAM Tab */ + +.cl-qam-collapse-button-container > div > div > div > div > button { + height: 10px !important; +} + +.cl-qam-themetoggle-notifbubble { + position: absolute !important; + top: 0 !important; + right: -1rem !important; + background: linear-gradient(45deg, transparent 49%, #fca904 50%) !important; + /* The focus ring has a z-index of 10000, so this is just to be cheeky */ + z-index: 10001 !important; + width: 20px !important; + height: 20px !important; +} + +.cl-qam-collapse-button-down-arrow { + transform: translateY(-13px) !important; + font-size: 1.5rem !important; +} + +.cl-qam-collapse-button-up-arrow { + transform: translateY(-12px) !important; + font-size: 1.5rem !important; +} + +.cl-qam-component-icon-container { + margin-left: auto !important; + width: 24px !important; + height: 24px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.cl-qam-component-color-preview { + width: 20px !important; + height: 20px !important; +} + +.cl-qam-hidden-themes-display { + display: flex !important; + align-items: center !important; + gap: 0.25rem !important; + font-size: 0.75rem !important; + padding: 8px 0 !important; +} + +/* Optional Deps Modal */ + +.cl-optional-deps-modal-title { + margin-block-end: 10px !important; + margin-block-start: 0px !important; + overflow-x: hidden !important; + font-size: 1.5rem !important; + white-space: nowrap !important; +} + +/* Theme Store */ + +.cl-store-filter-field-container { + display: flex !important; + flex-direction: column !important; + min-width: 49% !important; +} + + +.cl-store-dropdown-hide-spacer > button > div > div { + width: 100% !important; + display: flex !important; + align-items: start !important; +} + +.cl-store-dropdown-hide-spacer> button > div > .${gamepadDialogClasses.Spacer} { + width: 0 !important; +} + +.cl-store-searchbar { + min-width: 55% !important; +} + +.cl-store-refresh-button { + display: flex !important; + align-items: center !important; + justify-content: center !important; + gap: 0.5rem !important; + max-width: 20% !important; + height: 48% !important; +} + +.cl-store-scale-slider { + min-width: 20% !important; +} + +.cl-store-scale-slider > div > div > .${gamepadDialogClasses.FieldChildrenInner} { + min-width: 100% !important; +} + +.cl-store-theme-grid-container { + display: flex !important; + flex-wrap: wrap !important; + justify-content: center !important; + gap: 5px !important; +} \ No newline at end of file diff --git a/src/styles/stylesAsString.ts b/src/styles/stylesAsString.ts new file mode 100644 index 0000000..e49b895 --- /dev/null +++ b/src/styles/stylesAsString.ts @@ -0,0 +1,195 @@ +import { gamepadDialogClasses } from "@decky/ui"; + +export const styles = ` +.flex { + display: flex !important; +} + +.flex-col { + flex-direction: column !important; +} + +.gap-4 { + gap: 1rem !important; +} + +.gap-8 { + gap: 2rem !important; +} + +.items-center { + align-items: center !important; +} + +.items-stretch { + align-items: stretch !important; +} + +.justify-center { + justify-content: center !important; +} + +.justify-between { + justify-content: space-between !important; +} + +.p-0 { + padding: 0 !important; +} + +.m-0 { + margin: 0 !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.w-full { + width: 100% !important; +} + +.relative { + position: relative !important; +} + +.font-bold { + font-weight: bold !important; +} + +/* TitleView */ + +.cl-title-view-button { + height: 28px !important; + width: 40px !important; + min-width: 0 !important; + padding: 10px 12px !important; +} + +.cl-title-view-button-icon { + margin-top: -4px !important; + display: block !important; +} + +@keyframes onboardingButton { + 0% { + transform: scale(1) !important; + } + 50% { + transform: scale(1.1) !important; + } + 100% { + transform: scale(1) !important; + } +} + +.cl-animate-onboarding { + animation: onboardingButton 1s infinite ease-in-out !important; +} + +/* QAM Tab */ + +.cl-qam-collapse-button-container > div > div > div > div > button { + height: 10px !important; +} + +.cl-qam-themetoggle-notifbubble { + position: absolute !important; + top: 0 !important; + right: -1rem !important; + background: linear-gradient(45deg, transparent 49%, #fca904 50%) !important; + /* The focus ring has a z-index of 10000, so this is just to be cheeky */ + z-index: 10001 !important; + width: 20px !important; + height: 20px !important; +} + +.cl-qam-collapse-button-down-arrow { + transform: translateY(-13px) !important; + font-size: 1.5rem !important; +} + +.cl-qam-collapse-button-up-arrow { + transform: translateY(-12px) !important; + font-size: 1.5rem !important; +} + +.cl-qam-component-icon-container { + margin-left: auto !important; + width: 24px !important; + height: 24px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.cl-qam-component-color-preview { + width: 20px !important; + height: 20px !important; +} + +.cl-qam-hidden-themes-display { + display: flex !important; + align-items: center !important; + gap: 0.25rem !important; + font-size: 0.75rem !important; + padding: 8px 0 !important; +} + +/* Optional Deps Modal */ + +.cl-optional-deps-modal-title { + margin-block-end: 10px !important; + margin-block-start: 0px !important; + overflow-x: hidden !important; + font-size: 1.5rem !important; + white-space: nowrap !important; +} + +/* Theme Store */ + +.cl-store-filter-field-container { + display: flex !important; + flex-direction: column !important; + min-width: 49% !important; +} + + +.cl-store-dropdown-hide-spacer > button > div > div { + width: 100% !important; + display: flex !important; + align-items: start !important; +} + +.cl-store-dropdown-hide-spacer> button > div > .${gamepadDialogClasses.Spacer} { + width: 0 !important; +} + +.cl-store-searchbar { + min-width: 55% !important; +} + +.cl-store-refresh-button { + display: flex !important; + align-items: center !important; + justify-content: center !important; + gap: 0.5rem !important; + max-width: 20% !important; + height: 48% !important; +} + +.cl-store-scale-slider { + min-width: 20% !important; +} + +.cl-store-scale-slider > div > div > .${gamepadDialogClasses.FieldChildrenInner} { + min-width: 100% !important; +} + +.cl-store-theme-grid-container { + display: flex !important; + flex-wrap: wrap !important; + justify-content: center !important; + gap: 5px !important; +} +`; diff --git a/src/styles/themeSettingsModalStyles.css b/src/styles/themeSettingsModalStyles.css deleted file mode 100644 index 2cffd90..0000000 --- a/src/styles/themeSettingsModalStyles.css +++ /dev/null @@ -1,70 +0,0 @@ -.CSSLoader_ThemeSettingsModal_ToggleParent { - width: 90%; -} - -.CSSLoader_ThemeSettingsModal_Title { - font-weight: bold; - font-size: 2em; -} - -.CSSLoader_ThemeSettingsModal_Subtitle { - font-size: 0.75em; -} - -.CSSLoader_ThemeSettingsModal_Container { - display: flex; - flex-direction: column; - align-items: center; - gap: 1em; - width: 100%; -} - -.CSSLoader_ThemeSettingsModal_ButtonsContainer { - display: flex; - gap: 0.25em; -} - -.CSSLoader_ThemeSettingsModalHeader_DialogButton { - width: fit-content !important; - min-width: fit-content !important; - height: fit-content !important; - padding: 10px 12px !important; -} - -.CSSLoader_ThemeSettingsModal_IconTranslate { - transform: translate(0px, 2px); -} - -.CSSLoader_ThemeSettingsModal_Footer { - display: flex; - width: 100%; - justify-content: space-between; -} - -.CSSLoader_ThemeSettingsModal_TitleContainer { - display: flex; - max-width: 80%; - flex-direction: column; -} - -.CSSLoader_ThemeSettingsModal_Header { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -} - -.CSSLoader_ThemeSettingsModal_PatchContainer { - display: flex; - width: 100%; - flex-direction: column; -} - -.CSSLoader_ThemeSettingsModal_UpdateButton { - display: flex !important; - gap: 0.25em; -} - -.CSSLoader_ThemeSettingsModal_UpdateText { - font-size: 0.75em; -} diff --git a/src/apiTypes/AccountData.ts b/src/types/AccountData.ts similarity index 93% rename from src/apiTypes/AccountData.ts rename to src/types/AccountData.ts index 61c9acf..57f11cc 100644 --- a/src/apiTypes/AccountData.ts +++ b/src/types/AccountData.ts @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction } from "react"; -import { PartialCSSThemeInfo, UserInfo } from "./CSSThemeTypes"; +import { PartialCSSThemeInfo, UserInfo } from "./ThemeQueryTypes"; export enum Permissions { "editAny" = "EditAnyPost", diff --git a/src/apiTypes/BlobTypes.ts b/src/types/BlobTypes.ts similarity index 100% rename from src/apiTypes/BlobTypes.ts rename to src/types/BlobTypes.ts diff --git a/src/apiTypes/Motd.ts b/src/types/Motd.ts similarity index 100% rename from src/apiTypes/Motd.ts rename to src/types/Motd.ts diff --git a/src/apiTypes/SubmissionTypes.ts b/src/types/SubmissionTypes.ts similarity index 100% rename from src/apiTypes/SubmissionTypes.ts rename to src/types/SubmissionTypes.ts diff --git a/src/apiTypes/CSSThemeTypes.ts b/src/types/ThemeQueryTypes.ts similarity index 100% rename from src/apiTypes/CSSThemeTypes.ts rename to src/types/ThemeQueryTypes.ts diff --git a/src/ThemeTypes.ts b/src/types/ThemeTypes.ts similarity index 94% rename from src/ThemeTypes.ts rename to src/types/ThemeTypes.ts index cbab186..dba92ad 100644 --- a/src/ThemeTypes.ts +++ b/src/types/ThemeTypes.ts @@ -1,4 +1,4 @@ -import { MinimalCSSThemeInfo } from "./apiTypes"; +import { MinimalCSSThemeInfo } from "./ThemeQueryTypes"; export interface Theme { id: string; diff --git a/src/apiTypes/index.ts b/src/types/index.ts similarity index 64% rename from src/apiTypes/index.ts rename to src/types/index.ts index 5fa3f61..e3b8aaa 100644 --- a/src/apiTypes/index.ts +++ b/src/types/index.ts @@ -1,5 +1,6 @@ -export * from "./CSSThemeTypes"; +export * from "./ThemeQueryTypes"; export * from "./AccountData"; export * from "./BlobTypes"; export * from "./Motd"; export * from "./SubmissionTypes"; +export * from "./ThemeTypes"; diff --git a/tsconfig.json b/tsconfig.json index ea2f05c..97d3ad2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,9 +15,16 @@ "noImplicitThis": true, "noImplicitAny": true, "strict": true, - "suppressImplicitAnyIndexErrors": true, "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "paths": { + "@cssloader/backend": ["./src/backend"], + "@/backend": ["./src/backend-impl"], + "@/lib": ["./src/lib"], + "@/styles": ["./src/styles"], + "@/types": ["./src/types"], + "@/modules/*": ["./src/modules/*"], + "@/decky-patches": ["./src/decky-patches"] + } }, "include": ["src"], "exclude": ["node_modules"] From 9088f7259b7e1d228a34193503b6aa0ddae4bd3b Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 29 Jun 2024 16:40:08 -0600 Subject: [PATCH 11/53] update lockfile --- pnpm-lock.yaml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfc768a..2a4b743 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,12 +26,6 @@ importers: react-icons: specifier: ^4.12.0 version: 4.12.0(react@18.3.1) - rollup-plugin-delete: - specifier: ^2.0.0 - version: 2.0.0 - rollup-plugin-external-globals: - specifier: ^0.10.0 - version: 0.10.0(rollup@4.18.0) tailwind-merge: specifier: ^2.3.0 version: 2.3.0 @@ -56,7 +50,7 @@ importers: version: 5.0.7(rollup@4.18.0) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@5.4.5) + version: 11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@4.9.5) '@types/color': specifier: ^3.0.3 version: 3.0.6 @@ -72,6 +66,12 @@ importers: rollup: specifier: ^4.18.0 version: 4.18.0 + rollup-plugin-delete: + specifier: ^2.0.0 + version: 2.0.0 + rollup-plugin-external-globals: + specifier: ^0.10.0 + version: 0.10.0(rollup@4.18.0) rollup-plugin-import-assets: specifier: ^1.1.1 version: 1.1.1(rollup@4.18.0) @@ -85,8 +85,8 @@ importers: specifier: ^2.4.0 version: 2.6.2 typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^4.6.4 + version: 4.9.5 packages: @@ -1420,9 +1420,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} hasBin: true undici-types@5.26.5: @@ -1616,11 +1616,11 @@ snapshots: optionalDependencies: rollup: 4.18.0 - '@rollup/plugin-typescript@11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@5.4.5)': + '@rollup/plugin-typescript@11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@4.9.5)': dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.18.0) resolve: 1.22.8 - typescript: 5.4.5 + typescript: 4.9.5 optionalDependencies: rollup: 4.18.0 tslib: 2.6.2 @@ -2827,7 +2827,7 @@ snapshots: tslib@2.6.2: {} - typescript@5.4.5: {} + typescript@4.9.5: {} undici-types@5.26.5: {} From 5f9e1ce60e72ac6fe2651d422f544747ae92903b Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:18:55 -0600 Subject: [PATCH 12/53] work on theme store --- src/backend/state/theme-store.ts | 2 + src/index.tsx | 9 +- src/lib/components/index.ts | 4 +- src/lib/components/modals/index.ts | 2 + .../NavPatchInfoModal.tsx | 2 +- .../nav-patch-info-modal/index.ts | 0 .../optional-deps-modal/OptionalDepsModal.tsx | 2 +- .../{ => modals}/optional-deps-modal/index.ts | 0 src/lib/components/title-view/TitleView.tsx | 2 +- src/lib/hooks/index.ts | 1 + src/lib/hooks/useThemeInstallState.ts | 12 ++ src/lib/index.ts | 1 + src/lib/primitives/ConfirmModal.tsx | 2 +- src/lib/primitives/Modal.tsx | 2 +- src/lib/providers/index.ts | 1 + .../style-provider/StyleProvider.tsx | 0 .../style-provider/index.ts | 0 src/lib/utils/index.ts | 1 + src/lib/utils/shorten-number.ts | 48 +++++++ src/lib/utils/toggleThemeWithModals.tsx | 2 +- .../components/BrowserSearchFields.tsx | 9 +- .../components/ThemeBrowserPage.tsx | 34 ++++- .../theme-store/components/ThemeCard.tsx | 79 +++++++++++- .../ThemeCardCSSVariableProvider.tsx | 8 ++ .../components/ThemeGridDisplay.tsx | 13 -- src/modules/theme-store/components/index.ts | 2 + .../context/ThemeBrowserSharedStore.tsx | 8 +- .../theme-store/context/ThemeBrowserStore.tsx | 8 +- .../theme-store/pages/ThemeStoreRouter.tsx | 32 +++++ src/styles/index.ts | 3 +- ...{stylesAsString.ts => styles-as-string.ts} | 117 ++++++++++++++++++ src/styles/styles.css | 117 ++++++++++++++++++ src/styles/theme-card-styles-generator.ts | 18 +++ src/types/ThemeTypes.ts | 2 +- 34 files changed, 503 insertions(+), 40 deletions(-) create mode 100644 src/lib/components/modals/index.ts rename src/lib/components/{ => modals}/nav-patch-info-modal/NavPatchInfoModal.tsx (93%) rename src/lib/components/{ => modals}/nav-patch-info-modal/index.ts (100%) rename src/lib/components/{ => modals}/optional-deps-modal/OptionalDepsModal.tsx (97%) rename src/lib/components/{ => modals}/optional-deps-modal/index.ts (100%) create mode 100644 src/lib/hooks/useThemeInstallState.ts create mode 100644 src/lib/providers/index.ts rename src/lib/{components => providers}/style-provider/StyleProvider.tsx (100%) rename src/lib/{components => providers}/style-provider/index.ts (100%) create mode 100644 src/lib/utils/shorten-number.ts create mode 100644 src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx delete mode 100644 src/modules/theme-store/components/ThemeGridDisplay.tsx create mode 100644 src/modules/theme-store/components/index.ts create mode 100644 src/modules/theme-store/pages/ThemeStoreRouter.tsx rename src/styles/{stylesAsString.ts => styles-as-string.ts} (57%) create mode 100644 src/styles/theme-card-styles-generator.ts 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; From 92204e41914a0e4cb293c35ce074e245746ebf56 Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 24 Jul 2024 07:34:28 -0600 Subject: [PATCH 13/53] add decky fetchNoCors --- package.json | 4 ++-- pnpm-lock.yaml | 20 +++++++++---------- .../decky-backend-repository-impl.ts | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 11e4cfc..e2bcc14 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "typescript": "^4.6.4" }, "dependencies": { - "@decky/api": "^1.0.3", - "@decky/ui": "^4.0.1", + "@decky/api": "^1.0.6", + "@decky/ui": "^4.4.0", "clsx": "^2.1.1", "color": "^4.2.3", "lodash": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a4b743..d656b7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@decky/api': - specifier: ^1.0.3 - version: 1.0.3 + specifier: ^1.0.6 + version: 1.0.6 '@decky/ui': - specifier: ^4.0.1 - version: 4.0.1 + specifier: ^4.4.0 + version: 4.4.0 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -106,11 +106,11 @@ packages: resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} - '@decky/api@1.0.3': - resolution: {integrity: sha512-7hMKEHWcyz/bttx7DcKXqsOXcrtmC4CB6UwxRVrtlb/aolQtv1NVKHIEkIM6ND5hqTUU/VJ2HPUmCOwKm3Of0Q==} + '@decky/api@1.0.6': + resolution: {integrity: sha512-pacO2qvAin7ZoB9AnCgQbevQS+6Wiy0t1C6QVEJPCWeQQdEgJaUm3KSeRjh2KNqsjKHsbV0j72Pv4X5Q44Lr7Q==} - '@decky/ui@4.0.1': - resolution: {integrity: sha512-BCT10sirZtG6eK2FSwffySM9fAUzwKMi1nsXJP7x+BTQ/UqEudy4IqxSe0AV7qaG5PzmiHuQZWoRwaaqIbUdeg==} + '@decky/ui@4.4.0': + resolution: {integrity: sha512-w6hSoEdWQyXdvSk8cv4cGDJQ6/xoRx9LYrdNjTGPPj8hRmBcoRlThgZ+9vchmNsAJuOymwQ5hQXqHN6Y86eoOQ==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -1525,9 +1525,9 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@decky/api@1.0.3': {} + '@decky/api@1.0.6': {} - '@decky/ui@4.0.1': {} + '@decky/ui@4.4.0': {} '@isaacs/cliui@8.0.2': dependencies: diff --git a/src/backend-impl/decky-backend-repository-impl.ts b/src/backend-impl/decky-backend-repository-impl.ts index 4bb5b79..b8db8da 100644 --- a/src/backend-impl/decky-backend-repository-impl.ts +++ b/src/backend-impl/decky-backend-repository-impl.ts @@ -1,4 +1,4 @@ -import { callable, toaster } from "@decky/api"; +import { callable, toaster, fetchNoCors } from "@decky/api"; import { CallError, FetchError, type IBackendRepository } from "@cssloader/backend"; class DeckyBackendRepository implements IBackendRepository { @@ -16,7 +16,7 @@ class DeckyBackendRepository implements IBackendRepository { } async fetch(url: string, request: RequestInit) { try { - const res = await fetch(url, request); + const res = await fetchNoCors(url, request); if (!res.ok) { throw new Error(`Res Not Okay - Code ${res.status}`); } From fe9377bc340e7ff43af21e13e9c35cad430573b4 Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:45:45 -0600 Subject: [PATCH 14/53] fix store --- .../decky-backend-repository-impl.ts | 1 + .../theme-store/context/ThemeBrowserStore.tsx | 21 ++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/backend-impl/decky-backend-repository-impl.ts b/src/backend-impl/decky-backend-repository-impl.ts index b8db8da..6db65a5 100644 --- a/src/backend-impl/decky-backend-repository-impl.ts +++ b/src/backend-impl/decky-backend-repository-impl.ts @@ -16,6 +16,7 @@ class DeckyBackendRepository implements IBackendRepository { } async fetch(url: string, request: RequestInit) { try { + console.log("CSSLODAER FETCH", url, request); const res = await fetchNoCors(url, request); if (!res.ok) { throw new Error(`Res Not Okay - Code ${res.status}`); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 6435831..a77dfec 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -25,22 +25,19 @@ interface IThemeBrowserStore extends ThemeBrowserStoreValues, ThemeBrowserStoreA const ThemeBrowserStoreContext = createContext | null>(null); function generateParamStr(searchOpts: ThemeQueryRequest) { + const searchOptsClone = structuredClone(searchOpts); let prependString = "BPM-CSS.-Preset"; - switch (searchOpts.filters) { - // If it's desktop themes, remove the "BPM Only" filter in the default - case "Desktop": - prependString = "-Preset"; - break; - // If it's presets, remove the preset exclusion in the default - case "Preset": - prependString = "BPM-CSS"; - break; + if (searchOptsClone.filters.includes("Desktop")) { + prependString = "-Preset"; } - searchOpts.filters === "All" ? (searchOpts.filters = "") : (prependString += "."); - prependString && (searchOpts.filters = prependString + searchOpts.filters); + if (searchOptsClone.filters.includes("Preset")) { + prependString = "BPM-CSS"; + } + searchOptsClone.filters === "All" ? (searchOptsClone.filters = "") : (prependString += "."); + prependString && (searchOptsClone.filters = prependString + searchOptsClone.filters); // @ts-expect-error - const paramStr = new URLSearchParams(searchOpts).toString(); + const paramStr = new URLSearchParams(searchOptsClone).toString(); return paramStr; } From 85433240906285ddb7220f87530931260f6ed67a Mon Sep 17 00:00:00 2001 From: beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:12:29 -0600 Subject: [PATCH 15/53] add name --- src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.tsx b/src/index.tsx index adb9e3b..863f46d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,6 +17,7 @@ export default definePlugin(() => { )); return { + name: "SDH-CSSLoader", titleView: ( From 7d2f95dd978760517fb74edfa39494b5adeed0d3 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:03:53 -0600 Subject: [PATCH 16/53] rename zustand functions --- src/backend-impl/decky-theme-store.ts | 2 +- src/index.tsx | 9 ++- .../components/motd-display/MOTDDisplay.tsx | 6 +- .../PresetSelectionDropdown.tsx | 6 +- src/lib/components/theme-patch/ThemePatch.tsx | 2 +- .../theme-patch/ThemePatchComponent.tsx | 4 +- src/lib/components/title-view/TitleView.tsx | 4 +- src/lib/hooks/useThemeInstallState.ts | 4 +- .../components/ExpandedViewLoadingPage.tsx | 12 ++++ .../ExpandedViewScrollingSection.tsx | 21 ++++++ src/modules/expanded-view/components/index.ts | 2 + .../context/ExpandedViewStore.tsx | 66 +++++++++++++++++++ src/modules/expanded-view/context/index.ts | 1 + src/modules/expanded-view/index.ts | 2 + .../expanded-view/pages/ExpandedViewPage.tsx | 17 +++++ src/modules/expanded-view/pages/index.ts | 1 + .../components/QamDummyFunctionBoundary.tsx | 4 +- .../components/QamHiddenThemesDisplay.tsx | 4 +- .../components/QamRefreshButton.tsx | 2 +- .../qam-tab-page/components/QamThemeList.tsx | 6 +- .../components/QamThemeToggle.tsx | 6 +- src/modules/qam-tab-page/pages/QamTabPage.tsx | 4 +- .../components/BrowserSearchFields.tsx | 8 +-- .../components/ThemeBrowserPage.tsx | 4 +- .../theme-store/components/ThemeCard.tsx | 17 +++-- .../ThemeCardCSSVariableProvider.tsx | 4 +- .../context/ThemeBrowserSharedStore.tsx | 4 +- src/modules/theme-store/index.ts | 1 + src/modules/theme-store/pages/index.ts | 1 + src/styles/styles-as-string.ts | 18 ++++- src/styles/styles.css | 31 ++++++++- 31 files changed, 224 insertions(+), 49 deletions(-) create mode 100644 src/modules/expanded-view/components/ExpandedViewLoadingPage.tsx create mode 100644 src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx create mode 100644 src/modules/expanded-view/components/index.ts create mode 100644 src/modules/expanded-view/context/ExpandedViewStore.tsx create mode 100644 src/modules/expanded-view/context/index.ts create mode 100644 src/modules/expanded-view/index.ts create mode 100644 src/modules/expanded-view/pages/ExpandedViewPage.tsx create mode 100644 src/modules/expanded-view/pages/index.ts create mode 100644 src/modules/theme-store/index.ts create mode 100644 src/modules/theme-store/pages/index.ts diff --git a/src/backend-impl/decky-theme-store.ts b/src/backend-impl/decky-theme-store.ts index 908bc07..2fd68c0 100644 --- a/src/backend-impl/decky-theme-store.ts +++ b/src/backend-impl/decky-theme-store.ts @@ -11,7 +11,7 @@ const cssLoaderStore = createCSSLoaderStore(backend); const useCSSLoaderStore = (fn: (state: ICSSLoaderState) => any) => useStore(cssLoaderStore, fn); -export const useCSSLoaderStateValue = ( +export const useCSSLoaderValue = ( key: T ): ICSSLoaderState[T] => useCSSLoaderStore((state) => state[key]); diff --git a/src/index.tsx b/src/index.tsx index 863f46d..463fce6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,8 @@ import { QamTabPage } from "@/modules/qam-tab-page"; import { definePlugin, routerHook } from "@decky/api"; import { getCSSLoaderState } from "@/backend"; import { getDeckyPatchState } from "./decky-patches"; -import { ThemeStoreRouter } from "./modules/theme-store/pages/ThemeStoreRouter"; +import { ThemeStoreRouter } from "./modules/theme-store"; +import { ExpandedViewPage } from "./modules/expanded-view"; export default definePlugin(() => { getCSSLoaderState().initializeStore(); @@ -16,6 +17,12 @@ export default definePlugin(() => { )); + routerHook.addRoute("/cssloader/expanded-view", () => ( + + + + )); + return { name: "SDH-CSSLoader", titleView: ( diff --git a/src/lib/components/motd-display/MOTDDisplay.tsx b/src/lib/components/motd-display/MOTDDisplay.tsx index 5943f3e..57dff91 100644 --- a/src/lib/components/motd-display/MOTDDisplay.tsx +++ b/src/lib/components/motd-display/MOTDDisplay.tsx @@ -2,7 +2,7 @@ import { DialogButton, Focusable, PanelSection } from "@decky/ui"; import { useEffect, useMemo } from "react"; import { Motd } from "@/types"; import { FaTimes } from "react-icons/fa"; -import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; const SEVERITIES = { High: { @@ -22,8 +22,8 @@ const SEVERITIES = { export function MOTDDisplay() { const getMotd = useCSSLoaderAction("getMotd"); const hideMotd = useCSSLoaderAction("hideMotd"); - const motd = useCSSLoaderStateValue("motd"); - const hiddenMotdId = useCSSLoaderStateValue("hiddenMotdId"); + const motd = useCSSLoaderValue("motd"); + const hiddenMotdId = useCSSLoaderValue("hiddenMotdId"); useEffect(() => { void getMotd(); diff --git a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx index 8313709..3b2c585 100644 --- a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx +++ b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx @@ -3,11 +3,11 @@ import { Flags } from "@/types"; import { useMemo } from "react"; import { FiPlusCircle } from "react-icons/fi"; import { useForcedRerender } from "../../hooks"; -import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; export function PresetSelectionDropdown() { - const themes = useCSSLoaderStateValue("themes"); - const selectedPreset = useCSSLoaderStateValue("selectedPreset"); + const themes = useCSSLoaderValue("themes"); + const selectedPreset = useCSSLoaderValue("selectedPreset"); const changePreset = useCSSLoaderAction("changePreset"); const presets = useMemo(() => themes.filter((e) => e.flags.includes(Flags.isPreset)), [themes]); const hasInvalidPresetState = presets.length > 1; diff --git a/src/lib/components/theme-patch/ThemePatch.tsx b/src/lib/components/theme-patch/ThemePatch.tsx index 2d7ed3e..18c3bc5 100644 --- a/src/lib/components/theme-patch/ThemePatch.tsx +++ b/src/lib/components/theme-patch/ThemePatch.tsx @@ -1,5 +1,5 @@ import { Patch } from "@/types"; -import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; import { useState } from "react"; import { DropdownItem, PanelSectionRow, SliderField, ToggleField } from "@decky/ui"; import { ThemePatchComponent } from "./ThemePatchComponent"; diff --git a/src/lib/components/theme-patch/ThemePatchComponent.tsx b/src/lib/components/theme-patch/ThemePatchComponent.tsx index 08ba845..2509a53 100644 --- a/src/lib/components/theme-patch/ThemePatchComponent.tsx +++ b/src/lib/components/theme-patch/ThemePatchComponent.tsx @@ -1,5 +1,5 @@ import { ThemePatchComponent } from "@/types"; -import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; import { ButtonItem, ColorPickerModal, PanelSectionRow, showModal } from "@decky/ui"; import { FaFolder } from "react-icons/fa"; import { FileSelectionType, openFilePicker } from "@decky/api"; @@ -21,7 +21,7 @@ export function ThemePatchComponent({ const bottomSeparatorValue = shouldHaveBottomSeparator ? "standard" : "none"; const setComponentValue = useCSSLoaderAction("setComponentValue"); - const themeRootPath = useCSSLoaderStateValue("themeRootPath"); + const themeRootPath = useCSSLoaderValue("themeRootPath"); const toast = useCSSLoaderAction("toast"); if (currentPatchValue !== component.on) return null; diff --git a/src/lib/components/title-view/TitleView.tsx b/src/lib/components/title-view/TitleView.tsx index 2f28e58..45b4099 100644 --- a/src/lib/components/title-view/TitleView.tsx +++ b/src/lib/components/title-view/TitleView.tsx @@ -1,11 +1,11 @@ import { DialogButton, Navigation, Focusable, quickAccessMenuClasses } from "@decky/ui"; import { BsGearFill } from "react-icons/bs"; import { FaDownload } from "react-icons/fa"; -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; import { cn } from "../../utils"; export function TitleView() { - const themes = useCSSLoaderStateValue("themes"); + const themes = useCSSLoaderValue("themes"); const onSettingsClick = () => { Navigation.CloseSideMenus(); diff --git a/src/lib/hooks/useThemeInstallState.ts b/src/lib/hooks/useThemeInstallState.ts index f8a134c..de40e31 100644 --- a/src/lib/hooks/useThemeInstallState.ts +++ b/src/lib/hooks/useThemeInstallState.ts @@ -1,8 +1,8 @@ import { LocalThemeStatus, PartialCSSThemeInfo, Theme } from "@/types"; -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; export function useThemeInstallState(theme: Theme | PartialCSSThemeInfo): LocalThemeStatus { - const updateStatuses = useCSSLoaderStateValue("updateStatuses"); + const updateStatuses = useCSSLoaderValue("updateStatuses"); const status = updateStatuses.find((status) => status[0] === theme.id); if (status) { diff --git a/src/modules/expanded-view/components/ExpandedViewLoadingPage.tsx b/src/modules/expanded-view/components/ExpandedViewLoadingPage.tsx new file mode 100644 index 0000000..0442405 --- /dev/null +++ b/src/modules/expanded-view/components/ExpandedViewLoadingPage.tsx @@ -0,0 +1,12 @@ +import { ImSpinner5 } from "react-icons/im"; + +export function ExpandedViewLoadingPage() { + return ( + <> +
+ + Loading +
+ + ); +} diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx new file mode 100644 index 0000000..06ace3c --- /dev/null +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -0,0 +1,21 @@ +import { ScrollPanelGroup } from "@decky/ui"; +import { useExpandedViewAction } from "../context"; + +export function ExpandedViewScrollingSection() { + const close = useExpandedViewAction("close"); + return ( + { + if (!evt?.detail?.button) return; + if (evt.detail.button === 2) { + close(); + } + }} + > + ); +} diff --git a/src/modules/expanded-view/components/index.ts b/src/modules/expanded-view/components/index.ts new file mode 100644 index 0000000..9b1d7e8 --- /dev/null +++ b/src/modules/expanded-view/components/index.ts @@ -0,0 +1,2 @@ +export * from "./ExpandedViewLoadingPage"; +export * from "./ExpandedViewScrollingSection"; diff --git a/src/modules/expanded-view/context/ExpandedViewStore.tsx b/src/modules/expanded-view/context/ExpandedViewStore.tsx new file mode 100644 index 0000000..93f91aa --- /dev/null +++ b/src/modules/expanded-view/context/ExpandedViewStore.tsx @@ -0,0 +1,66 @@ +import { getCSSLoaderState } from "@/backend"; +import { FullCSSThemeInfo } from "@/types"; +import { Navigation } from "@decky/ui"; +import { createStore, useStore } from "zustand"; + +interface IExpandedViewStoreValues { + loaded: boolean; + error: string | null; + openedId: string | null; + data: FullCSSThemeInfo; +} + +interface IExpandedViewStoreActions { + openTheme: (themeId: string) => Promise; + downloadTheme: () => Promise; + close: () => void; +} + +export interface IExpandedViewStore extends IExpandedViewStoreValues, IExpandedViewStoreActions {} + +const expandedViewStore = createStore((set, get) => { + return { + loaded: false, + openedId: null, + data: {} as FullCSSThemeInfo, + error: null, + openTheme: async (themeId) => { + set({ loaded: false, error: null, openedId: themeId }); + Navigation.Navigate("/cssloader/expanded-view"); + const { apiFetch } = getCSSLoaderState(); + try { + const response = await apiFetch(`/themes/${themeId}`); + if (response) { + set({ data: response, loaded: true }); + return; + } + throw new Error("No response returned"); + } catch (error) { + set({ error: "Error fetching theme!", loaded: true }); + } + }, + downloadTheme: async () => { + // const { apiFetch } = getCSSLoaderState(); + // try { + // await apiFetch(`/theme/${get().data.id}/download`, {}, true); + // } catch (error) { + // set({ error: "Error downloading theme!" }); + // } + }, + close: () => { + set({ loaded: false, openedId: null, data: {} as FullCSSThemeInfo, error: null }); + Navigation.NavigateBack(); + }, + }; +}); + +const useExpandedViewState = (fn: (state: IExpandedViewStore) => any) => + useStore(expandedViewStore, fn); + +export const useExpandedViewValue = ( + key: T +): IExpandedViewStore[T] => useExpandedViewState((state) => state[key]); + +export const useExpandedViewAction = ( + key: T +): IExpandedViewStore[T] => useExpandedViewState((state) => state[key]); diff --git a/src/modules/expanded-view/context/index.ts b/src/modules/expanded-view/context/index.ts new file mode 100644 index 0000000..66cfa22 --- /dev/null +++ b/src/modules/expanded-view/context/index.ts @@ -0,0 +1 @@ +export * from "./ExpandedViewStore"; diff --git a/src/modules/expanded-view/index.ts b/src/modules/expanded-view/index.ts new file mode 100644 index 0000000..7ec876d --- /dev/null +++ b/src/modules/expanded-view/index.ts @@ -0,0 +1,2 @@ +export * from "./pages"; +export * from "./context"; diff --git a/src/modules/expanded-view/pages/ExpandedViewPage.tsx b/src/modules/expanded-view/pages/ExpandedViewPage.tsx new file mode 100644 index 0000000..dc663ca --- /dev/null +++ b/src/modules/expanded-view/pages/ExpandedViewPage.tsx @@ -0,0 +1,17 @@ +import { ExpandedViewLoadingPage, ExpandedViewScrollingSection } from "../components"; +import { useExpandedViewValue } from "../context"; + +export function ExpandedViewPage() { + const loaded = useExpandedViewValue("loaded"); + const error = useExpandedViewValue("error"); + + if (!loaded) return ; + + if (error) return {error}; + + return ( +
+ +
+ ); +} diff --git a/src/modules/expanded-view/pages/index.ts b/src/modules/expanded-view/pages/index.ts new file mode 100644 index 0000000..7d6a1e1 --- /dev/null +++ b/src/modules/expanded-view/pages/index.ts @@ -0,0 +1 @@ +export * from "./ExpandedViewPage"; diff --git a/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx b/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx index 73d491a..0f8332e 100644 --- a/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx +++ b/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx @@ -1,8 +1,8 @@ -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; import { PanelSectionRow } from "@decky/ui"; export function QamDummyFunctionBoundary({ children }: { children: React.ReactNode }) { - const dummyFunctionResult = useCSSLoaderStateValue("dummyFunctionResult"); + const dummyFunctionResult = useCSSLoaderValue("dummyFunctionResult"); if (!dummyFunctionResult) { return ( diff --git a/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx b/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx index 364bcbd..85dcb7e 100644 --- a/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx +++ b/src/modules/qam-tab-page/components/QamHiddenThemesDisplay.tsx @@ -1,8 +1,8 @@ -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; import { FaEyeSlash } from "react-icons/fa"; export function QamHiddenThemesDisplay() { - const unpinnedThemes = useCSSLoaderStateValue("unpinnedThemes"); + const unpinnedThemes = useCSSLoaderValue("unpinnedThemes"); if (unpinnedThemes.length === 0) { return null; diff --git a/src/modules/qam-tab-page/components/QamRefreshButton.tsx b/src/modules/qam-tab-page/components/QamRefreshButton.tsx index abf6588..729b4ed 100644 --- a/src/modules/qam-tab-page/components/QamRefreshButton.tsx +++ b/src/modules/qam-tab-page/components/QamRefreshButton.tsx @@ -1,5 +1,5 @@ import { ButtonItem, PanelSectionRow } from "@decky/ui"; -import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; export function QamRefreshButton() { const reloadPlugin = useCSSLoaderAction("reloadPlugin"); diff --git a/src/modules/qam-tab-page/components/QamThemeList.tsx b/src/modules/qam-tab-page/components/QamThemeList.tsx index 3520dcb..8cfa0c7 100644 --- a/src/modules/qam-tab-page/components/QamThemeList.tsx +++ b/src/modules/qam-tab-page/components/QamThemeList.tsx @@ -1,11 +1,11 @@ import { Focusable } from "@decky/ui"; -import { useCSSLoaderStateValue } from "../../../backend-impl/decky-theme-store"; +import { useCSSLoaderValue } from "../../../backend-impl/decky-theme-store"; import { Flags } from "@/types"; import { QamThemeToggle } from "./QamThemeToggle"; export function QamThemeList() { - const themes = useCSSLoaderStateValue("themes"); - const unpinnedThemes = useCSSLoaderStateValue("unpinnedThemes"); + const themes = useCSSLoaderValue("themes"); + const unpinnedThemes = useCSSLoaderValue("unpinnedThemes"); if (themes.length === 0) { return You have no themes, visit the theme store to download some!; diff --git a/src/modules/qam-tab-page/components/QamThemeToggle.tsx b/src/modules/qam-tab-page/components/QamThemeToggle.tsx index 07ad32a..0521ffc 100644 --- a/src/modules/qam-tab-page/components/QamThemeToggle.tsx +++ b/src/modules/qam-tab-page/components/QamThemeToggle.tsx @@ -1,4 +1,4 @@ -import { useCSSLoaderAction, useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; import { ThemePatch, toggleThemeWithModals, useForcedRerender } from "@/lib"; import { useEffect, useState } from "react"; import { LocalThemeStatus, Theme } from "@/types"; @@ -6,8 +6,8 @@ import { ButtonItem, Focusable, PanelSectionRow, ToggleField, showModal } from " import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; export function QamThemeToggle({ theme }: { theme: Theme }) { - const updateStatuses = useCSSLoaderStateValue("updateStatuses"); - const isWorking = useCSSLoaderStateValue("isWorking"); + const updateStatuses = useCSSLoaderValue("updateStatuses"); + const isWorking = useCSSLoaderValue("isWorking"); const installTheme = useCSSLoaderAction("installTheme"); const [collapsed, setCollapsed] = useState(true); diff --git a/src/modules/qam-tab-page/pages/QamTabPage.tsx b/src/modules/qam-tab-page/pages/QamTabPage.tsx index b93a535..19ce28f 100644 --- a/src/modules/qam-tab-page/pages/QamTabPage.tsx +++ b/src/modules/qam-tab-page/pages/QamTabPage.tsx @@ -6,10 +6,10 @@ import { QamRefreshButton, QamThemeList, } from "../components"; -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; export function QamTabPage() { - const themes = useCSSLoaderStateValue("themes"); + const themes = useCSSLoaderValue("themes"); return ( <> diff --git a/src/modules/theme-store/components/BrowserSearchFields.tsx b/src/modules/theme-store/components/BrowserSearchFields.tsx index 6bed73d..4e72835 100644 --- a/src/modules/theme-store/components/BrowserSearchFields.tsx +++ b/src/modules/theme-store/components/BrowserSearchFields.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { - useThemeBrowserSharedStateAction, - useThemeBrowserSharedStateValue, + useThemeBrowserSharedAction, + useThemeBrowserSharedValue, useThemeBrowserStoreAction, useThemeBrowserStoreValue, } from "../context"; @@ -23,8 +23,8 @@ export function BrowserSearchFields() { const setSearchOpts = useThemeBrowserStoreAction("setSearchOpts"); const refreshThemes = useThemeBrowserStoreAction("refreshThemes"); - const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize"); - const setBrowserCardSize = useThemeBrowserSharedStateAction("setBrowserCardSize"); + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); + const setBrowserCardSize = useThemeBrowserSharedAction("setBrowserCardSize"); const formattedFilters: DropdownOption[] = useMemo(() => { const totalNumOptions = Object.values(filters).reduce((acc, cur) => acc + Number(cur), 0); diff --git a/src/modules/theme-store/components/ThemeBrowserPage.tsx b/src/modules/theme-store/components/ThemeBrowserPage.tsx index 7c1cca5..51055c8 100644 --- a/src/modules/theme-store/components/ThemeBrowserPage.tsx +++ b/src/modules/theme-store/components/ThemeBrowserPage.tsx @@ -1,7 +1,7 @@ import { Focusable } from "@decky/ui"; import { useThemeBrowserStoreAction, useThemeBrowserStoreValue } from "../context"; import { BrowserSearchFields } from "./BrowserSearchFields"; -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; import { ThemeCard } from "./ThemeCard"; import { useEffect, useRef } from "react"; @@ -9,7 +9,7 @@ export function ThemeBrowserPage() { const initializeStore = useThemeBrowserStoreAction("initializeStore"); const themes = useThemeBrowserStoreValue("themes"); const indexToSnapToOnLoad = useThemeBrowserStoreValue("indexToSnapToOnLoad"); - const backendVersion = useCSSLoaderStateValue("backendVersion"); + const backendVersion = useCSSLoaderValue("backendVersion"); const endOfPageRef = useRef(null); const firstCardRef = useRef(null); diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx index 5bf61b2..0cf1717 100644 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -1,15 +1,12 @@ import { PartialCSSThemeInfo } from "@/types"; -import { - ColumnNumbers, - useThemeBrowserSharedStateValue, - useThemeBrowserStoreValue, -} from "../context"; +import { ColumnNumbers, useThemeBrowserSharedValue, useThemeBrowserStoreValue } from "../context"; import { forwardRef } from "react"; import { shortenNumber, useThemeInstallState } from "@/lib"; -import { useCSSLoaderStateValue } from "@/backend"; +import { useCSSLoaderValue } from "@/backend"; import { AiOutlineDownload } from "react-icons/ai"; import { Focusable } from "@decky/ui"; import { FaBullseye, FaDownload, FaStar } from "react-icons/fa6"; +import { useExpandedViewStateAction } from "@/modules/expanded-view"; interface ThemeCardProps { theme: PartialCSSThemeInfo; @@ -23,11 +20,13 @@ const cardWidth = { }; export const ThemeCard = forwardRef(({ theme, size }, ref) => { - const apiUrl = useCSSLoaderStateValue("apiUrl"); - const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize"); + const apiUrl = useCSSLoaderValue("apiUrl"); + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); const cols = size ?? browserCardSize; const installStatus = useThemeInstallState(theme); + const openTheme = useExpandedViewStateAction("openTheme"); + const imageUrl = theme?.images[0]?.id && theme.images[0].id !== "MISSING" ? `${apiUrl}/blobs/${theme.images[0].id}` @@ -45,7 +44,7 @@ export const ThemeCard = forwardRef(({ theme, si className="cl_storeitem_container" focusWithinClassName="gpfocuswithin" onActivate={() => { - // ADD IN CLICK LOGIC + openTheme(theme.id); }} >
diff --git a/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx b/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx index bd1ce07..3fb7dbd 100644 --- a/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx +++ b/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx @@ -1,8 +1,8 @@ import { themeCardStylesGenerator } from "@/styles"; -import { useThemeBrowserSharedStateValue } from "../context"; +import { useThemeBrowserSharedValue } from "../context"; export function ThemeCardCSSVariableProvider() { - const browserCardSize = useThemeBrowserSharedStateValue("browserCardSize"); + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); return ; } diff --git a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx index a5a2555..85d3aa8 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -26,10 +26,10 @@ const themeBrowserSharedStore = createStore((set) => { const useThemeBrowserSharedState = (fn: (state: IThemeBrowserSharedStore) => any) => useStore(themeBrowserSharedStore, fn); -export const useThemeBrowserSharedStateValue = ( +export const useThemeBrowserSharedValue = ( key: T ): IThemeBrowserSharedStore[T] => useThemeBrowserSharedState((state) => state[key]); -export const useThemeBrowserSharedStateAction = ( +export const useThemeBrowserSharedAction = ( key: T ): IThemeBrowserSharedStore[T] => useThemeBrowserSharedState((state) => state[key]); diff --git a/src/modules/theme-store/index.ts b/src/modules/theme-store/index.ts new file mode 100644 index 0000000..c4e34b2 --- /dev/null +++ b/src/modules/theme-store/index.ts @@ -0,0 +1 @@ +export * from "./pages"; diff --git a/src/modules/theme-store/pages/index.ts b/src/modules/theme-store/pages/index.ts new file mode 100644 index 0000000..d0b93b7 --- /dev/null +++ b/src/modules/theme-store/pages/index.ts @@ -0,0 +1 @@ +export * from "./ThemeStoreRouter"; diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index a7d30d2..3fcef40 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -215,7 +215,7 @@ export const styles = ` /* Store Theme Cards */ /* The variables should be injected wherever needed */ -/* This one actually is based on font-size, so EM makes sense */ +/* This module actually is based on font-size, so EM makes sense over REM */ .cl_storeitem_notifbubble { position: absolute; @@ -309,4 +309,20 @@ export const styles = ` .cl_storeitem_subtitle { font-size: 0.75em; } + +/* Expanded View */ + +@keyframes cl_spin { + to { + transform: rotate(360deg); + } +} +.cl_spinny { + animation: cl_spin 1s linear infinite; +} + +.cl_fullscreen_loadingtext { + font-size: 2.5rem; + font-weight: bold; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index 86c5b0f..2c3fc54 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -216,7 +216,7 @@ /* Store Theme Cards */ /* The variables should be injected wherever needed */ -/* This one actually is based on font-size, so EM makes sense */ +/* This module actually is based on font-size, so EM makes sense over REM */ .cl_storeitem_notifbubble { position: absolute; @@ -309,4 +309,33 @@ } .cl_storeitem_subtitle { font-size: 0.75em; +} + +/* Expanded View */ + +@keyframes cl_spin { + to { + transform: rotate(360deg); + } +} +.cl_spinny { + animation: cl_spin 1s linear infinite; +} + +.cl_expandedview_loadingtext { + font-size: 2.5rem; + font-weight: bold; +} + +.cl_expandedview_container { + background: rgb(27, 40, 56); + padding: auto 1rem; + gap: 1rem; + display: flex; + justify-content: space-between; +} + +.cl_expandedview_scrollcontainer { + display: flex; + margin-bottom: 40px; } \ No newline at end of file From 5d99e68e0e8f44135f80195128ac28986b0014b5 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:40:37 -0600 Subject: [PATCH 17/53] finish expandedview image section --- .../components/ExpandedViewCssVariables.tsx | 21 ++ .../components/ExpandedViewImageContainer.tsx | 71 +++++++ .../ExpandedViewScrollingSection.tsx | 12 +- src/modules/expanded-view/components/index.ts | 1 + .../context/ExpandedViewStore.tsx | 58 ++++- .../expanded-view/pages/ExpandedViewPage.tsx | 7 +- src/styles/styles.css | 201 +++++++++++------- 7 files changed, 291 insertions(+), 80 deletions(-) create mode 100644 src/modules/expanded-view/components/ExpandedViewCssVariables.tsx create mode 100644 src/modules/expanded-view/components/ExpandedViewImageContainer.tsx diff --git a/src/modules/expanded-view/components/ExpandedViewCssVariables.tsx b/src/modules/expanded-view/components/ExpandedViewCssVariables.tsx new file mode 100644 index 0000000..b7e0ac2 --- /dev/null +++ b/src/modules/expanded-view/components/ExpandedViewCssVariables.tsx @@ -0,0 +1,21 @@ +import { useExpandedViewValue } from "../context"; + +export function ExpandedViewCssVariables() { + const imageDimensionKeys = useExpandedViewValue("imageAreaStyleKeys"); + + return ( + + ); +} diff --git a/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx b/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx new file mode 100644 index 0000000..6ccfb49 --- /dev/null +++ b/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx @@ -0,0 +1,71 @@ +import { Focusable, ScrollPanelGroup } from "@decky/ui"; +import { useExpandedViewAction, useExpandedViewValue } from "../context"; + +export function ExpandedViewImageContainer() { + const data = useExpandedViewValue("data"); + const { + imageCarouselEntryWidth, + imageCarouselEntryHeight, + selectedImageHeight, + selectedImageWidth, + } = useExpandedViewValue("imageAreaStyleKeys"); + const focusedImageId = useExpandedViewValue("focusedImageId"); + + const setFocusedImage = useExpandedViewAction("setFocusedImage"); + + return ( +
+ {/* Image Carousel Container */} + {data.images.length > 1 && ( + + {data.images.map((image) => ( + { + setFocusedImage(image.id); + }} + className="cl_expandedview_imagecarouselentry" + focusWithinClassName="gpfocuswithin" + onActivate={() => {}} + > + + + ))} + + )} + + {/* Selected Image Display */} + {}} + > + 0 + ? `https://api.deckthemes.com/blobs/${focusedImageId}` + : `https://share.deckthemes.com/cssplaceholder.png` + } + /> + {data.images.length > 1 && ( +
+ + {data.images.findIndex((blob) => blob.id === focusedImageId) + 1}/{data.images.length} + +
+ )} +
+
+ ); +} diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx index 06ace3c..973f599 100644 --- a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -1,12 +1,14 @@ import { ScrollPanelGroup } from "@decky/ui"; -import { useExpandedViewAction } from "../context"; +import { useExpandedViewAction, useExpandedViewValue } from "../context"; +import { ExpandedViewImageContainer } from "./ExpandedViewImageContainer"; export function ExpandedViewScrollingSection() { const close = useExpandedViewAction("close"); + return ( + > +
+ +
+ ); } diff --git a/src/modules/expanded-view/components/index.ts b/src/modules/expanded-view/components/index.ts index 9b1d7e8..10fd0d0 100644 --- a/src/modules/expanded-view/components/index.ts +++ b/src/modules/expanded-view/components/index.ts @@ -1,2 +1,3 @@ export * from "./ExpandedViewLoadingPage"; export * from "./ExpandedViewScrollingSection"; +export * from "./ExpandedViewCssVariables"; diff --git a/src/modules/expanded-view/context/ExpandedViewStore.tsx b/src/modules/expanded-view/context/ExpandedViewStore.tsx index 93f91aa..f8f1f3e 100644 --- a/src/modules/expanded-view/context/ExpandedViewStore.tsx +++ b/src/modules/expanded-view/context/ExpandedViewStore.tsx @@ -8,22 +8,64 @@ interface IExpandedViewStoreValues { error: string | null; openedId: string | null; data: FullCSSThemeInfo; + focusedImageId: string; + imageAreaStyleKeys: { + imageAreaWidth: number; + imageAreaPadding: number; + gapBetweenCarouselAndImage: number; + selectedImageWidth: number; + selectedImageHeight: number; + imageCarouselEntryWidth: number; + imageCarouselEntryHeight: number; + }; } interface IExpandedViewStoreActions { openTheme: (themeId: string) => Promise; downloadTheme: () => Promise; + setFocusedImage: (imageId: string) => void; close: () => void; } export interface IExpandedViewStore extends IExpandedViewStoreValues, IExpandedViewStoreActions {} const expandedViewStore = createStore((set, get) => { + function setImageSizes() { + const imageAreaWidth = 556; + const imageAreaPadding = 16; + const gapBetweenCarouselAndImage = 8; + const selectedImageWidth = + get().data?.images?.length > 1 ? 434.8 : imageAreaWidth - imageAreaPadding * 2; + const selectedImageHeight = (selectedImageWidth / 16) * 10; + const imageCarouselEntryWidth = + imageAreaWidth - imageAreaPadding * 2 - selectedImageWidth - gapBetweenCarouselAndImage; + set({ + imageAreaStyleKeys: { + imageAreaWidth, + imageAreaPadding, + gapBetweenCarouselAndImage, + selectedImageWidth, + selectedImageHeight, + imageCarouselEntryWidth, + imageCarouselEntryHeight: (imageCarouselEntryWidth / 16) * 10, + }, + }); + } return { loaded: false, openedId: null, data: {} as FullCSSThemeInfo, error: null, + focusedImageId: "", + imageAreaStyleKeys: { + imageAreaWidth: 0, + imageAreaPadding: 0, + gapBetweenCarouselAndImage: 0, + selectedImageWidth: 0, + selectedImageHeight: 0, + imageCarouselEntryWidth: 0, + imageCarouselEntryHeight: 0, + }, openTheme: async (themeId) => { set({ loaded: false, error: null, openedId: themeId }); Navigation.Navigate("/cssloader/expanded-view"); @@ -31,12 +73,14 @@ const expandedViewStore = createStore((set, get) => { try { const response = await apiFetch(`/themes/${themeId}`); if (response) { - set({ data: response, loaded: true }); + set({ data: response, loaded: true, focusedImageId: response.images[0]?.id || "" }); + setImageSizes(); return; } throw new Error("No response returned"); } catch (error) { set({ error: "Error fetching theme!", loaded: true }); + setImageSizes(); } }, downloadTheme: async () => { @@ -48,9 +92,19 @@ const expandedViewStore = createStore((set, get) => { // } }, close: () => { - set({ loaded: false, openedId: null, data: {} as FullCSSThemeInfo, error: null }); + set({ + loaded: false, + openedId: null, + data: {} as FullCSSThemeInfo, + error: null, + focusedImageId: "", + }); + setImageSizes(); Navigation.NavigateBack(); }, + setFocusedImage: (imageId) => { + set({ focusedImageId: imageId }); + }, }; }); diff --git a/src/modules/expanded-view/pages/ExpandedViewPage.tsx b/src/modules/expanded-view/pages/ExpandedViewPage.tsx index dc663ca..a3bddb2 100644 --- a/src/modules/expanded-view/pages/ExpandedViewPage.tsx +++ b/src/modules/expanded-view/pages/ExpandedViewPage.tsx @@ -1,4 +1,8 @@ -import { ExpandedViewLoadingPage, ExpandedViewScrollingSection } from "../components"; +import { + ExpandedViewLoadingPage, + ExpandedViewCssVariables, + ExpandedViewScrollingSection, +} from "../components"; import { useExpandedViewValue } from "../context"; export function ExpandedViewPage() { @@ -11,6 +15,7 @@ export function ExpandedViewPage() { return (
+
); diff --git a/src/styles/styles.css b/src/styles/styles.css index 2c3fc54..c6d2545 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -42,6 +42,10 @@ justify-content: center !important; } +.justify-around { + justify-content: space-around !important; +} + .justify-between { justify-content: space-between !important; } @@ -219,96 +223,96 @@ /* This module actually is based on font-size, so EM makes sense over REM */ .cl_storeitem_notifbubble { - position: absolute; - background: linear-gradient(135deg, #fca904 50%, transparent 51%); - z-index: 10001; - left: 0; - top: 0; + position: absolute !important; + background: linear-gradient(135deg, #fca904 50%, transparent 51%) !important; + z-index: 10001 !important; + left: 0 !important; + top: 0 !important; color: black; - font-size: var(--cl-storeitem-fontsize); - width: var(--cl-storeitem-bubblesize); - height: var(--cl-storeitem-bubblesize); + font-size: var(--cl-storeitem-fontsize) !important; + width: var(--cl-storeitem-bubblesize) !important; + height: var(--cl-storeitem-bubblesize) !important; } .cl_storeitem_bubbleicon { - padding: 0.25em; + padding: 0.25em !important; } .cl_storeitem_container { - display: flex; - flex-direction: column; - background-color: #ACB2C924; - overflow: hidden; - width: var(--cl-storeitem-width); + display: flex !important; + flex-direction: column !important; + background-color: #ACB2C924 !important; + overflow: hidden !important; + width: var(--cl-storeitem-width) !important; } .gpfocuswithin.cl_storeitem_container { - background-color: #ACB2C947; + background-color: #ACB2C947 !important; } .cl_storeitem_imagecontainer { - overflow: hidden; - position: relative; - width: var(--cl-storeitem-width); - height: var(--cl-storeitem-imgheight); + overflow: hidden !important; + position: relative !important; + width: var(--cl-storeitem-width) !important; + height: var(--cl-storeitem-imgheight) !important; } .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); + display: flex !important; + gap: 0.5em !important; + width: 100% !important; + align-items: center !important; + justify-content: center !important; + position: absolute !important; + bottom: 0 !important; + transform: translateY(100%) !important; + opacity: 0 !important; + transition-property: transform,opacity !important; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83) !important; + transition-duration: 0.15s !important; + font-size: var(--cl-storeitem-fontsize) !important; } .gpfocuswithin > div > .cl_storeitem_supinfocontainer { - transform: translateY(0); - opacity: 1; - transition-delay: 0.1s; + transform: translateY(0) !important; + opacity: 1 !important; + transition-delay: 0.1s !important; } .cl_storeitem_maininfocontainer { - display: flex; - flex-direction: column; - padding: 0.5em; - font-size: var(--cl-storeitem-fontsize); + display: flex !important; + flex-direction: column !important; + padding: 0.5em !important; + font-size: var(--cl-storeitem-fontsize) !important; } .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); + object-fit: cover !important; + transition-property: filter,transform !important; + transition-duration: 0.32s !important; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83) !important; } .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); + position: absolute !important; + top: 0 !important; + left: 0 !important; + opacity: 0 !important; + transition-property: opacity !important; + transition-duration: 0.65s !important; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83) !important; + background: linear-gradient(0deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 30%) !important; + mix-blend-mode: multiply !important; + width: var(--cl-storeitem-width) !important; + height: var(--cl-storeitem-imgheight) !important; } .gpfocuswithin > div > .cl_storeitem_imagedarkener { - opacity: 1; + opacity: 1 !important; } .cl_storeitem_title { - font-weight: bold; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; + font-weight: bold !important; + text-overflow: ellipsis !important; + overflow: hidden !important; + white-space: nowrap !important; } .cl_storeitem_iconinfoitem { - display: flex; - gap: 0.25em; - align-items: center; + display: flex !important; + gap: 0.25em !important; + align-items: center !important; } .cl_storeitem_subtitle { - font-size: 0.75em; + font-size: 0.75em !important; } /* Expanded View */ @@ -319,23 +323,72 @@ } } .cl_spinny { - animation: cl_spin 1s linear infinite; + animation: cl_spin 1s linear infinite !important; } .cl_expandedview_loadingtext { - font-size: 2.5rem; - font-weight: bold; + font-size: 2.5rem !important; + font-weight: bold !important; } .cl_expandedview_container { - background: rgb(27, 40, 56); - padding: auto 1rem; - gap: 1rem; - display: flex; - justify-content: space-between; + background: rgb(27, 40, 56) !important; + padding: auto 1rem !important; + gap: 1rem !important; + display: flex !important; + justify-content: space-between !important; +} + +.cl_expandedview_scrollpanel { + display: flex !important; + margin-bottom: 40px !important; +} + +.cl_expandedview_themedatacontainer { + display: flex !important; + flex-direction: column !important; + height: max-content !important; + min-height: 100% !important; + background: rgba(14, 20, 27, 0.8) !important; + width: var(--cl-ev-image-area-width) !important; +} + +.cl_expandedview_imageareacontainer { + display: flex !important; + gap: var(--cl-ev-gap-between-carousel-and-image) !important; + padding: var(--cl-ev-image-area-padding) !important; +} + +.cl_expandedview_imagecarouselcontainer { + display: flex !important; + justify-content: space-around !important; + flex-direction: column !important; + width: var(--cl-ev-image-carousel-entry-width) !important; + height: var(--cl-ev-selected-image-height) !important; } -.cl_expandedview_scrollcontainer { - display: flex; - margin-bottom: 40px; +.cl_expandedview_imagecarouselentry { + width: var(--cl-ev-image-carousel-entry-width) !important; + height: var(--cl-ev-image-carousel-entry-height) !important; + position: relative !important; +} + +.cl_expandedview_selectedimage { + position: relative !important; + width: var(--cl-ev-selected-image-width) !important; + height: var(--cl-ev-selected-image-height) !important; +} + +.cl_expandedview_imagenumbercontainer { + .image-number-container { + width: 3em !important; + height: 2em !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + background: #000a !important; + position: absolute !important; + bottom: 1em !important; + right: 1em !important; + } } \ No newline at end of file From fd0767e345a2436dcc277e070fd4882abb710625 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:02:06 -0600 Subject: [PATCH 18/53] No rest for the shitty js devs --- src/modules/theme-store/components/ThemeCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx index 0cf1717..d248af1 100644 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -6,7 +6,7 @@ import { useCSSLoaderValue } from "@/backend"; import { AiOutlineDownload } from "react-icons/ai"; import { Focusable } from "@decky/ui"; import { FaBullseye, FaDownload, FaStar } from "react-icons/fa6"; -import { useExpandedViewStateAction } from "@/modules/expanded-view"; +import { useExpandedViewAction } from "@/modules/expanded-view"; interface ThemeCardProps { theme: PartialCSSThemeInfo; From 013ee9b2a7df665a3c6db61aee515d800501d733 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:11:42 -0600 Subject: [PATCH 19/53] WIP: further work on expanded view --- src/backend-impl/decky-theme-store.ts | 2 +- .../components/ExpandedViewButtonsSection.tsx | 102 +++++++ .../ExpandedViewScrollingSection.tsx | 58 +++- src/modules/expanded-view/components/index.ts | 1 + .../context/ExpandedViewStore.tsx | 65 ++++- .../expanded-view/pages/ExpandedViewPage.tsx | 2 + src/styles/styles-as-string.ts | 273 +++++++++++++----- src/styles/styles.css | 73 +++++ 8 files changed, 492 insertions(+), 84 deletions(-) create mode 100644 src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx diff --git a/src/backend-impl/decky-theme-store.ts b/src/backend-impl/decky-theme-store.ts index 2fd68c0..6339dcf 100644 --- a/src/backend-impl/decky-theme-store.ts +++ b/src/backend-impl/decky-theme-store.ts @@ -7,7 +7,7 @@ import { import { backend } from "./decky-backend-service"; import { useStore } from "zustand"; -const cssLoaderStore = createCSSLoaderStore(backend); +export const cssLoaderStore = createCSSLoaderStore(backend); const useCSSLoaderStore = (fn: (state: ICSSLoaderState) => any) => useStore(cssLoaderStore, fn); diff --git a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx new file mode 100644 index 0000000..7623ac3 --- /dev/null +++ b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx @@ -0,0 +1,102 @@ +import { shortenNumber, useThemeInstallState } from "@/lib"; +import { useExpandedViewAction, useExpandedViewValue } from "../context"; +import { FaRegStar, FaStar } from "react-icons/fa"; +import { DialogButton } from "@decky/ui"; +import { useState } from "react"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { ImCog } from "react-icons/im"; + +export function ExpandedViewButtonsSection() { + const data = useExpandedViewValue("data"); + const isStarred = useExpandedViewValue("isStarred"); + const toggleStar = useExpandedViewAction("toggleStar"); + + const apiFullToken = useCSSLoaderValue("apiFullToken"); + const [starButtonBlurred, setStarButtonBlurred] = useState(false); + + const isWorking = useCSSLoaderValue("isWorking"); + + const installTheme = useCSSLoaderAction("installTheme"); + + const installStatus = useThemeInstallState(data); + + async function handleStar() { + setStarButtonBlurred(true); + await toggleStar(); + setStarButtonBlurred(false); + } + + return ( +
+ {/* Star */} +
+
+
+ {isStarred ? : } + {/* Need to make the text size smaller or else it wraps */} + = 100 ? "0.75rem" : "1rem" }}> + {shortenNumber(data.starCount) ?? data.starCount} Star + {data.starCount === 1 ? "" : "s"} + +
+ void handleStar()} + disabled={starButtonBlurred || !apiFullToken} + > +
+ + {!apiFullToken ? "Log In to Star" : isStarred ? "Unstar Theme" : "Star Theme"} + +
+
+
+
+ + {/* Download / Configure */} +
+
+ Install {data.displayName} + + {shortenNumber(data.download.downloadCount) ?? data.download.downloadCount} Download + {data.download.downloadCount === 1 ? "" : "s"} + + { + installTheme(data.id); + }} + > + + {installStatus === "installed" && "Reinstall"} + {installStatus === "outdated" && "Update"} + {installStatus === "notinstalled" && "Install"} + + + {installStatus === "installed" && ( + { + // TODO: THEME SETTINGS MODAL + // showModal( + // e.id === fullThemeData.id)?.id || + // // using name here because in submissions id is different + // installedThemes.find((e) => e.name === fullThemeData.name)!.id + // } + // /> + // ); + }} + className="relative" + > + + + )} +
+
+
+ ); +} diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx index 973f599..c0c7c2b 100644 --- a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -1,8 +1,9 @@ -import { ScrollPanelGroup } from "@decky/ui"; +import { DialogButton, Focusable, ScrollPanelGroup } from "@decky/ui"; import { useExpandedViewAction, useExpandedViewValue } from "../context"; import { ExpandedViewImageContainer } from "./ExpandedViewImageContainer"; export function ExpandedViewScrollingSection() { + const data = useExpandedViewValue("data"); const close = useExpandedViewAction("close"); return ( @@ -21,6 +22,61 @@ export function ExpandedViewScrollingSection() { >
+
+ {/* Title / Version */} +
+ {data.displayName} + {data.version} +
+ {/* Author / Modified Date */} +
+ { + // TODO: MODAL + // showModal(); + }} + > + By {data.specifiedAuthor} + + Last Updated {new Date(data.updated).toLocaleDateString()} +
+ {/* Description */} + {}} + > + Description + 400 ? "text-sm" : ""}> + {data.description || ( + No description provided. + )} + + + {/* Targets */} +
+ Targets +
+ {data.targets.map((target) => ( + { + // TODO: target navigation + // setGlobalState("themeSearchOpts", { ...themeSearchOpts, filters: e }); + // setGlobalState("currentTab", "ThemeBrowser"); + // setGlobalState("forceScrollBackUp", true); + // Navigation.NavigateBack(); + }} + className="cl_expandedview_targetbutton" + > + {target} + + ))} +
+
+
); diff --git a/src/modules/expanded-view/components/index.ts b/src/modules/expanded-view/components/index.ts index 10fd0d0..640ffca 100644 --- a/src/modules/expanded-view/components/index.ts +++ b/src/modules/expanded-view/components/index.ts @@ -1,3 +1,4 @@ export * from "./ExpandedViewLoadingPage"; export * from "./ExpandedViewScrollingSection"; export * from "./ExpandedViewCssVariables"; +export * from "./ExpandedViewButtonsSection"; diff --git a/src/modules/expanded-view/context/ExpandedViewStore.tsx b/src/modules/expanded-view/context/ExpandedViewStore.tsx index f8f1f3e..269b89d 100644 --- a/src/modules/expanded-view/context/ExpandedViewStore.tsx +++ b/src/modules/expanded-view/context/ExpandedViewStore.tsx @@ -1,4 +1,4 @@ -import { getCSSLoaderState } from "@/backend"; +import { cssLoaderStore, getCSSLoaderState } from "@/backend"; import { FullCSSThemeInfo } from "@/types"; import { Navigation } from "@decky/ui"; import { createStore, useStore } from "zustand"; @@ -7,6 +7,7 @@ interface IExpandedViewStoreValues { loaded: boolean; error: string | null; openedId: string | null; + isStarred: boolean; data: FullCSSThemeInfo; focusedImageId: string; imageAreaStyleKeys: { @@ -22,7 +23,7 @@ interface IExpandedViewStoreValues { interface IExpandedViewStoreActions { openTheme: (themeId: string) => Promise; - downloadTheme: () => Promise; + toggleStar: () => Promise; setFocusedImage: (imageId: string) => void; close: () => void; } @@ -55,6 +56,7 @@ const expandedViewStore = createStore((set, get) => { loaded: false, openedId: null, data: {} as FullCSSThemeInfo, + isStarred: false, error: null, focusedImageId: "", imageAreaStyleKeys: { @@ -69,31 +71,64 @@ const expandedViewStore = createStore((set, get) => { openTheme: async (themeId) => { set({ loaded: false, error: null, openedId: themeId }); Navigation.Navigate("/cssloader/expanded-view"); - const { apiFetch } = getCSSLoaderState(); + const { apiFetch, apiFullToken } = getCSSLoaderState(); try { const response = await apiFetch(`/themes/${themeId}`); - if (response) { - set({ data: response, loaded: true, focusedImageId: response.images[0]?.id || "" }); - setImageSizes(); - return; + if (!response) { + throw new Error("No response returned"); + } + set({ data: response, loaded: true, focusedImageId: response.images[0]?.id || "" }); + setImageSizes(); + + if (!apiFullToken) return; + const starResponse = await apiFetch<{ starred: boolean }>( + `/users/me/stars/${themeId}`, + {}, + true + ); + if (!starResponse) { + // Silently error + set({ isStarred: false }); + } + set({ isStarred: starResponse.starred }); + // If you star and then quickly refresh, the API hasn't updated the cached starcount + if (response.starCount === 0) { + set({ data: { ...response, starCount: 1 } }); } - throw new Error("No response returned"); } catch (error) { set({ error: "Error fetching theme!", loaded: true }); setImageSizes(); } }, - downloadTheme: async () => { - // const { apiFetch } = getCSSLoaderState(); - // try { - // await apiFetch(`/theme/${get().data.id}/download`, {}, true); - // } catch (error) { - // set({ error: "Error downloading theme!" }); - // } + toggleStar: async () => { + try { + const { data, isStarred } = get(); + const { apiFetch, apiFullToken } = getCSSLoaderState(); + if (!apiFullToken && !data.id) return; + const response = await apiFetch(`/users/me/stars/${data.id}`, { + method: isStarred ? "DELETE" : "POST", + }); + const newIsStarred = !isStarred; + set({ + isStarred: newIsStarred, + data: { + ...data, + starCount: newIsStarred + ? data.starCount + 1 + : // If it was at 0 stars before (api hadn't updated, prevent it from going to -1) + data.starCount === 0 + ? 0 + : data.starCount - 1, + }, + }); + } catch (error) { + // TODO: (potentially) handle error + } }, close: () => { set({ loaded: false, + isStarred: false, openedId: null, data: {} as FullCSSThemeInfo, error: null, diff --git a/src/modules/expanded-view/pages/ExpandedViewPage.tsx b/src/modules/expanded-view/pages/ExpandedViewPage.tsx index a3bddb2..956e98d 100644 --- a/src/modules/expanded-view/pages/ExpandedViewPage.tsx +++ b/src/modules/expanded-view/pages/ExpandedViewPage.tsx @@ -2,6 +2,7 @@ import { ExpandedViewLoadingPage, ExpandedViewCssVariables, ExpandedViewScrollingSection, + ExpandedViewButtonsSection, } from "../components"; import { useExpandedViewValue } from "../context"; @@ -17,6 +18,7 @@ export function ExpandedViewPage() {
+
); } diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 3fcef40..bfa56dd 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -13,6 +13,10 @@ export const styles = ` flex-wrap: wrap !important; } +.gap-1 { + gap: 0.25rem !important; +} + .gap-2 { gap: 0.5rem !important; } @@ -41,6 +45,10 @@ export const styles = ` justify-content: center !important; } +.justify-around { + justify-content: space-around !important; +} + .justify-between { justify-content: space-between !important; } @@ -69,6 +77,13 @@ export const styles = ` font-weight: bold !important; } +.absolute-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + /* Fullscreen Routes */ .cl_fullscreenroute_container { @@ -218,96 +233,96 @@ export const styles = ` /* This module actually is based on font-size, so EM makes sense over REM */ .cl_storeitem_notifbubble { - position: absolute; - background: linear-gradient(135deg, #fca904 50%, transparent 51%); - z-index: 10001; - left: 0; - top: 0; + position: absolute !important; + background: linear-gradient(135deg, #fca904 50%, transparent 51%) !important; + z-index: 10001 !important; + left: 0 !important; + top: 0 !important; color: black; - font-size: var(--cl-storeitem-fontsize); - width: var(--cl-storeitem-bubblesize); - height: var(--cl-storeitem-bubblesize); + font-size: var(--cl-storeitem-fontsize) !important; + width: var(--cl-storeitem-bubblesize) !important; + height: var(--cl-storeitem-bubblesize) !important; } .cl_storeitem_bubbleicon { - padding: 0.25em; + padding: 0.25em !important; } .cl_storeitem_container { - display: flex; - flex-direction: column; - background-color: #ACB2C924; - overflow: hidden; - width: var(--cl-storeitem-width); + display: flex !important; + flex-direction: column !important; + background-color: #ACB2C924 !important; + overflow: hidden !important; + width: var(--cl-storeitem-width) !important; } .gpfocuswithin.cl_storeitem_container { - background-color: #ACB2C947; + background-color: #ACB2C947 !important; } .cl_storeitem_imagecontainer { - overflow: hidden; - position: relative; - width: var(--cl-storeitem-width); - height: var(--cl-storeitem-imgheight); + overflow: hidden !important; + position: relative !important; + width: var(--cl-storeitem-width) !important; + height: var(--cl-storeitem-imgheight) !important; } .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); + display: flex !important; + gap: 0.5em !important; + width: 100% !important; + align-items: center !important; + justify-content: center !important; + position: absolute !important; + bottom: 0 !important; + transform: translateY(100%) !important; + opacity: 0 !important; + transition-property: transform,opacity !important; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83) !important; + transition-duration: 0.15s !important; + font-size: var(--cl-storeitem-fontsize) !important; } .gpfocuswithin > div > .cl_storeitem_supinfocontainer { - transform: translateY(0); - opacity: 1; - transition-delay: 0.1s; + transform: translateY(0) !important; + opacity: 1 !important; + transition-delay: 0.1s !important; } .cl_storeitem_maininfocontainer { - display: flex; - flex-direction: column; - padding: 0.5em; - font-size: var(--cl-storeitem-fontsize); + display: flex !important; + flex-direction: column !important; + padding: 0.5em !important; + font-size: var(--cl-storeitem-fontsize) !important; } .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); + object-fit: cover !important; + transition-property: filter,transform !important; + transition-duration: 0.32s !important; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83) !important; } .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); + position: absolute !important; + top: 0 !important; + left: 0 !important; + opacity: 0 !important; + transition-property: opacity !important; + transition-duration: 0.65s !important; + transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83) !important; + background: linear-gradient(0deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 30%) !important; + mix-blend-mode: multiply !important; + width: var(--cl-storeitem-width) !important; + height: var(--cl-storeitem-imgheight) !important; } .gpfocuswithin > div > .cl_storeitem_imagedarkener { - opacity: 1; + opacity: 1 !important; } .cl_storeitem_title { - font-weight: bold; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; + font-weight: bold !important; + text-overflow: ellipsis !important; + overflow: hidden !important; + white-space: nowrap !important; } .cl_storeitem_iconinfoitem { - display: flex; - gap: 0.25em; - align-items: center; + display: flex !important; + gap: 0.25em !important; + align-items: center !important; } .cl_storeitem_subtitle { - font-size: 0.75em; + font-size: 0.75em !important; } /* Expanded View */ @@ -318,11 +333,135 @@ export const styles = ` } } .cl_spinny { - animation: cl_spin 1s linear infinite; + animation: cl_spin 1s linear infinite !important; +} + +.cl_expandedview_loadingtext { + font-size: 2.5rem !important; + font-weight: bold !important; +} + +.cl_expandedview_container { + background: rgb(27, 40, 56) !important; + padding: auto 1rem !important; + gap: 1rem !important; + display: flex !important; + justify-content: space-between !important; +} + +.cl_expandedview_scrollpanel { + display: flex !important; + margin-bottom: 40px !important; +} + +.cl_expandedview_themedatacontainer { + display: flex !important; + flex-direction: column !important; + height: max-content !important; + min-height: 100% !important; + background: rgba(14, 20, 27, 0.8) !important; + width: var(--cl-ev-image-area-width) !important; +} + +.cl_expandedview_imageareacontainer { + display: flex !important; + gap: var(--cl-ev-gap-between-carousel-and-image) !important; + padding: var(--cl-ev-image-area-padding) !important; +} + +.cl_expandedview_imagecarouselcontainer { + display: flex !important; + justify-content: space-around !important; + flex-direction: column !important; + width: var(--cl-ev-image-carousel-entry-width) !important; + height: var(--cl-ev-selected-image-height) !important; +} + +.cl_expandedview_imagecarouselentry { + width: var(--cl-ev-image-carousel-entry-width) !important; + height: var(--cl-ev-image-carousel-entry-height) !important; + position: relative !important; +} + +.cl_expandedview_selectedimage { + position: relative !important; + width: var(--cl-ev-selected-image-width) !important; + height: var(--cl-ev-selected-image-height) !important; +} + +.cl_expandedview_imagenumbercontainer { + .image-number-container { + width: 3em !important; + height: 2em !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + background: #000a !important; + position: absolute !important; + bottom: 1em !important; + right: 1em !important; + } +} + +.cl_expandedview_title { + white-space: nowrap !important; + text-overflow: ellipsis !important; + overflow: hidden !important; + font-size: 1.5rem !important; + font-weight: bold !important; } -.cl_fullscreen_loadingtext { - font-size: 2.5rem; - font-weight: bold; +.cl_expandedview_version { + font-size: 1.25rem !important; + font-weight: bold !important; +} + +.cl_expandedview_graytext { + color: rgb(124, 142, 163) !important; +} + +.cl_expandedview_bluetext { + color: rgb(26, 159, 255) !important; +} + +.cl_expandedview_targetbutton { + background: rgba(59, 90, 114, 0.5) !important; + color: rgb(26, 159, 255) !important; + padding: 8px 12px !important; + width: fit-content !important; +} + +.cl_expandedview_buttonscontainer { + position: sticky !important; + padding-top: 1em !important; + flex: 1 !important; + display: flex !important; + flex-direction: column !important; + gap: 0.25em !important; +} + +.cl_expandedview_singlebuttoncontainer { + background: #2a4153 !important; + padding: 1em !important; +} + +.cl_expandedview_starbutton { + min-width: 30% !important; + padding: 8px 12px !important; + width: fit-content !important; +} + +.cl_expandedview_installtext { + width: 200px !important; + white-space: nowrap !important; + text-overflow: ellipsis !important; + overflow: hidden !important; +} + +.cl_expandedview_bluebutton { + background: #1a9fff !important; +} +.cl_expandedview_bluebutton.gpfocuswithin { + background: white !important; } `; diff --git a/src/styles/styles.css b/src/styles/styles.css index c6d2545..8e6a5b8 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -14,6 +14,10 @@ flex-wrap: wrap !important; } +.gap-1 { + gap: 0.25rem !important; +} + .gap-2 { gap: 0.5rem !important; } @@ -74,6 +78,13 @@ font-weight: bold !important; } +.absolute-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + /* Fullscreen Routes */ .cl_fullscreenroute_container { @@ -391,4 +402,66 @@ bottom: 1em !important; right: 1em !important; } +} + +.cl_expandedview_title { + white-space: nowrap !important; + text-overflow: ellipsis !important; + overflow: hidden !important; + font-size: 1.5rem !important; + font-weight: bold !important; +} + +.cl_expandedview_version { + font-size: 1.25rem !important; + font-weight: bold !important; +} + +.cl_expandedview_graytext { + color: rgb(124, 142, 163) !important; +} + +.cl_expandedview_bluetext { + color: rgb(26, 159, 255) !important; +} + +.cl_expandedview_targetbutton { + background: rgba(59, 90, 114, 0.5) !important; + color: rgb(26, 159, 255) !important; + padding: 8px 12px !important; + width: fit-content !important; +} + +.cl_expandedview_buttonscontainer { + position: sticky !important; + padding-top: 1em !important; + flex: 1 !important; + display: flex !important; + flex-direction: column !important; + gap: 0.25em !important; +} + +.cl_expandedview_singlebuttoncontainer { + background: #2a4153 !important; + padding: 1em !important; +} + +.cl_expandedview_starbutton { + min-width: 30% !important; + padding: 8px 12px !important; + width: fit-content !important; +} + +.cl_expandedview_installtext { + width: 200px !important; + white-space: nowrap !important; + text-overflow: ellipsis !important; + overflow: hidden !important; +} + +.cl_expandedview_bluebutton { + background: #1a9fff !important; +} +.cl_expandedview_bluebutton.gpfocuswithin { + background: white !important; } \ No newline at end of file From d5c554684123608715bde4618b388f11c77200e2 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:03:06 -0600 Subject: [PATCH 20/53] quick visual fixes but HOLY FUCK CS2 OPERATION --- .../decky-backend-repository-impl.ts | 2 +- src/backend-impl/decky-theme-store.ts | 5 --- src/decky-patches/nav-patch/nav-patch.ts | 1 + .../ExpandedViewAuthorViewModal.tsx | 3 ++ .../components/ExpandedViewButtonsSection.tsx | 13 +++++-- .../components/ExpandedViewImageContainer.tsx | 4 +-- .../ExpandedViewScrollingSection.tsx | 34 +++++++++---------- .../context/ExpandedViewStore.tsx | 2 +- .../expanded-view/pages/ExpandedViewPage.tsx | 10 +++--- .../components/QamRefreshButton.tsx | 2 +- .../components/BrowserSearchFields.tsx | 7 +++- .../theme-store/components/ThemeCard.tsx | 2 +- .../context/ThemeBrowserSharedStore.tsx | 4 +++ src/styles/styles.css | 10 +++++- 14 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 src/modules/expanded-view/components/ExpandedViewAuthorViewModal.tsx diff --git a/src/backend-impl/decky-backend-repository-impl.ts b/src/backend-impl/decky-backend-repository-impl.ts index 6db65a5..1d6f369 100644 --- a/src/backend-impl/decky-backend-repository-impl.ts +++ b/src/backend-impl/decky-backend-repository-impl.ts @@ -16,7 +16,7 @@ class DeckyBackendRepository implements IBackendRepository { } async fetch(url: string, request: RequestInit) { try { - console.log("CSSLODAER FETCH", url, request); + console.debug("CSSLOADER FETCH", url, request); const res = await fetchNoCors(url, request); if (!res.ok) { throw new Error(`Res Not Okay - Code ${res.status}`); diff --git a/src/backend-impl/decky-theme-store.ts b/src/backend-impl/decky-theme-store.ts index 6339dcf..7e45a58 100644 --- a/src/backend-impl/decky-theme-store.ts +++ b/src/backend-impl/decky-theme-store.ts @@ -19,11 +19,6 @@ export const useCSSLoaderAction = ( key: T ): ICSSLoaderState[T] => useCSSLoaderStore((state) => state[key]); -const useCSSLoaderStoreSetter = - (key: T) => - (value: ICSSLoaderState[T]) => - cssLoaderStore.setState((state) => ({ ...state, [key]: value })); - export const getCSSLoaderState = () => cssLoaderStore.getState(); export const setCSSLoaderState = ( key: T, diff --git a/src/decky-patches/nav-patch/nav-patch.ts b/src/decky-patches/nav-patch/nav-patch.ts index 3efb62a..cea8973 100644 --- a/src/decky-patches/nav-patch/nav-patch.ts +++ b/src/decky-patches/nav-patch/nav-patch.ts @@ -16,6 +16,7 @@ export function enableNavPatch(): Patch { for (let t = e + n; t >= 0 && t < this.m_rgChildren.length; t += n) { // @ts-ignore const e = this.m_rgChildren[t].FindFocusableNode(r); + console.log(e.m_element); if (e && window.getComputedStyle(e.m_element).display !== "none") return e; } return null; diff --git a/src/modules/expanded-view/components/ExpandedViewAuthorViewModal.tsx b/src/modules/expanded-view/components/ExpandedViewAuthorViewModal.tsx new file mode 100644 index 0000000..a6cf5f7 --- /dev/null +++ b/src/modules/expanded-view/components/ExpandedViewAuthorViewModal.tsx @@ -0,0 +1,3 @@ +import { UserInfo } from "@/types"; + +export function ExpandedViewAuthorViewModal({ authorData }: { authorData: UserInfo }) {} diff --git a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx index 7623ac3..27a347b 100644 --- a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx @@ -2,7 +2,7 @@ import { shortenNumber, useThemeInstallState } from "@/lib"; import { useExpandedViewAction, useExpandedViewValue } from "../context"; import { FaRegStar, FaStar } from "react-icons/fa"; import { DialogButton } from "@decky/ui"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; import { ImCog } from "react-icons/im"; @@ -26,11 +26,20 @@ export function ExpandedViewButtonsSection() { setStarButtonBlurred(false); } + const downloadButtonRef = useRef(null); + const [hasBeenFocused, setHasFocused] = useState(false); + useEffect(() => { + if (downloadButtonRef?.current && !hasBeenFocused) { + downloadButtonRef.current.focus(); + setHasFocused(true); + } + }, [downloadButtonRef, hasBeenFocused]); + return (
{/* Star */}
-
+
{isStarred ? : } {/* Need to make the text size smaller or else it wraps */} diff --git a/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx b/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx index 6ccfb49..72d7997 100644 --- a/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx +++ b/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx @@ -14,7 +14,7 @@ export function ExpandedViewImageContainer() { const setFocusedImage = useExpandedViewAction("setFocusedImage"); return ( -
+ {/* Image Carousel Container */} {data.images.length > 1 && ( )} -
+ ); } diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx index c0c7c2b..cc32b50 100644 --- a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -1,11 +1,14 @@ -import { DialogButton, Focusable, ScrollPanelGroup } from "@decky/ui"; +import { DialogButton, Focusable, Navigation, ScrollPanelGroup } from "@decky/ui"; import { useExpandedViewAction, useExpandedViewValue } from "../context"; import { ExpandedViewImageContainer } from "./ExpandedViewImageContainer"; +import { useThemeBrowserSharedAction } from "@/modules/theme-store/context"; export function ExpandedViewScrollingSection() { const data = useExpandedViewValue("data"); const close = useExpandedViewAction("close"); + const setTargetOverride = useThemeBrowserSharedAction("setTargetOverride"); + return ( -
+ -
+ {/* Title / Version */} -
+
{data.displayName} {data.version}
{/* Author / Modified Date */} -
+ {data.specifiedAuthor} Last Updated {new Date(data.updated).toLocaleDateString()} -
+ {/* Description */} {/* Targets */} -
+ Targets -
+ {data.targets.map((target) => ( { - // TODO: target navigation - // setGlobalState("themeSearchOpts", { ...themeSearchOpts, filters: e }); - // setGlobalState("currentTab", "ThemeBrowser"); - // setGlobalState("forceScrollBackUp", true); - // Navigation.NavigateBack(); + setTargetOverride(target); + Navigation.NavigateBack(); }} className="cl_expandedview_targetbutton" > {target} ))} -
-
-
-
+
+ + + ); } diff --git a/src/modules/expanded-view/context/ExpandedViewStore.tsx b/src/modules/expanded-view/context/ExpandedViewStore.tsx index 269b89d..d26dc75 100644 --- a/src/modules/expanded-view/context/ExpandedViewStore.tsx +++ b/src/modules/expanded-view/context/ExpandedViewStore.tsx @@ -105,7 +105,7 @@ const expandedViewStore = createStore((set, get) => { const { data, isStarred } = get(); const { apiFetch, apiFullToken } = getCSSLoaderState(); if (!apiFullToken && !data.id) return; - const response = await apiFetch(`/users/me/stars/${data.id}`, { + await apiFetch(`/users/me/stars/${data.id}`, { method: isStarred ? "DELETE" : "POST", }); const newIsStarred = !isStarred; diff --git a/src/modules/expanded-view/pages/ExpandedViewPage.tsx b/src/modules/expanded-view/pages/ExpandedViewPage.tsx index 956e98d..fb2ea65 100644 --- a/src/modules/expanded-view/pages/ExpandedViewPage.tsx +++ b/src/modules/expanded-view/pages/ExpandedViewPage.tsx @@ -15,10 +15,12 @@ export function ExpandedViewPage() { if (error) return {error}; return ( -
- - - +
+
+ + + +
); } diff --git a/src/modules/qam-tab-page/components/QamRefreshButton.tsx b/src/modules/qam-tab-page/components/QamRefreshButton.tsx index 729b4ed..1abbe62 100644 --- a/src/modules/qam-tab-page/components/QamRefreshButton.tsx +++ b/src/modules/qam-tab-page/components/QamRefreshButton.tsx @@ -1,5 +1,5 @@ import { ButtonItem, PanelSectionRow } from "@decky/ui"; -import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { useCSSLoaderAction } from "@/backend"; export function QamRefreshButton() { const reloadPlugin = useCSSLoaderAction("reloadPlugin"); diff --git a/src/modules/theme-store/components/BrowserSearchFields.tsx b/src/modules/theme-store/components/BrowserSearchFields.tsx index 4e72835..24c2ac8 100644 --- a/src/modules/theme-store/components/BrowserSearchFields.tsx +++ b/src/modules/theme-store/components/BrowserSearchFields.tsx @@ -23,6 +23,9 @@ export function BrowserSearchFields() { const setSearchOpts = useThemeBrowserStoreAction("setSearchOpts"); const refreshThemes = useThemeBrowserStoreAction("refreshThemes"); + const targetOverride = useThemeBrowserSharedValue("targetOverride"); + const setTargetOverride = useThemeBrowserSharedAction("setTargetOverride"); + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); const setBrowserCardSize = useThemeBrowserSharedAction("setBrowserCardSize"); @@ -62,10 +65,12 @@ export function BrowserSearchFields() { menuLabel="Filter" rgOptions={formattedFilters} strDefaultLabel="All" - selectedOption={searchOpts.filters} + selectedOption={targetOverride ?? searchOpts.filters} onChange={(value) => { + // When you select a new target, remove the global override const newSearchOpts = { ...searchOpts, filters: value.data }; setSearchOpts(newSearchOpts); + setTargetOverride(null); }} />
diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx index d248af1..c50da9c 100644 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -25,7 +25,7 @@ export const ThemeCard = forwardRef(({ theme, si const cols = size ?? browserCardSize; const installStatus = useThemeInstallState(theme); - const openTheme = useExpandedViewStateAction("openTheme"); + const openTheme = useExpandedViewAction("openTheme"); const imageUrl = theme?.images[0]?.id && theme.images[0].id !== "MISSING" diff --git a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx index 85d3aa8..b7a4978 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -6,10 +6,12 @@ export type ColumnNumbers = 3 | 4 | 5; interface ThemeBrowserSharedStoreValues { browserCardSize: ColumnNumbers; + targetOverride: string | null; } interface ThemeBrowserSharedStoreActions { setBrowserCardSize: (value: ColumnNumbers) => void; + setTargetOverride: (value: string | null) => void; } interface IThemeBrowserSharedStore @@ -19,7 +21,9 @@ interface IThemeBrowserSharedStore const themeBrowserSharedStore = createStore((set) => { return { browserCardSize: 3, + targetOverride: "", setBrowserCardSize: (value: ColumnNumbers) => set({ browserCardSize: value }), + setTargetOverride: (value: string | null) => set({ targetOverride: value }), }; }); diff --git a/src/styles/styles.css b/src/styles/styles.css index 8e6a5b8..a8cf083 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -344,7 +344,7 @@ .cl_expandedview_container { background: rgb(27, 40, 56) !important; - padding: auto 1rem !important; + padding: 0 1rem !important; gap: 1rem !important; display: flex !important; justify-content: space-between !important; @@ -404,6 +404,14 @@ } } +.cl_expandedview_infocontainer { + padding-left: 1rem; + padding-right: 1rem; + display: flex; + flex-direction: column; + gap: 0.25rem +} + .cl_expandedview_title { white-space: nowrap !important; text-overflow: ellipsis !important; From e8a349cf2444496300f73a712eb120f6d6450aab Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:04:26 -0600 Subject: [PATCH 21/53] fix preset dropdown --- package.json | 3 ++- src/backend/state/theme-store.ts | 2 ++ .../PresetSelectionDropdown.tsx | 6 ++++-- .../components/ExpandedViewButtonsSection.tsx | 1 + .../theme-store/context/ThemeBrowserStore.tsx | 5 ++++- src/styles/styles-as-string.ts | 14 +++++++++++++- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e2bcc14..c4d8ae0 100644 --- a/package.json +++ b/package.json @@ -62,5 +62,6 @@ "react-dom" ] } - } + }, + "packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1" } diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index cdb0b77..5007c3c 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -373,6 +373,7 @@ export const createCSSLoaderStore = (backend: Backend) => } catch (error) {} }, installTheme: async (themeId: string) => { + set({ isWorking: true }); try { await backend.downloadThemeFromUrl(themeId, apiUrl); const { updateStatuses, reloadThemes } = get(); @@ -381,6 +382,7 @@ export const createCSSLoaderStore = (backend: Backend) => updateStatusesClone.push([themeId, "installed", false]); set({ updateStatuses: updateStatusesClone }); } catch (error) {} + set({ isWorking: false }); }, toggleTheme: async ( theme: Theme, diff --git a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx index 3b2c585..23edb86 100644 --- a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx +++ b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx @@ -9,11 +9,13 @@ export function PresetSelectionDropdown() { const themes = useCSSLoaderValue("themes"); const selectedPreset = useCSSLoaderValue("selectedPreset"); const changePreset = useCSSLoaderAction("changePreset"); - const presets = useMemo(() => themes.filter((e) => e.flags.includes(Flags.isPreset)), [themes]); - const hasInvalidPresetState = presets.length > 1; + const presets = themes.filter((e) => e.flags.includes(Flags.isPreset)); + const hasInvalidPresetState = presets.filter((e) => e.enabled).length > 1; const [render, rerender] = useForcedRerender(); + console.log(themes, presets, selectedPreset, hasInvalidPresetState); + return ( <> {render && ( diff --git a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx index 27a347b..ff42658 100644 --- a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx @@ -12,6 +12,7 @@ export function ExpandedViewButtonsSection() { const toggleStar = useExpandedViewAction("toggleStar"); const apiFullToken = useCSSLoaderValue("apiFullToken"); + // Because this is an action handled by the expanded view store and not the backend theme store, we can't just use the backend's isWorking const [starButtonBlurred, setStarButtonBlurred] = useState(false); const isWorking = useCSSLoaderValue("isWorking"); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index a77dfec..85f48ae 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -103,7 +103,10 @@ export function ThemeBrowserStoreProvider({ getThemes(); } }, - refreshThemes: async () => {}, + refreshThemes: async () => { + const { getThemes } = get(); + await getThemes(); + }, getThemes: async () => { try { const { searchOpts } = get(); diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index bfa56dd..bcdb4d1 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -1,6 +1,10 @@ import { gamepadDialogClasses } from "@decky/ui"; export const styles = ` +/* THIS FILE IS NOT USED IN BUILD */ +/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO stylesAsString.ts */ +/* THAT IS NEEDED FOR STATIC CLASS INJECTIOn */ + .flex { display: flex !important; } @@ -343,7 +347,7 @@ export const styles = ` .cl_expandedview_container { background: rgb(27, 40, 56) !important; - padding: auto 1rem !important; + padding: 0 1rem !important; gap: 1rem !important; display: flex !important; justify-content: space-between !important; @@ -403,6 +407,14 @@ export const styles = ` } } +.cl_expandedview_infocontainer { + padding-left: 1rem; + padding-right: 1rem; + display: flex; + flex-direction: column; + gap: 0.25rem +} + .cl_expandedview_title { white-space: nowrap !important; text-overflow: ellipsis !important; From c32077a077bdfa55adf85c8c65ca93fd34e80c72 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:22:23 -0600 Subject: [PATCH 22/53] fix styling on qam tab page --- src/modules/qam-tab-page/pages/QamTabPage.tsx | 4 +++- src/styles/styles.css | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/modules/qam-tab-page/pages/QamTabPage.tsx b/src/modules/qam-tab-page/pages/QamTabPage.tsx index 19ce28f..4ff9838 100644 --- a/src/modules/qam-tab-page/pages/QamTabPage.tsx +++ b/src/modules/qam-tab-page/pages/QamTabPage.tsx @@ -21,7 +21,9 @@ export function QamTabPage() { - + + + ); } diff --git a/src/styles/styles.css b/src/styles/styles.css index a8cf083..3bd2cec 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -1,6 +1,7 @@ /* THIS FILE IS NOT USED IN BUILD */ -/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO stylesAsString.ts */ -/* THAT IS NEEDED FOR STATIC CLASS INJECTIOn */ +/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO styles-as-string.ts */ +/* THAT IS NEEDED FOR STATIC CLASS INJECTION */ +/* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */ .flex { display: flex !important; From fc872d4140c6149910ca48791a913a6a26d86f44 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:02:39 -0600 Subject: [PATCH 23/53] fix dummy function result padding --- src/decky-patches/decky-patch-store.ts | 1 + .../qam-tab-page/components/QamDummyFunctionBoundary.tsx | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/decky-patches/decky-patch-store.ts b/src/decky-patches/decky-patch-store.ts index ccb6690..fee5b98 100644 --- a/src/decky-patches/decky-patch-store.ts +++ b/src/decky-patches/decky-patch-store.ts @@ -25,6 +25,7 @@ const createDeckyPatchStore = (backend: Backend) => initializeStore: async () => { try { const shouldEnable = await backend.storeRead("enableNavPatch"); + return; if (shouldEnable) { const patch = enableNavPatch(); set({ navPatchInstance: patch }); diff --git a/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx b/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx index 0f8332e..9aff28a 100644 --- a/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx +++ b/src/modules/qam-tab-page/components/QamDummyFunctionBoundary.tsx @@ -1,5 +1,5 @@ import { useCSSLoaderValue } from "@/backend"; -import { PanelSectionRow } from "@decky/ui"; +import { PanelSection, PanelSectionRow } from "@decky/ui"; export function QamDummyFunctionBoundary({ children }: { children: React.ReactNode }) { const dummyFunctionResult = useCSSLoaderValue("dummyFunctionResult"); @@ -7,12 +7,12 @@ export function QamDummyFunctionBoundary({ children }: { children: React.ReactNo if (!dummyFunctionResult) { return ( <> - + CSS Loader failed to initialize, try reloading, and if that doesn't work, try restarting your deck. - + ); } From ba538cf4757d824c369c62ad17bbbedfa4a61236 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:36:42 -0600 Subject: [PATCH 24/53] add visual feedback to refresh button --- src/backend/services/backend-service.ts | 8 +++++- src/backend/state/theme-store.ts | 26 +++++++++++++++---- .../unminify-mode/dump-mappings.ts | 14 ---------- .../components/QamRefreshButton.tsx | 5 +++- 4 files changed, 32 insertions(+), 21 deletions(-) delete mode 100644 src/decky-patches/unminify-mode/dump-mappings.ts diff --git a/src/backend/services/backend-service.ts b/src/backend/services/backend-service.ts index 79cbb5f..003f551 100644 --- a/src/backend/services/backend-service.ts +++ b/src/backend/services/backend-service.ts @@ -22,7 +22,13 @@ export class Backend { await Backend.repository.call<[], void>("reset", []); } async dummyFunction(): Promise { - return await Backend.repository.call<[], boolean>("dummy_function", []); + // While most of the try catching should happen in the stores, this makes sense here + try { + const value = await Backend.repository.call<[], boolean>("dummy_function", []); + return value; + } catch (error) { + return false; + } } async storeRead(key: string) { return Backend.repository.call<[string], string>("store_read", [key]); diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 5007c3c..8289e22 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -171,7 +171,9 @@ export const createCSSLoaderStore = (backend: Backend) => const { bulkThemeUpdateCheck, scheduleBulkThemeUpdateCheck } = get(); await bulkThemeUpdateCheck(); scheduleBulkThemeUpdateCheck(); - } catch (error) {} + } catch (error) { + console.log("Error During Initialzation", error); + } }, deactivate: () => { const { updateCheckTimeout } = get(); @@ -181,17 +183,31 @@ export const createCSSLoaderStore = (backend: Backend) => backend.toast("CSS Loader", message); }, reloadPlugin: async () => { + set({ isWorking: true }); try { - const { reloadThemes, bulkThemeUpdateCheck } = get(); - await reloadThemes(); - await bulkThemeUpdateCheck(); + const { reloadThemes, initializeStore, bulkThemeUpdateCheck, dummyFunctionResult } = + get(); + + // If the dummy func result is false, the plugin never initialized properly anyway, so we should just re-initialize the whole thing. + if (dummyFunctionResult === false) { + await initializeStore(); + } else { + // Otherwise, we can just reload the necessary stuff + const dummyFunctionResult = await backend.dummyFunction(); + set({ dummyFunctionResult }); + await reloadThemes(); + await bulkThemeUpdateCheck(); + } } catch (error) {} + set({ isWorking: false }); }, reloadThemes: async () => { try { await backend.reset(); await get().getThemes(); - } catch (error) {} + } catch (error) { + console.error("Error Reloading Themes", error); + } }, refreshToken: async (): Promise => { const { apiFullToken, apiTokenExpireDate } = get(); diff --git a/src/decky-patches/unminify-mode/dump-mappings.ts b/src/decky-patches/unminify-mode/dump-mappings.ts deleted file mode 100644 index 8a76b63..0000000 --- a/src/decky-patches/unminify-mode/dump-mappings.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { saveMappings } from "../python"; - -export async function dumpMappings() { - try { - if (!window.DFL) return; - const map = window.DFL.classModuleMap as Map; - if (!map) return; - const jsonStr = JSON.stringify(Object.fromEntries(map)); - - await saveMappings(jsonStr); - } catch (error) { - console.error("ERROR SAVING MAPPINGS", error); - } -} diff --git a/src/modules/qam-tab-page/components/QamRefreshButton.tsx b/src/modules/qam-tab-page/components/QamRefreshButton.tsx index 1abbe62..6ce94f2 100644 --- a/src/modules/qam-tab-page/components/QamRefreshButton.tsx +++ b/src/modules/qam-tab-page/components/QamRefreshButton.tsx @@ -1,12 +1,15 @@ import { ButtonItem, PanelSectionRow } from "@decky/ui"; -import { useCSSLoaderAction } from "@/backend"; +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; export function QamRefreshButton() { const reloadPlugin = useCSSLoaderAction("reloadPlugin"); + const isWorking = useCSSLoaderValue("isWorking"); return ( { + console.log("TEST"); void reloadPlugin(); }} layout="below" From 8d4c62cd328002ed0c02bc524ae927b44dec3bc4 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:40:38 -0600 Subject: [PATCH 25/53] focus refresh button after refresh --- .../components/QamRefreshButton.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/modules/qam-tab-page/components/QamRefreshButton.tsx b/src/modules/qam-tab-page/components/QamRefreshButton.tsx index 6ce94f2..7153616 100644 --- a/src/modules/qam-tab-page/components/QamRefreshButton.tsx +++ b/src/modules/qam-tab-page/components/QamRefreshButton.tsx @@ -1,16 +1,31 @@ import { ButtonItem, PanelSectionRow } from "@decky/ui"; import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { useEffect, useRef } from "react"; export function QamRefreshButton() { const reloadPlugin = useCSSLoaderAction("reloadPlugin"); const isWorking = useCSSLoaderValue("isWorking"); + + const refreshButtonRef = useRef(null); + + useEffect(() => { + console.log(refreshButtonRef.current); + }); + + async function handleRefresh() { + await reloadPlugin(); + // This just ensures focus isn't lost + refreshButtonRef.current?.focus(); + } + return ( { - console.log("TEST"); - void reloadPlugin(); + void handleRefresh(); }} layout="below" > From 58b49efbcac56592a5d5b8f238408fa1c6e4d36e Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:44:54 -0600 Subject: [PATCH 26/53] begin work on auto-style-commit script --- commit-styles-macos.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 commit-styles-macos.sh diff --git a/commit-styles-macos.sh b/commit-styles-macos.sh new file mode 100755 index 0000000..67ef9b1 --- /dev/null +++ b/commit-styles-macos.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# TODO: NOT FINISHED + +# This uses macos' sed, which is different from the one in linux + +css_file_path="$(dirname "$0")/src/styles/styles.css" +css_as_string_path="$(dirname "$0")/src/styles/styles-as-string.ts" + +# Read the content of the CSS file, ignoring lines that start with '/*' +css_content=$(sed '/^\s*\/\*/d' "$css_file_path") + +export_header_line_number=$(grep -n "export const styles" $css_as_string_path | cut -d: -f1) +echo export_header_line_number: $export_header_line_number \ No newline at end of file From b909ed14fd3e831e7961d9f4a1df358ca385065e Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:47:30 -0600 Subject: [PATCH 27/53] fix selected preset and style fixes --- .../decky-backend-repository-impl.ts | 2 ++ src/backend/state/theme-store.ts | 8 +++++++- .../components/ExpandedViewButtonsSection.tsx | 11 +++++++---- .../components/ExpandedViewScrollingSection.tsx | 2 +- .../expanded-view/pages/ExpandedViewPage.tsx | 7 ++++--- .../context/ThemeBrowserSharedStore.tsx | 2 ++ .../theme-store/context/ThemeBrowserStore.tsx | 4 ++++ src/styles/styles-as-string.ts | 17 +++++++++++++---- src/styles/styles.css | 13 +++++++++++++ 9 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/backend-impl/decky-backend-repository-impl.ts b/src/backend-impl/decky-backend-repository-impl.ts index 1d6f369..4735fb0 100644 --- a/src/backend-impl/decky-backend-repository-impl.ts +++ b/src/backend-impl/decky-backend-repository-impl.ts @@ -17,6 +17,8 @@ class DeckyBackendRepository implements IBackendRepository { async fetch(url: string, request: RequestInit) { try { console.debug("CSSLOADER FETCH", url, request); + // TODO: Think this is a decky types issue + // @ts-ignore const res = await fetchNoCors(url, request); if (!res.ok) { throw new Error(`Res Not Okay - Code ${res.status}`); diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 8289e22..427f9e9 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -145,7 +145,11 @@ export const createCSSLoaderStore = (backend: Backend) => set({ backendVersion }); const themes = (await backend.getThemes()) ?? []; - set({ themes, selectedPreset: themes.find((e) => e.flags.includes(Flags.isPreset)) }); + console.log("THEMES", themes.length, themes.filter((e) => e.enabled).length); + set({ + themes, + selectedPreset: themes.find((e) => e.flags.includes(Flags.isPreset) && e.enabled), + }); const themePath = await backend.fetchThemePath(); set({ themeRootPath: themePath }); @@ -284,7 +288,9 @@ export const createCSSLoaderStore = (backend: Backend) => async function fetchThemeIDS(idsToQuery: string[]): Promise { const queryStr = "?ids=" + idsToQuery.join("."); try { + console.log("FETCHTHEMEIDS STRING", themes.length); const value = await apiFetch(`/themes/ids${queryStr}`); + console.log("VALUE", value); if (value) return value; } catch {} return []; diff --git a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx index ff42658..600efeb 100644 --- a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx @@ -1,7 +1,7 @@ import { shortenNumber, useThemeInstallState } from "@/lib"; import { useExpandedViewAction, useExpandedViewValue } from "../context"; import { FaRegStar, FaStar } from "react-icons/fa"; -import { DialogButton } from "@decky/ui"; +import { DialogButton, Focusable } from "@decky/ui"; import { useEffect, useRef, useState } from "react"; import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; import { ImCog } from "react-icons/im"; @@ -36,8 +36,10 @@ export function ExpandedViewButtonsSection() { } }, [downloadButtonRef, hasBeenFocused]); + console.log("INSTALL STATUS, ", installStatus); + return ( -
+ {/* Star */}
@@ -81,7 +83,8 @@ export function ExpandedViewButtonsSection() { }} > - {installStatus === "installed" && "Reinstall"} + {/* Technically 'local' should mean a remote copy doesn't exist, but there might be weird network race conditions on the install status check */} + {(installStatus === "installed" || installStatus === "local") && "Reinstall"} {installStatus === "outdated" && "Update"} {installStatus === "notinstalled" && "Install"} @@ -107,6 +110,6 @@ export function ExpandedViewButtonsSection() { )}
-
+ ); } diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx index cc32b50..cc6090a 100644 --- a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -61,7 +61,7 @@ export function ExpandedViewScrollingSection() { {/* Targets */} Targets - + {data.targets.map((target) => ( -
- + + -
+
); } diff --git a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx index b7a4978..754d531 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -27,6 +27,8 @@ const themeBrowserSharedStore = createStore((set) => { }; }); +export const getThemeBrowserSharedState = () => themeBrowserSharedStore.getState(); + const useThemeBrowserSharedState = (fn: (state: IThemeBrowserSharedStore) => any) => useStore(themeBrowserSharedStore, fn); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 85f48ae..87135cf 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -3,6 +3,7 @@ import { FilterQueryResponse, ThemeQueryRequest, ThemeQueryResponse } from "@/ty import { StoreApi, createStore, useStore } from "zustand"; import { getCSSLoaderState } from "@/backend"; import { isEqual } from "lodash"; +import { getThemeBrowserSharedState } from "./ThemeBrowserSharedStore"; interface ThemeBrowserStoreValues { themes: ThemeQueryResponse; @@ -110,6 +111,9 @@ export function ThemeBrowserStoreProvider({ getThemes: async () => { try { const { searchOpts } = get(); + const { targetOverride } = getThemeBrowserSharedState(); + const formattedSearchOpts = { ...searchOpts }; + targetOverride && (formattedSearchOpts.filters = targetOverride); const { apiFetch } = getCSSLoaderState(); const response = await apiFetch( diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index bcdb4d1..09c0e58 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -1,10 +1,6 @@ import { gamepadDialogClasses } from "@decky/ui"; export const styles = ` -/* THIS FILE IS NOT USED IN BUILD */ -/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO stylesAsString.ts */ -/* THAT IS NEEDED FOR STATIC CLASS INJECTIOn */ - .flex { display: flex !important; } @@ -356,6 +352,8 @@ export const styles = ` .cl_expandedview_scrollpanel { display: flex !important; margin-bottom: 40px !important; + /* To be completely honest, I'm not sure why this isn't inherited from fullscrenroute_container */ + height: calc(100vh - 40px) !important } .cl_expandedview_themedatacontainer { @@ -436,6 +434,12 @@ export const styles = ` color: rgb(26, 159, 255) !important; } +.cl_expandedview_targetbuttonscontainer { + display: flex !important; + gap: 0.25rem !important; + padding-bottom: 80px !important; +} + .cl_expandedview_targetbutton { background: rgba(59, 90, 114, 0.5) !important; color: rgb(26, 159, 255) !important; @@ -443,6 +447,11 @@ export const styles = ` width: fit-content !important; } +.cl_expandedview_targetbutton.gpfocuswithin { + background: white !important; + color: black !important; +} + .cl_expandedview_buttonscontainer { position: sticky !important; padding-top: 1em !important; diff --git a/src/styles/styles.css b/src/styles/styles.css index 3bd2cec..ab807c8 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -354,6 +354,8 @@ .cl_expandedview_scrollpanel { display: flex !important; margin-bottom: 40px !important; + /* To be completely honest, I'm not sure why this isn't inherited from fullscrenroute_container */ + height: calc(100vh - 40px) !important } .cl_expandedview_themedatacontainer { @@ -434,6 +436,12 @@ color: rgb(26, 159, 255) !important; } +.cl_expandedview_targetbuttonscontainer { + display: flex !important; + gap: 0.25rem !important; + padding-bottom: 80px !important; +} + .cl_expandedview_targetbutton { background: rgba(59, 90, 114, 0.5) !important; color: rgb(26, 159, 255) !important; @@ -441,6 +449,11 @@ width: fit-content !important; } +.cl_expandedview_targetbutton.gpfocuswithin { + background: white !important; + color: black !important; +} + .cl_expandedview_buttonscontainer { position: sticky !important; padding-top: 1em !important; From 102e5de65fb40f76c9a0091ee9874ccbf85a1eea Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sat, 12 Oct 2024 17:31:00 -0600 Subject: [PATCH 28/53] final fixes to expanded view --- src/backend/state/theme-store.ts | 3 - .../components/ExpandedViewButtonsSection.tsx | 62 ++++++++++--------- .../components/ExpandedViewImageContainer.tsx | 2 +- src/styles/styles-as-string.ts | 53 ++++++++-------- src/styles/styles.css | 53 ++++++++-------- 5 files changed, 89 insertions(+), 84 deletions(-) diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 427f9e9..7143ceb 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -145,7 +145,6 @@ export const createCSSLoaderStore = (backend: Backend) => set({ backendVersion }); const themes = (await backend.getThemes()) ?? []; - console.log("THEMES", themes.length, themes.filter((e) => e.enabled).length); set({ themes, selectedPreset: themes.find((e) => e.flags.includes(Flags.isPreset) && e.enabled), @@ -288,9 +287,7 @@ export const createCSSLoaderStore = (backend: Backend) => async function fetchThemeIDS(idsToQuery: string[]): Promise { const queryStr = "?ids=" + idsToQuery.join("."); try { - console.log("FETCHTHEMEIDS STRING", themes.length); const value = await apiFetch(`/themes/ids${queryStr}`); - console.log("VALUE", value); if (value) return value; } catch {} return []; diff --git a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx index 600efeb..ab6f48f 100644 --- a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx @@ -73,41 +73,43 @@ export function ExpandedViewButtonsSection() { {shortenNumber(data.download.downloadCount) ?? data.download.downloadCount} Download {data.download.downloadCount === 1 ? "" : "s"} - { - installTheme(data.id); - }} - > - - {/* Technically 'local' should mean a remote copy doesn't exist, but there might be weird network race conditions on the install status check */} - {(installStatus === "installed" || installStatus === "local") && "Reinstall"} - {installStatus === "outdated" && "Update"} - {installStatus === "notinstalled" && "Install"} - - - {installStatus === "installed" && ( + { - // TODO: THEME SETTINGS MODAL - // showModal( - // e.id === fullThemeData.id)?.id || - // // using name here because in submissions id is different - // installedThemes.find((e) => e.name === fullThemeData.name)!.id - // } - // /> - // ); + installTheme(data.id); }} - className="relative" > - + + {/* Technically 'local' should mean a remote copy doesn't exist, but there might be weird network race conditions on the install status check */} + {(installStatus === "installed" || installStatus === "local") && "Reinstall"} + {installStatus === "outdated" && "Update"} + {installStatus === "notinstalled" && "Install"} + - )} + {(installStatus === "installed" || installStatus === "local") && ( + { + // TODO: THEME SETTINGS MODAL + // showModal( + // e.id === fullThemeData.id)?.id || + // // using name here because in submissions id is different + // installedThemes.find((e) => e.name === fullThemeData.name)!.id + // } + // /> + // ); + }} + className="cl_expandedview_configure_button" + > + + + )} +
diff --git a/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx b/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx index 72d7997..590a276 100644 --- a/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx +++ b/src/modules/expanded-view/components/ExpandedViewImageContainer.tsx @@ -35,7 +35,7 @@ export function ExpandedViewImageContainer() { width={imageCarouselEntryWidth} height={imageCarouselEntryHeight} style={{ objectFit: "contain" }} - src={`https://api.deckthemes.com/blobs/${focusedImageId}`} + src={`https://api.deckthemes.com/blobs/${image.id}`} /> ))} diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 09c0e58..d201bb9 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -78,10 +78,10 @@ export const styles = ` } .absolute-center { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: absolute !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; } /* Fullscreen Routes */ @@ -352,8 +352,7 @@ export const styles = ` .cl_expandedview_scrollpanel { display: flex !important; margin-bottom: 40px !important; - /* To be completely honest, I'm not sure why this isn't inherited from fullscrenroute_container */ - height: calc(100vh - 40px) !important + height: calc(100vh - 80px) !important } .cl_expandedview_themedatacontainer { @@ -392,25 +391,24 @@ export const styles = ` } .cl_expandedview_imagenumbercontainer { - .image-number-container { - width: 3em !important; - height: 2em !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - background: #000a !important; - position: absolute !important; - bottom: 1em !important; - right: 1em !important; - } + width: 3em !important; + height: 2em !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + background: #000a !important; + position: absolute !important; + bottom: 1em !important; + right: 1em !important; } .cl_expandedview_infocontainer { - padding-left: 1rem; - padding-right: 1rem; - display: flex; - flex-direction: column; - gap: 0.25rem + padding-left: 1rem !important; + padding-right: 1rem !important; + padding-bottom: 1rem !important; + display: flex !important; + flex-direction: column !important; + gap: 0.25rem !important; } .cl_expandedview_title { @@ -437,7 +435,6 @@ export const styles = ` .cl_expandedview_targetbuttonscontainer { display: flex !important; gap: 0.25rem !important; - padding-bottom: 80px !important; } .cl_expandedview_targetbutton { @@ -454,7 +451,7 @@ export const styles = ` .cl_expandedview_buttonscontainer { position: sticky !important; - padding-top: 1em !important; + padding-top: 1rem !important; flex: 1 !important; display: flex !important; flex-direction: column !important; @@ -463,7 +460,7 @@ export const styles = ` .cl_expandedview_singlebuttoncontainer { background: #2a4153 !important; - padding: 1em !important; + padding: 1rem !important; } .cl_expandedview_starbutton { @@ -485,4 +482,10 @@ export const styles = ` .cl_expandedview_bluebutton.gpfocuswithin { background: white !important; } + +.cl_expandedview_configure_button { + width: 1rem !important; + min-width: 1rem !important; + position: relative; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index ab807c8..d179ee2 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -80,10 +80,10 @@ } .absolute-center { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: absolute !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; } /* Fullscreen Routes */ @@ -354,8 +354,7 @@ .cl_expandedview_scrollpanel { display: flex !important; margin-bottom: 40px !important; - /* To be completely honest, I'm not sure why this isn't inherited from fullscrenroute_container */ - height: calc(100vh - 40px) !important + height: calc(100vh - 80px) !important } .cl_expandedview_themedatacontainer { @@ -394,25 +393,24 @@ } .cl_expandedview_imagenumbercontainer { - .image-number-container { - width: 3em !important; - height: 2em !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - background: #000a !important; - position: absolute !important; - bottom: 1em !important; - right: 1em !important; - } + width: 3em !important; + height: 2em !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + background: #000a !important; + position: absolute !important; + bottom: 1em !important; + right: 1em !important; } .cl_expandedview_infocontainer { - padding-left: 1rem; - padding-right: 1rem; - display: flex; - flex-direction: column; - gap: 0.25rem + padding-left: 1rem !important; + padding-right: 1rem !important; + padding-bottom: 1rem !important; + display: flex !important; + flex-direction: column !important; + gap: 0.25rem !important; } .cl_expandedview_title { @@ -439,7 +437,6 @@ .cl_expandedview_targetbuttonscontainer { display: flex !important; gap: 0.25rem !important; - padding-bottom: 80px !important; } .cl_expandedview_targetbutton { @@ -456,7 +453,7 @@ .cl_expandedview_buttonscontainer { position: sticky !important; - padding-top: 1em !important; + padding-top: 1rem !important; flex: 1 !important; display: flex !important; flex-direction: column !important; @@ -465,7 +462,7 @@ .cl_expandedview_singlebuttoncontainer { background: #2a4153 !important; - padding: 1em !important; + padding: 1rem !important; } .cl_expandedview_starbutton { @@ -486,4 +483,10 @@ } .cl_expandedview_bluebutton.gpfocuswithin { background: white !important; +} + +.cl_expandedview_configure_button { + width: 1rem !important; + min-width: 1rem !important; + position: relative; } \ No newline at end of file From b182d8167f70fc656ac0bd24c204b965d6426b0b Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:27:22 -0700 Subject: [PATCH 29/53] clean up unused dependencies and port rollup config --- package.json | 14 +- pnpm-lock.yaml | 1240 ++++++---------------------------------------- rollup.config.js | 66 +-- 3 files changed, 177 insertions(+), 1143 deletions(-) diff --git a/package.json b/package.json index c4d8ae0..d616242 100644 --- a/package.json +++ b/package.json @@ -27,26 +27,18 @@ "homepage": "https://github.com/SteamDeckHomebrew/decky-plugin-template#readme", "devDependencies": { "@rollup/plugin-alias": "^5.1.0", - "@rollup/plugin-commonjs": "^26.0.1", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.7", - "@rollup/plugin-typescript": "^11.1.6", "@types/color": "^3.0.3", "@types/lodash": "^4.14.191", "@types/react": "16.14.0", "@types/webpack": "^5.28.0", - "rollup": "^4.18.0", - "rollup-plugin-delete": "^2.0.0", - "rollup-plugin-external-globals": "^0.10.0", - "rollup-plugin-import-assets": "^1.1.1", - "rollup-plugin-styles": "^4.0.0", + "rollup": "^4.24.4", "shx": "^0.3.4", "tslib": "^2.4.0", - "typescript": "^4.6.4" + "typescript": "^5.6.3" }, "dependencies": { "@decky/api": "^1.0.6", + "@decky/rollup": "^1.0.1", "@decky/ui": "^4.4.0", "clsx": "^2.1.1", "color": "^4.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd146cb..1214686 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,4 @@ lockfileVersion: '9.0' -lockfileVersion: '9.0' settings: autoInstallPeers: true @@ -12,6 +11,9 @@ importers: '@decky/api': specifier: ^1.0.6 version: 1.0.6 + '@decky/rollup': + specifier: ^1.0.1 + version: 1.0.1 '@decky/ui': specifier: ^4.4.0 version: 4.4.0 @@ -36,22 +38,7 @@ importers: devDependencies: '@rollup/plugin-alias': specifier: ^5.1.0 - version: 5.1.0(rollup@4.18.0) - '@rollup/plugin-commonjs': - specifier: ^26.0.1 - version: 26.0.1(rollup@4.18.0) - '@rollup/plugin-json': - specifier: ^6.1.0 - version: 6.1.0(rollup@4.18.0) - '@rollup/plugin-node-resolve': - specifier: ^15.2.3 - version: 15.2.3(rollup@4.18.0) - '@rollup/plugin-replace': - specifier: ^5.0.7 - version: 5.0.7(rollup@4.18.0) - '@rollup/plugin-typescript': - specifier: ^11.1.6 - version: 11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@4.9.5) + version: 5.1.0(rollup@4.24.4) '@types/color': specifier: ^3.0.3 version: 3.0.6 @@ -65,20 +52,8 @@ importers: specifier: ^5.28.0 version: 5.28.5 rollup: - specifier: ^4.18.0 - version: 4.18.0 - rollup-plugin-delete: - specifier: ^2.0.0 - version: 2.0.0 - rollup-plugin-external-globals: - specifier: ^0.10.0 - version: 0.10.0(rollup@4.18.0) - rollup-plugin-import-assets: - specifier: ^1.1.1 - version: 1.1.1(rollup@4.18.0) - rollup-plugin-styles: - specifier: ^4.0.0 - version: 4.0.0(rollup@4.18.0) + specifier: ^4.24.4 + version: 4.24.4 shx: specifier: ^0.3.4 version: 0.3.4 @@ -86,23 +61,11 @@ importers: specifier: ^2.4.0 version: 2.6.2 typescript: - specifier: ^4.6.4 - version: 4.9.5 + specifier: ^5.6.3 + version: 5.6.3 packages: - '@babel/code-frame@7.24.2': - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.24.5': - resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.5': - resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.24.5': resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} @@ -110,6 +73,9 @@ packages: '@decky/api@1.0.6': resolution: {integrity: sha512-pacO2qvAin7ZoB9AnCgQbevQS+6Wiy0t1C6QVEJPCWeQQdEgJaUm3KSeRjh2KNqsjKHsbV0j72Pv4X5Q44Lr7Q==} + '@decky/rollup@1.0.1': + resolution: {integrity: sha512-dx1VJwD7ul14PA/aZvOwAfY/GujHzqZJ+MFb4OIUVi63/z4KWMSuZrK6QWo0S4LrNW3RzB3ua6LT0WcJaNY9gw==} + '@decky/ui@4.4.0': resolution: {integrity: sha512-w6hSoEdWQyXdvSk8cv4cGDJQ6/xoRx9LYrdNjTGPPj8hRmBcoRlThgZ+9vchmNsAJuOymwQ5hQXqHN6Y86eoOQ==} @@ -212,10 +178,6 @@ packages: tslib: optional: true - '@rollup/pluginutils@4.2.1': - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - '@rollup/pluginutils@5.1.0': resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} @@ -225,90 +187,96 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + '@rollup/rollup-android-arm-eabi@4.24.4': + resolution: {integrity: sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + '@rollup/rollup-android-arm64@4.24.4': + resolution: {integrity: sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-darwin-arm64@4.24.4': + resolution: {integrity: sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + '@rollup/rollup-darwin-x64@4.24.4': + resolution: {integrity: sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + '@rollup/rollup-freebsd-arm64@4.24.4': + resolution: {integrity: sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.24.4': + resolution: {integrity: sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.24.4': + resolution: {integrity: sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-musleabihf@4.24.4': + resolution: {integrity: sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + '@rollup/rollup-linux-arm64-gnu@4.24.4': + resolution: {integrity: sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-musl@4.24.4': + resolution: {integrity: sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': + resolution: {integrity: sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + '@rollup/rollup-linux-riscv64-gnu@4.24.4': + resolution: {integrity: sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + '@rollup/rollup-linux-s390x-gnu@4.24.4': + resolution: {integrity: sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + '@rollup/rollup-linux-x64-gnu@4.24.4': + resolution: {integrity: sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-musl@4.24.4': + resolution: {integrity: sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + '@rollup/rollup-win32-arm64-msvc@4.24.4': + resolution: {integrity: sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + '@rollup/rollup-win32-ia32-msvc@4.24.4': + resolution: {integrity: sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + '@rollup/rollup-win32-x64-msvc@4.24.4': + resolution: {integrity: sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==} cpu: [x64] os: [win32] - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - '@types/color-convert@2.0.3': resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==} @@ -318,10 +286,6 @@ packages: '@types/color@3.0.6': resolution: {integrity: sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==} - '@types/cssnano@5.1.0': - resolution: {integrity: sha512-ikR+18UpFGgvaWSur4og6SJYF/6QEYHXvrIt36dp81p1MG3cAPTYDMBJGeyWa3LCnqEbgNMHKRb+FP0NrXtoWQ==} - deprecated: This is a stub types definition. cssnano provides its own type definitions, so you do not need this installed. - '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -331,6 +295,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -346,9 +313,6 @@ packages: '@types/node@20.12.12': resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -442,10 +406,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -461,9 +421,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -486,20 +443,9 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001620: resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} @@ -512,16 +458,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -532,81 +472,22 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - css-declaration-sorter@6.4.1: - resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 - - css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - - css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} - - css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@5.2.14: - resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano-utils@3.1.0: - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano@5.1.15: - resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - csso@4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -619,19 +500,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -648,12 +516,6 @@ packages: resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} engines: {node: '>=10.13.0'} - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-module-lexer@1.5.3: resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} @@ -661,10 +523,6 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -690,9 +548,6 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -714,18 +569,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - filter-obj@1.1.0: - resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} - engines: {node: '>=0.10.0'} - foreground-child@3.2.1: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -759,10 +606,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -771,20 +614,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -800,9 +633,6 @@ packages: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} @@ -846,6 +676,10 @@ packages: is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-what@5.0.2: + resolution: {integrity: sha512-vI7Ui0qzNQ2ClDZd0bC7uqRk3T1imbX5cZODmVlqqdqiwmSIUX3CNSiRgFjFMJ987sVCMSa7xZeEDtpJduPg4A==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -866,26 +700,10 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -900,8 +718,9 @@ packages: magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + merge-anything@6.0.2: + resolution: {integrity: sha512-U8x6DL/YVudOcf82B6hd8GFg+6gF6hEHYwzqdo67GrH6vnDZ5YBq6BYX3hHWyCnG3CcqJDB1a9tj9fzMI3RL9Q==} + engines: {node: '>=18'} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -936,51 +755,19 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - p-map@3.0.0: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1007,210 +794,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - postcss-calc@8.2.4: - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 - - postcss-colormin@5.3.1: - resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-convert-values@5.1.3: - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-comments@5.1.2: - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-duplicates@5.1.0: - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-empty@5.1.1: - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-overridden@5.1.0: - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-merge-longhand@5.1.7: - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-merge-rules@5.1.4: - resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-font-values@5.1.0: - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-gradients@5.1.1: - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-params@5.1.4: - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-selectors@5.2.1: - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-local-by-default@4.0.5: - resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-scope@3.2.0: - resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-values@4.0.0: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-normalize-charset@5.1.0: - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-display-values@5.1.0: - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-positions@5.1.1: - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-repeat-style@5.1.1: - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-string@5.1.0: - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-timing-functions@5.1.0: - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-unicode@5.1.1: - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-url@5.1.0: - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-whitespace@5.1.1: - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-ordered-values@5.1.3: - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-initial@5.1.2: - resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-transforms@5.1.0: - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-selector-parser@6.0.16: - resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} - engines: {node: '>=4'} - - postcss-svgo@5.1.0: - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-unique-selectors@5.1.1: - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - query-string@7.1.3: - resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} - engines: {node: '>=6'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1233,10 +820,6 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -1254,8 +837,8 @@ packages: resolution: {integrity: sha512-/VpLMtDy+8wwRlDANuYmDa9ss/knGsAgrDhM+tEwB1npHwNu4DYNmDfUL55csse/GHs9Q+SMT/rw9uiaZ3pnzA==} engines: {node: '>=10'} - rollup-plugin-external-globals@0.10.0: - resolution: {integrity: sha512-RXlupZrmn97AaaS5dWnktkjM+Iy+od0E+8L0mUkMIs3iuoUXNJebueQocQKV7Ircd54fSGGmkBaXwNzY05J1yQ==} + rollup-plugin-external-globals@0.11.0: + resolution: {integrity: sha512-LR+sH2WkgWMPxsA5o5rT7uW7BeWXSeygLe60QQi9qoN/ufaCuHDaVOIbndIkqDPnZt/wZugJh5DCzkZFdSWlLQ==} peerDependencies: rollup: ^2.25.0 || ^3.3.0 || ^4.1.4 @@ -1264,17 +847,11 @@ packages: peerDependencies: rollup: '>=1.9.0' - rollup-plugin-styles@4.0.0: - resolution: {integrity: sha512-A2K2sao84OsTmDxXG83JTCdXWrmgvQkkI38XDat46rdtpGMRm9tSYqeCdlwwGDJF4kKIafhV1mUidqu8MxUGig==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - rollup: ^2.63.0 - rollup-pluginutils@2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.24.4: + resolution: {integrity: sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1324,10 +901,6 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1335,18 +908,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} - engines: {node: '>=6'} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - - strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1363,16 +924,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - stylehacks@5.1.1: - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} @@ -1381,11 +932,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svgo@2.8.0: - resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} - engines: {node: '>=10.13.0'} - hasBin: true - tailwind-merge@2.3.0: resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} @@ -1421,18 +967,17 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} hasBin: true undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - update-browserslist-db@1.0.16: resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} hasBin: true @@ -1450,9 +995,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - watchpack@2.4.1: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} @@ -1487,10 +1029,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - zustand@4.5.2: resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} engines: {node: '>=12.7.0'} @@ -1508,26 +1046,27 @@ packages: snapshots: - '@babel/code-frame@7.24.2': - dependencies: - '@babel/highlight': 7.24.5 - picocolors: 1.0.1 - - '@babel/helper-validator-identifier@7.24.5': {} - - '@babel/highlight@7.24.5': - dependencies: - '@babel/helper-validator-identifier': 7.24.5 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 - '@babel/runtime@7.24.5': dependencies: regenerator-runtime: 0.14.1 '@decky/api@1.0.6': {} + '@decky/rollup@1.0.1': + dependencies: + '@rollup/plugin-commonjs': 26.0.1(rollup@4.24.4) + '@rollup/plugin-json': 6.1.0(rollup@4.24.4) + '@rollup/plugin-node-resolve': 15.2.3(rollup@4.24.4) + '@rollup/plugin-replace': 5.0.7(rollup@4.24.4) + '@rollup/plugin-typescript': 11.1.6(rollup@4.24.4)(tslib@2.8.1)(typescript@5.6.3) + merge-anything: 6.0.2 + rollup: 4.24.4 + rollup-plugin-delete: 2.0.0 + rollup-plugin-external-globals: 0.11.0(rollup@4.24.4) + rollup-plugin-import-assets: 1.1.1(rollup@4.24.4) + tslib: 2.8.1 + typescript: 5.6.3 + '@decky/ui@4.4.0': {} '@isaacs/cliui@8.0.2': @@ -1576,118 +1115,117 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/plugin-alias@5.1.0(rollup@4.18.0)': + '@rollup/plugin-alias@5.1.0(rollup@4.24.4)': dependencies: slash: 4.0.0 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.4 - '@rollup/plugin-commonjs@26.0.1(rollup@4.18.0)': + '@rollup/plugin-commonjs@26.0.1(rollup@4.24.4)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) commondir: 1.0.1 estree-walker: 2.0.2 glob: 10.4.1 is-reference: 1.2.1 magic-string: 0.30.10 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.4 - '@rollup/plugin-json@6.1.0(rollup@4.18.0)': + '@rollup/plugin-json@6.1.0(rollup@4.24.4)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.4 - '@rollup/plugin-node-resolve@15.2.3(rollup@4.18.0)': + '@rollup/plugin-node-resolve@15.2.3(rollup@4.24.4)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.8 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.4 - '@rollup/plugin-replace@5.0.7(rollup@4.18.0)': + '@rollup/plugin-replace@5.0.7(rollup@4.24.4)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) magic-string: 0.30.10 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.4 - '@rollup/plugin-typescript@11.1.6(rollup@4.18.0)(tslib@2.6.2)(typescript@4.9.5)': + '@rollup/plugin-typescript@11.1.6(rollup@4.24.4)(tslib@2.8.1)(typescript@5.6.3)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) resolve: 1.22.8 - typescript: 4.9.5 + typescript: 5.6.3 optionalDependencies: - rollup: 4.18.0 - tslib: 2.6.2 - - '@rollup/pluginutils@4.2.1': - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 + rollup: 4.24.4 + tslib: 2.8.1 - '@rollup/pluginutils@5.1.0(rollup@4.18.0)': + '@rollup/pluginutils@5.1.0(rollup@4.24.4)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.4 - '@rollup/rollup-android-arm-eabi@4.18.0': + '@rollup/rollup-android-arm-eabi@4.24.4': optional: true - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-android-arm64@4.24.4': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-darwin-arm64@4.24.4': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-darwin-x64@4.24.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-freebsd-arm64@4.24.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-freebsd-x64@4.24.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-linux-arm-gnueabihf@4.24.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rollup/rollup-linux-arm-musleabihf@4.24.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rollup/rollup-linux-arm64-gnu@4.24.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rollup/rollup-linux-arm64-musl@4.24.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.24.4': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.24.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.24.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rollup/rollup-linux-x64-musl@4.24.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rollup/rollup-win32-arm64-msvc@4.24.4': optional: true - '@trysound/sax@0.2.0': {} + '@rollup/rollup-win32-ia32-msvc@4.24.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.24.4': + optional: true '@types/color-convert@2.0.3': dependencies: @@ -1699,12 +1237,6 @@ snapshots: dependencies: '@types/color-convert': 2.0.3 - '@types/cssnano@5.1.0(postcss@8.4.38)': - dependencies: - cssnano: 5.1.15(postcss@8.4.38) - transitivePeerDependencies: - - postcss - '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 8.56.10 @@ -1717,6 +1249,8 @@ snapshots: '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 @@ -1732,8 +1266,6 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/parse-json@4.0.2': {} - '@types/prop-types@15.7.12': {} '@types/react@16.14.0': @@ -1860,10 +1392,6 @@ snapshots: ansi-regex@6.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -1874,8 +1402,6 @@ snapshots: balanced-match@1.0.2: {} - boolbase@1.0.0: {} - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -1900,39 +1426,18 @@ snapshots: builtin-modules@3.3.0: {} - callsites@3.1.0: {} - - caniuse-api@3.0.0: - dependencies: - browserslist: 4.23.0 - caniuse-lite: 1.0.30001620 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001620: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chrome-trace-event@1.0.3: {} clean-stack@2.2.0: {} clsx@2.1.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} color-string@1.9.1: @@ -1945,103 +1450,20 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 - colord@2.9.3: {} - commander@2.20.3: {} - commander@7.2.0: {} - commondir@1.0.1: {} concat-map@0.0.1: {} - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - css-declaration-sorter@6.4.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - css-select@4.3.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - - css-tree@1.1.3: - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - - css-what@6.1.0: {} - - cssesc@3.0.0: {} - - cssnano-preset-default@5.2.14(postcss@8.4.38): - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.4.38) - cssnano-utils: 3.1.0(postcss@8.4.38) - postcss: 8.4.38 - postcss-calc: 8.2.4(postcss@8.4.38) - postcss-colormin: 5.3.1(postcss@8.4.38) - postcss-convert-values: 5.1.3(postcss@8.4.38) - postcss-discard-comments: 5.1.2(postcss@8.4.38) - postcss-discard-duplicates: 5.1.0(postcss@8.4.38) - postcss-discard-empty: 5.1.1(postcss@8.4.38) - postcss-discard-overridden: 5.1.0(postcss@8.4.38) - postcss-merge-longhand: 5.1.7(postcss@8.4.38) - postcss-merge-rules: 5.1.4(postcss@8.4.38) - postcss-minify-font-values: 5.1.0(postcss@8.4.38) - postcss-minify-gradients: 5.1.1(postcss@8.4.38) - postcss-minify-params: 5.1.4(postcss@8.4.38) - postcss-minify-selectors: 5.2.1(postcss@8.4.38) - postcss-normalize-charset: 5.1.0(postcss@8.4.38) - postcss-normalize-display-values: 5.1.0(postcss@8.4.38) - postcss-normalize-positions: 5.1.1(postcss@8.4.38) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.38) - postcss-normalize-string: 5.1.0(postcss@8.4.38) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.38) - postcss-normalize-unicode: 5.1.1(postcss@8.4.38) - postcss-normalize-url: 5.1.0(postcss@8.4.38) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.38) - postcss-ordered-values: 5.1.3(postcss@8.4.38) - postcss-reduce-initial: 5.1.2(postcss@8.4.38) - postcss-reduce-transforms: 5.1.0(postcss@8.4.38) - postcss-svgo: 5.1.0(postcss@8.4.38) - postcss-unique-selectors: 5.1.1(postcss@8.4.38) - - cssnano-utils@3.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - cssnano@5.1.15(postcss@8.4.38): - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.4.38) - lilconfig: 2.1.0 - postcss: 8.4.38 - yaml: 1.10.2 - - csso@4.2.0: - dependencies: - css-tree: 1.1.3 - csstype@3.1.3: {} - decode-uri-component@0.2.2: {} - deepmerge@4.3.1: {} del@5.1.0: @@ -2059,24 +1481,6 @@ snapshots: dependencies: path-type: 4.0.0 - dom-serializer@1.4.1: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - - domelementtype@2.3.0: {} - - domhandler@4.3.1: - dependencies: - domelementtype: 2.3.0 - - domutils@2.8.0: - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - eastasianwidth@0.2.0: {} electron-to-chromium@1.4.775: {} @@ -2090,18 +1494,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 - entities@2.2.0: {} - - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - es-module-lexer@1.5.3: {} escalade@3.1.2: {} - escape-string-regexp@1.0.5: {} - eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 @@ -2123,8 +1519,6 @@ snapshots: dependencies: '@types/estree': 1.0.5 - eventemitter3@4.0.7: {} - events@3.3.0: {} fast-deep-equal@3.1.3: {} @@ -2147,19 +1541,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 - filter-obj@1.1.0: {} - foreground-child@3.2.1: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -2203,25 +1589,14 @@ snapshots: graceful-fs@4.2.11: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} hasown@2.0.2: dependencies: function-bind: 1.1.2 - icss-utils@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - ignore@5.3.1: {} - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - indent-string@4.0.0: {} inflight@1.0.6: @@ -2233,8 +1608,6 @@ snapshots: interpret@1.4.0: {} - is-arrayish@0.2.1: {} - is-arrayish@0.3.2: {} is-builtin-module@3.2.1: @@ -2269,6 +1642,8 @@ snapshots: dependencies: '@types/estree': 1.0.5 + is-what@5.0.2: {} + isexe@2.0.0: {} jackspeak@3.4.0: @@ -2289,22 +1664,8 @@ snapshots: json-schema-traverse@0.4.1: {} - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - lilconfig@2.1.0: {} - - lines-and-columns@1.2.4: {} - loader-runner@4.3.0: {} - lodash.memoize@4.1.2: {} - - lodash.uniq@4.5.0: {} - lodash@4.17.21: {} loose-envify@1.4.0: @@ -2317,7 +1678,9 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - mdn-data@2.0.14: {} + merge-anything@6.0.2: + dependencies: + is-what: 5.0.2 merge-stream@2.0.0: {} @@ -2346,48 +1709,18 @@ snapshots: minipass@7.1.2: {} - nanoid@3.3.7: {} - neo-async@2.6.2: {} node-releases@2.0.14: {} - normalize-url@6.1.0: {} - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - once@1.4.0: dependencies: wrappy: 1.0.2 - p-finally@1.0.0: {} - p-map@3.0.0: dependencies: aggregate-error: 3.1.0 - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.24.2 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -2405,197 +1738,8 @@ snapshots: picomatch@2.3.1: {} - postcss-calc@8.2.4(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - postcss-value-parser: 4.2.0 - - postcss-colormin@5.3.1(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-convert-values@5.1.3(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@5.1.2(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - postcss-discard-duplicates@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - postcss-discard-empty@5.1.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - postcss-discard-overridden@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - postcss-merge-longhand@5.1.7(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.38) - - postcss-merge-rules@5.1.4(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.38) - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - - postcss-minify-font-values@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@5.1.1(postcss@8.4.38): - dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.38) - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-minify-params@5.1.4(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - cssnano-utils: 3.1.0(postcss@8.4.38) - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@5.2.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - - postcss-modules-extract-imports@3.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - postcss-modules-local-by-default@4.0.5(postcss@8.4.38): - dependencies: - icss-utils: 5.1.0(postcss@8.4.38) - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - postcss-value-parser: 4.2.0 - - postcss-modules-scope@3.2.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - - postcss-modules-values@4.0.0(postcss@8.4.38): - dependencies: - icss-utils: 5.1.0(postcss@8.4.38) - postcss: 8.4.38 - - postcss-normalize-charset@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - - postcss-normalize-display-values@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@5.1.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@5.1.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@5.1.1(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@5.1.0(postcss@8.4.38): - dependencies: - normalize-url: 6.1.0 - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@5.1.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@5.1.3(postcss@8.4.38): - dependencies: - cssnano-utils: 3.1.0(postcss@8.4.38) - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@5.1.2(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - caniuse-api: 3.0.0 - postcss: 8.4.38 - - postcss-reduce-transforms@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - - postcss-selector-parser@6.0.16: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@5.1.0(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 - - postcss-unique-selectors@5.1.1(postcss@8.4.38): - dependencies: - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - - postcss-value-parser@4.2.0: {} - - postcss@8.4.38: - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - punycode@2.3.1: {} - query-string@7.1.3: - dependencies: - decode-uri-component: 0.2.2 - filter-obj: 1.1.0 - split-on-first: 1.1.0 - strict-uri-encode: 2.0.0 - queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -2616,8 +1760,6 @@ snapshots: regenerator-runtime@0.14.1: {} - resolve-from@4.0.0: {} - resolve@1.22.8: dependencies: is-core-module: 2.13.1 @@ -2634,66 +1776,46 @@ snapshots: dependencies: del: 5.1.0 - rollup-plugin-external-globals@0.10.0(rollup@4.18.0): + rollup-plugin-external-globals@0.11.0(rollup@4.24.4): dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) estree-walker: 3.0.3 is-reference: 3.0.2 magic-string: 0.30.10 - rollup: 4.18.0 + rollup: 4.24.4 - rollup-plugin-import-assets@1.1.1(rollup@4.18.0): + rollup-plugin-import-assets@1.1.1(rollup@4.24.4): dependencies: - rollup: 4.18.0 + rollup: 4.24.4 rollup-pluginutils: 2.8.2 url-join: 4.0.1 - rollup-plugin-styles@4.0.0(rollup@4.18.0): - dependencies: - '@rollup/pluginutils': 4.2.1 - '@types/cssnano': 5.1.0(postcss@8.4.38) - cosmiconfig: 7.1.0 - cssnano: 5.1.15(postcss@8.4.38) - fs-extra: 10.1.0 - icss-utils: 5.1.0(postcss@8.4.38) - mime-types: 2.1.35 - p-queue: 6.6.2 - postcss: 8.4.38 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) - postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) - postcss-modules-scope: 3.2.0(postcss@8.4.38) - postcss-modules-values: 4.0.0(postcss@8.4.38) - postcss-value-parser: 4.2.0 - query-string: 7.1.3 - resolve: 1.22.8 - rollup: 4.18.0 - source-map-js: 1.2.0 - tslib: 2.6.2 - rollup-pluginutils@2.8.2: dependencies: estree-walker: 0.6.1 - rollup@4.18.0: + rollup@4.24.4: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 + '@rollup/rollup-android-arm-eabi': 4.24.4 + '@rollup/rollup-android-arm64': 4.24.4 + '@rollup/rollup-darwin-arm64': 4.24.4 + '@rollup/rollup-darwin-x64': 4.24.4 + '@rollup/rollup-freebsd-arm64': 4.24.4 + '@rollup/rollup-freebsd-x64': 4.24.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.4 + '@rollup/rollup-linux-arm-musleabihf': 4.24.4 + '@rollup/rollup-linux-arm64-gnu': 4.24.4 + '@rollup/rollup-linux-arm64-musl': 4.24.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.4 + '@rollup/rollup-linux-riscv64-gnu': 4.24.4 + '@rollup/rollup-linux-s390x-gnu': 4.24.4 + '@rollup/rollup-linux-x64-gnu': 4.24.4 + '@rollup/rollup-linux-x64-musl': 4.24.4 + '@rollup/rollup-win32-arm64-msvc': 4.24.4 + '@rollup/rollup-win32-ia32-msvc': 4.24.4 + '@rollup/rollup-win32-x64-msvc': 4.24.4 fsevents: 2.3.3 run-parallel@1.2.0: @@ -2739,8 +1861,6 @@ snapshots: slash@4.0.0: {} - source-map-js@1.2.0: {} - source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -2748,12 +1868,6 @@ snapshots: source-map@0.6.1: {} - split-on-first@1.1.0: {} - - stable@0.1.8: {} - - strict-uri-encode@2.0.0: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -2774,32 +1888,12 @@ snapshots: dependencies: ansi-regex: 6.0.1 - stylehacks@5.1.1(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 - - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@8.1.1: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} - svgo@2.8.0: - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.0.1 - stable: 0.1.8 - tailwind-merge@2.3.0: dependencies: '@babel/runtime': 7.24.5 @@ -2828,11 +1922,11 @@ snapshots: tslib@2.6.2: {} - typescript@4.9.5: {} + tslib@2.8.1: {} - undici-types@5.26.5: {} + typescript@5.6.3: {} - universalify@2.0.1: {} + undici-types@5.26.5: {} update-browserslist-db@1.0.16(browserslist@4.23.0): dependencies: @@ -2850,8 +1944,6 @@ snapshots: dependencies: react: 18.3.1 - util-deprecate@1.0.2: {} - watchpack@2.4.1: dependencies: glob-to-regexp: 0.4.1 @@ -2908,8 +2000,6 @@ snapshots: wrappy@1.0.2: {} - yaml@1.10.2: {} - zustand@4.5.2(@types/react@16.14.0)(react@18.3.1): dependencies: use-sync-external-store: 1.2.0(react@18.3.1) diff --git a/rollup.config.js b/rollup.config.js index 63360a9..5055b43 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,66 +1,18 @@ -import commonjs from "@rollup/plugin-commonjs"; -import json from "@rollup/plugin-json"; -import { nodeResolve } from "@rollup/plugin-node-resolve"; -import replace from "@rollup/plugin-replace"; -import typescript from "@rollup/plugin-typescript"; -import { defineConfig } from "rollup"; -import importAssets from "rollup-plugin-import-assets"; -import styles from "rollup-plugin-styles"; +import deckyPlugin from "@decky/rollup"; import alias from "@rollup/plugin-alias"; -import del from "rollup-plugin-delete"; -import externalGlobals from "rollup-plugin-external-globals"; -// replace "assert" with "with" once node implements that -import manifest from "./plugin.json" assert { type: "json" }; - -export default defineConfig({ - input: "./src/index.tsx", +export default deckyPlugin({ plugins: [ - del({ targets: "./dist/*", force: true }), - commonjs(), - nodeResolve({ - browser: true, - }), - externalGlobals({ - react: "SP_REACT", - "react-dom": "SP_REACTDOM", - "@decky/ui": "DFL", - "@decky/manifest": JSON.stringify(manifest), - }), - typescript(), - json(), - replace({ - preventAssignment: false, - "process.env.NODE_ENV": JSON.stringify("production"), - }), - styles(), alias({ entries: [ - { find: "@cssloader/backend", replacement: "./src/backend" }, - { find: "@/backend", replacement: "./src/backend-impl" }, - { find: "@/lib", replacement: "./src/lib" }, - { find: "@/styles", replacement: "./src/styles" }, - { find: "@/types", replacement: "./src/types" }, - { find: "@/modules", replacement: "./src/modules" }, - { find: "@/decky-patches", replacement: "./src/decky-patches" }, + { find: "@cssloader/backend", replacement: `${import.meta.dirname}//src/backend` }, + { find: "@/backend", replacement: `${import.meta.dirname}/src/backend-impl` }, + { find: "@/lib", replacement: `${import.meta.dirname}/src/lib` }, + { find: "@/styles", replacement: `${import.meta.dirname}/src/styles` }, + { find: "@/types", replacement: `${import.meta.dirname}/src/types` }, + { find: "@/modules", replacement: `${import.meta.dirname}/src/modules` }, + { find: "@/decky-patches", replacement: `${import.meta.dirname}/src/decky-patches` }, ], }), - importAssets({ - publicPath: `http://127.0.0.1:1337/plugins/${manifest.name}/`, - }), ], - context: "window", - external: ["react", "react-dom", "@decky/ui"], - output: { - dir: "dist", - format: "esm", - sourcemap: true, - // **Don't** change this. - sourcemapPathTransform: (relativeSourcePath) => - relativeSourcePath.replace( - /^\.\.\//, - `decky://decky/plugin/${encodeURIComponent(manifest.name)}/` - ), - exports: "default", - }, }); From 69eaf0e3b8fdb0a27a032a8a9d4011ebebad2d21 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:35:47 -0700 Subject: [PATCH 30/53] add loading indicator to theme store --- .../components/ThemeBrowserPage.tsx | 44 ++++++++++++------- .../theme-store/context/ThemeBrowserStore.tsx | 4 ++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/modules/theme-store/components/ThemeBrowserPage.tsx b/src/modules/theme-store/components/ThemeBrowserPage.tsx index 51055c8..7fc3810 100644 --- a/src/modules/theme-store/components/ThemeBrowserPage.tsx +++ b/src/modules/theme-store/components/ThemeBrowserPage.tsx @@ -4,10 +4,12 @@ import { BrowserSearchFields } from "./BrowserSearchFields"; import { useCSSLoaderValue } from "@/backend"; import { ThemeCard } from "./ThemeCard"; import { useEffect, useRef } from "react"; +import { ImSpinner5 } from "react-icons/im"; export function ThemeBrowserPage() { const initializeStore = useThemeBrowserStoreAction("initializeStore"); const themes = useThemeBrowserStoreValue("themes"); + const loading = useThemeBrowserStoreValue("loading"); const indexToSnapToOnLoad = useThemeBrowserStoreValue("indexToSnapToOnLoad"); const backendVersion = useCSSLoaderValue("backendVersion"); @@ -21,22 +23,32 @@ export function ThemeBrowserPage() { return ( <> - - {themes.items - .filter((theme) => theme.manifestVersion <= backendVersion) - .map((theme, index) => ( - - ))} + + {loading ? ( +
+ + {/* Re-using expanded view's loading class */} + Loading +
+ ) : ( + <> + {themes.items + .filter((theme) => theme.manifestVersion <= backendVersion) + .map((theme, index) => ( + + ))} + + )}
); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 87135cf..067d1df 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -6,6 +6,7 @@ import { isEqual } from "lodash"; import { getThemeBrowserSharedState } from "./ThemeBrowserSharedStore"; interface ThemeBrowserStoreValues { + loading: boolean; themes: ThemeQueryResponse; searchOpts: ThemeQueryRequest; prevSearchOpts: ThemeQueryRequest; @@ -57,6 +58,7 @@ export function ThemeBrowserStoreProvider({ if (!storeRef.current) { storeRef.current = createStore((set, get) => ({ + loading: true, themes: { total: 0, items: [] }, searchOpts: { page: 1, @@ -109,6 +111,7 @@ export function ThemeBrowserStoreProvider({ await getThemes(); }, getThemes: async () => { + set({ loading: true }); try { const { searchOpts } = get(); const { targetOverride } = getThemeBrowserSharedState(); @@ -125,6 +128,7 @@ export function ThemeBrowserStoreProvider({ set({ themes: response, indexToSnapToOnLoad: -1 }); } } catch (error) {} + set({ loading: false }); }, })); } From 4e13bd201355e82a769c5a75a403a37e4a4c876b Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:38:21 -0700 Subject: [PATCH 31/53] switch build to node 20 --- .github/workflows/push.yml | 110 ++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 3e19fe0..772202d 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -8,69 +8,69 @@ jobs: runs-on: ubuntu-20.04 steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up NodeJS 20 + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Install JS dependencies + run: | + npm i -g pnpm + pnpm install + + - name: Build Frontend + run: | + pnpm run build + + - name: Package Release + run: | + mkdir "SDH-CssLoader" + cp *.py "./SDH-CssLoader" + cp *.json "./SDH-CssLoader" + cp LICENSE "./SDH-CssLoader" + cp README.md "./SDH-CssLoader" + cp -r dist "./SDH-CssLoader" + mkdir upload + mv "./SDH-CssLoader" ./upload + + - name: Upload package artifact + uses: actions/upload-artifact@v3 + with: + name: SDH-CSSLoader-Decky + path: ./upload - - name: Set up NodeJS 18 - uses: actions/setup-node@v3 - with: - node-version: 18 - - - name: Install JS dependencies - run: | - npm i -g pnpm - pnpm install - - - name: Build Frontend - run: | - pnpm run build - - - name: Package Release - run: | - mkdir "SDH-CssLoader" - cp *.py "./SDH-CssLoader" - cp *.json "./SDH-CssLoader" - cp LICENSE "./SDH-CssLoader" - cp README.md "./SDH-CssLoader" - cp -r dist "./SDH-CssLoader" - mkdir upload - mv "./SDH-CssLoader" ./upload - - - name: Upload package artifact - uses: actions/upload-artifact@v3 - with: - name: SDH-CSSLoader-Decky - path: ./upload - build-standalone-win: name: Build SDH-CSSLoader Standalone for Windows runs-on: windows-2022 steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - name: Set up Python 3.10.2 - uses: actions/setup-python@v4 - with: - python-version: "3.10.2" + - name: Set up Python 3.10.2 + uses: actions/setup-python@v4 + with: + python-version: "3.10.2" - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install pyinstaller==5.5 - pip install -r requirements.txt + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller==5.5 + pip install -r requirements.txt - - name: Build Python Backend - run: pyinstaller --noconfirm --onefile --add-data "./assets;/assets" --name "CssLoader-Standalone" ./main.py ./css_win_tray.py + - name: Build Python Backend + run: pyinstaller --noconfirm --onefile --add-data "./assets;/assets" --name "CssLoader-Standalone" ./main.py ./css_win_tray.py - - name: Build Python Backend Headless - run: pyinstaller --noconfirm --noconsole --onefile --add-data "./assets;/assets" --name "CssLoader-Standalone-Headless" ./main.py ./css_win_tray.py + - name: Build Python Backend Headless + run: pyinstaller --noconfirm --noconsole --onefile --add-data "./assets;/assets" --name "CssLoader-Standalone-Headless" ./main.py ./css_win_tray.py - - name: Upload package artifact - uses: actions/upload-artifact@v3 - with: - name: SDH-CSSLoader-Win-Standalone - path: | - ./dist/CssLoader-Standalone.exe - ./dist/CssLoader-Standalone-Headless.exe + - name: Upload package artifact + uses: actions/upload-artifact@v3 + with: + name: SDH-CSSLoader-Win-Standalone + path: | + ./dist/CssLoader-Standalone.exe + ./dist/CssLoader-Standalone-Headless.exe From 3ac1575c587a62a55b54a2f8c21f15d2e64b9afe Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Fri, 8 Nov 2024 19:17:08 -0700 Subject: [PATCH 32/53] (UNTESTED): fix target override --- .../theme-store/context/ThemeBrowserSharedStore.tsx | 2 +- src/modules/theme-store/context/ThemeBrowserStore.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx index 754d531..8693ed7 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -18,7 +18,7 @@ interface IThemeBrowserSharedStore extends ThemeBrowserSharedStoreValues, ThemeBrowserSharedStoreActions {} -const themeBrowserSharedStore = createStore((set) => { +export const themeBrowserSharedStore = createStore((set) => { return { browserCardSize: 3, targetOverride: "", diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 067d1df..8b005c1 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -3,7 +3,7 @@ import { FilterQueryResponse, ThemeQueryRequest, ThemeQueryResponse } from "@/ty import { StoreApi, createStore, useStore } from "zustand"; import { getCSSLoaderState } from "@/backend"; import { isEqual } from "lodash"; -import { getThemeBrowserSharedState } from "./ThemeBrowserSharedStore"; +import { getThemeBrowserSharedState, themeBrowserSharedStore } from "./ThemeBrowserSharedStore"; interface ThemeBrowserStoreValues { loading: boolean; @@ -83,6 +83,12 @@ export function ThemeBrowserStoreProvider({ try { await get().getFilters(); await get().getThemes(); + + themeBrowserSharedStore.subscribe((state, prevState) => { + if (state.targetOverride !== prevState.targetOverride) { + get().getThemes(); + } + }); } catch (error) {} }, getFilters: async () => { From 1485804b1f8c618ab643ee9b79423a36236d207f Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:49:40 -0700 Subject: [PATCH 33/53] add separate BPM and Desktop theme tabs --- .../theme-store/context/ThemeBrowserStore.tsx | 28 +++++++++++-------- .../theme-store/pages/ThemeStoreRouter.tsx | 19 +++++++++++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 8b005c1..b1348b4 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -26,17 +26,14 @@ interface IThemeBrowserStore extends ThemeBrowserStoreValues, ThemeBrowserStoreA const ThemeBrowserStoreContext = createContext | null>(null); -function generateParamStr(searchOpts: ThemeQueryRequest) { +function generateParamStr(searchOpts: ThemeQueryRequest, themeType: "ALL" | "DESKTOP" | "BPM") { const searchOptsClone = structuredClone(searchOpts); - let prependString = "BPM-CSS.-Preset"; - if (searchOptsClone.filters.includes("Desktop")) { - prependString = "-Preset"; - } - if (searchOptsClone.filters.includes("Preset")) { - prependString = "BPM-CSS"; - } + + let prependString = + themeType === "ALL" ? "CSS" : themeType === "DESKTOP" ? "DESKTOP-CSS" : "BPM-CSS"; + // "All" is a fake term made up by the frontend to have a unique key for it, the server just expects empty searchOptsClone.filters === "All" ? (searchOptsClone.filters = "") : (prependString += "."); - prependString && (searchOptsClone.filters = prependString + searchOptsClone.filters); + searchOptsClone.filters = prependString + searchOptsClone.filters; // @ts-expect-error const paramStr = new URLSearchParams(searchOptsClone).toString(); @@ -47,11 +44,13 @@ export function ThemeBrowserStoreProvider({ children, filterPath, themePath, + themeType, requiresAuth = false, }: { children: React.ReactNode; filterPath: string; themePath: string; + themeType: "ALL" | "DESKTOP" | "BPM"; requiresAuth?: boolean; }) { const storeRef = useRef | null>(null); @@ -84,6 +83,7 @@ export function ThemeBrowserStoreProvider({ await get().getFilters(); await get().getThemes(); + // This ensures that it actually fetches new themed when you click on a forced target themeBrowserSharedStore.subscribe((state, prevState) => { if (state.targetOverride !== prevState.targetOverride) { get().getThemes(); @@ -93,9 +93,15 @@ export function ThemeBrowserStoreProvider({ }, getFilters: async () => { const { apiFetch } = getCSSLoaderState(); + const typeMapping = { + ALL: "CSS", + DESKTOP: "DESKTOP-CSS", + BPM: "BPM-CSS", + }; + try { const response = await apiFetch( - `${filterPath}?type=CSS`, + `${filterPath}?type=${typeMapping[themeType]}`, {}, requiresAuth ); @@ -126,7 +132,7 @@ export function ThemeBrowserStoreProvider({ const { apiFetch } = getCSSLoaderState(); const response = await apiFetch( - `${themePath}?${generateParamStr(searchOpts)}`, + `${themePath}?${generateParamStr(formattedSearchOpts, themeType)}`, {}, requiresAuth ); diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx index 61c5b88..7dca9c5 100644 --- a/src/modules/theme-store/pages/ThemeStoreRouter.tsx +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -13,12 +13,27 @@ export function ThemeStoreRouter() { onShowTab={(tab) => setCurrentTab(tab)} tabs={[ { - id: "allthemes", - title: "All Themes", + id: "bpm-themes", + title: "BPM Themes", content: ( + + + ), + }, + { + id: "desktop-themes", + title: "Desktop Themes", + content: ( + From 23dafea623f86d1558622f7be5c26ac9ece8ee8a Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:08:24 -0700 Subject: [PATCH 34/53] add load more button --- .../components/BrowserSearchFields.tsx | 12 +++--- .../theme-store/components/LoadMoreButton.tsx | 26 ++++++++++++ .../components/ThemeBrowserPage.tsx | 42 +++++++++++-------- .../theme-store/context/ThemeBrowserStore.tsx | 38 ++++++++++++----- 4 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 src/modules/theme-store/components/LoadMoreButton.tsx diff --git a/src/modules/theme-store/components/BrowserSearchFields.tsx b/src/modules/theme-store/components/BrowserSearchFields.tsx index 24c2ac8..61e5093 100644 --- a/src/modules/theme-store/components/BrowserSearchFields.tsx +++ b/src/modules/theme-store/components/BrowserSearchFields.tsx @@ -54,8 +54,8 @@ export function BrowserSearchFields() { strDefaultLabel="Last Updated" selectedOption={searchOpts.order} onChange={(value) => { - const newSearchOpts = { ...searchOpts, order: value.data }; - setSearchOpts(newSearchOpts); + const newSearchOpts = { ...searchOpts, order: value.data, page: 1 }; + void setSearchOpts(newSearchOpts); }} />
@@ -68,8 +68,8 @@ export function BrowserSearchFields() { selectedOption={targetOverride ?? searchOpts.filters} onChange={(value) => { // When you select a new target, remove the global override - const newSearchOpts = { ...searchOpts, filters: value.data }; - setSearchOpts(newSearchOpts); + const newSearchOpts = { ...searchOpts, filters: value.data, page: 1 }; + void setSearchOpts(newSearchOpts); setTargetOverride(null); }} /> @@ -83,8 +83,8 @@ export function BrowserSearchFields() { label="Search" value={searchOpts.search} onChange={(event) => { - const newSearchOpts = { ...searchOpts, search: event.target.value }; - setSearchOpts(newSearchOpts); + const newSearchOpts = { ...searchOpts, search: event.target.value, page: 1 }; + void setSearchOpts(newSearchOpts); }} />
diff --git a/src/modules/theme-store/components/LoadMoreButton.tsx b/src/modules/theme-store/components/LoadMoreButton.tsx new file mode 100644 index 0000000..0f13a23 --- /dev/null +++ b/src/modules/theme-store/components/LoadMoreButton.tsx @@ -0,0 +1,26 @@ +import { DialogButton } from "@decky/ui"; +import { useThemeBrowserStoreAction, useThemeBrowserStoreValue } from "../context"; + +export function LoadMoreButton() { + const searchOpts = useThemeBrowserStoreValue("searchOpts"); + const themeTotal = useThemeBrowserStoreValue("themeTotal"); + const themes = useThemeBrowserStoreValue("themes"); + const loading = useThemeBrowserStoreValue("loading"); + const setSearchOpts = useThemeBrowserStoreAction("setSearchOpts"); + + function handleClick() { + void setSearchOpts({ ...searchOpts, page: searchOpts.page + 1 }); + } + + return ( + <> + {themes.length < themeTotal ? ( + <> + + Load More + + + ) : null} + + ); +} diff --git a/src/modules/theme-store/components/ThemeBrowserPage.tsx b/src/modules/theme-store/components/ThemeBrowserPage.tsx index 7fc3810..b404cb8 100644 --- a/src/modules/theme-store/components/ThemeBrowserPage.tsx +++ b/src/modules/theme-store/components/ThemeBrowserPage.tsx @@ -5,6 +5,7 @@ import { useCSSLoaderValue } from "@/backend"; import { ThemeCard } from "./ThemeCard"; import { useEffect, useRef } from "react"; import { ImSpinner5 } from "react-icons/im"; +import { LoadMoreButton } from "./LoadMoreButton"; export function ThemeBrowserPage() { const initializeStore = useThemeBrowserStoreAction("initializeStore"); @@ -20,6 +21,12 @@ export function ThemeBrowserPage() { void initializeStore(); }, []); + useEffect(() => { + if (endOfPageRef?.current) { + endOfPageRef?.current?.focus(); + } + }, [indexToSnapToOnLoad]); + return ( <> @@ -31,25 +38,26 @@ export function ThemeBrowserPage() { Loading
) : ( - <> - {themes.items - .filter((theme) => theme.manifestVersion <= backendVersion) - .map((theme, index) => ( - - ))} - + themes + .filter((theme) => theme.manifestVersion <= backendVersion) + .map((theme, index) => ( + + )) )} +
+ +
); } diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index b1348b4..06e6fe1 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -1,5 +1,10 @@ import { createContext, useContext, useRef } from "react"; -import { FilterQueryResponse, ThemeQueryRequest, ThemeQueryResponse } from "@/types"; +import { + FilterQueryResponse, + PartialCSSThemeInfo, + ThemeQueryRequest, + ThemeQueryResponse, +} from "@/types"; import { StoreApi, createStore, useStore } from "zustand"; import { getCSSLoaderState } from "@/backend"; import { isEqual } from "lodash"; @@ -7,7 +12,8 @@ import { getThemeBrowserSharedState, themeBrowserSharedStore } from "./ThemeBrow interface ThemeBrowserStoreValues { loading: boolean; - themes: ThemeQueryResponse; + themes: PartialCSSThemeInfo[]; + themeTotal: number; searchOpts: ThemeQueryRequest; prevSearchOpts: ThemeQueryRequest; filterOptions: FilterQueryResponse; @@ -17,7 +23,7 @@ interface ThemeBrowserStoreValues { interface ThemeBrowserStoreActions { initializeStore: () => Promise; getFilters: () => Promise; - setSearchOpts: (searchOpts: ThemeQueryRequest) => void; + setSearchOpts: (searchOpts: ThemeQueryRequest) => Promise; refreshThemes: () => Promise; getThemes: () => Promise; } @@ -58,7 +64,8 @@ export function ThemeBrowserStoreProvider({ if (!storeRef.current) { storeRef.current = createStore((set, get) => ({ loading: true, - themes: { total: 0, items: [] }, + themes: [], + themeTotal: 0, searchOpts: { page: 1, perPage: 50, @@ -110,17 +117,18 @@ export function ThemeBrowserStoreProvider({ } } catch (error) {} }, - setSearchOpts(searchOpts) { + setSearchOpts: async (searchOpts, options: { dontResetPage?: boolean } = {}) => { const { searchOpts: prevSearchOpts, themes, getThemes } = get(); set({ searchOpts, prevSearchOpts }); - if (!isEqual(prevSearchOpts, searchOpts) || themes.total === 0) { - getThemes(); + if (!isEqual(prevSearchOpts, searchOpts) || themes.length === 0) { + await getThemes(); } }, refreshThemes: async () => { - const { getThemes } = get(); - await getThemes(); + // setSearchOpts calls get + const { searchOpts, setSearchOpts } = get(); + await setSearchOpts({ ...searchOpts, page: 1 }); }, getThemes: async () => { set({ loading: true }); @@ -137,7 +145,17 @@ export function ThemeBrowserStoreProvider({ requiresAuth ); if (response.items) { - set({ themes: response, indexToSnapToOnLoad: -1 }); + set({ themeTotal: response.total }); + if (searchOpts.page === 1) { + set({ themes: response.items, indexToSnapToOnLoad: -1 }); + } else { + set({ + themes: [...get().themes, ...response.items], + // This ensures that you snap back to the last theme you were viewing + // For example, if you were at the end of page 1 (theme 50) and you load page 2, you should snap back to theme 50 + indexToSnapToOnLoad: searchOpts.perPage * (searchOpts.page - 1) - 1, + }); + } } } catch (error) {} set({ loading: false }); From ed57887f25c2e47565b0bea521ef3d17f803d709 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:19:36 -0700 Subject: [PATCH 35/53] finish load more button --- .../theme-store/components/LoadMoreButton.tsx | 8 ++++---- .../components/ThemeBrowserPage.tsx | 4 +--- src/styles/styles-as-string.ts | 19 ++++++++++++++++++- src/styles/styles.css | 14 +++++++++++++- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/modules/theme-store/components/LoadMoreButton.tsx b/src/modules/theme-store/components/LoadMoreButton.tsx index 0f13a23..c7c63a0 100644 --- a/src/modules/theme-store/components/LoadMoreButton.tsx +++ b/src/modules/theme-store/components/LoadMoreButton.tsx @@ -13,14 +13,14 @@ export function LoadMoreButton() { } return ( - <> +
{themes.length < themeTotal ? ( - <> +
Load More - +
) : null} - +
); } diff --git a/src/modules/theme-store/components/ThemeBrowserPage.tsx b/src/modules/theme-store/components/ThemeBrowserPage.tsx index b404cb8..9d5e047 100644 --- a/src/modules/theme-store/components/ThemeBrowserPage.tsx +++ b/src/modules/theme-store/components/ThemeBrowserPage.tsx @@ -55,9 +55,7 @@ export function ThemeBrowserPage() { )) )} -
- -
+ ); } diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index d201bb9..52f6475 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -1,6 +1,11 @@ import { gamepadDialogClasses } from "@decky/ui"; export const styles = ` +/* THIS FILE IS NOT USED IN BUILD */ +/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO styles-as-string.ts */ +/* THAT IS NEEDED FOR STATIC CLASS INJECTION */ +/* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */ + .flex { display: flex !important; } @@ -69,6 +74,10 @@ export const styles = ` width: 100% !important; } +.max-w-1\/2 { + max-width: 50% !important; +} + .relative { position: relative !important; } @@ -196,7 +205,7 @@ export const styles = ` align-items: start !important; } -.cl-store-dropdown-hide-spacer> button > div > .${gamepadDialogClasses.Spacer} { +.cl-store-dropdown-hide-spacer > button > div > .${gamepadDialogClasses.Spacer} { width: 0 !important; } @@ -213,6 +222,14 @@ export const styles = ` height: 48% !important; } +.cl-store-loadmore-container { + display: flex !important; + justify-content: center !important; + align-items: center !important; + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} + .cl-store-scale-slider { min-width: 20% !important; } diff --git a/src/styles/styles.css b/src/styles/styles.css index d179ee2..af464cf 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -71,6 +71,10 @@ width: 100% !important; } +.max-w-1\/2 { + max-width: 50% !important; +} + .relative { position: relative !important; } @@ -198,7 +202,7 @@ align-items: start !important; } -.cl-store-dropdown-hide-spacer> button > div > .${gamepadDialogClasses.Spacer} { +.cl-store-dropdown-hide-spacer > button > div > .${gamepadDialogClasses.Spacer} { width: 0 !important; } @@ -215,6 +219,14 @@ height: 48% !important; } +.cl-store-loadmore-container { + display: flex !important; + justify-content: center !important; + align-items: center !important; + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} + .cl-store-scale-slider { min-width: 20% !important; } From a0bcbe60ab0cdd0d63035c442e0a1376f5533126 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:03:09 -0700 Subject: [PATCH 36/53] fix navigation back to the expanded view --- .../unminify-mode/class-hash-map.ts | 4 +++ .../components/BrowserSearchFields.tsx | 9 ++--- .../context/ThemeBrowserSharedStore.tsx | 4 +++ .../theme-store/context/ThemeBrowserStore.tsx | 35 +++++++++++-------- .../theme-store/pages/ThemeStoreRouter.tsx | 10 ++++-- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/decky-patches/unminify-mode/class-hash-map.ts b/src/decky-patches/unminify-mode/class-hash-map.ts index c82c870..86c96ff 100644 --- a/src/decky-patches/unminify-mode/class-hash-map.ts +++ b/src/decky-patches/unminify-mode/class-hash-map.ts @@ -12,6 +12,7 @@ export function initializeClassHashMap() { // Filter out things that start with a number (eg: Breakpoints like 800px) // I have confirmed the new classes don't start with numbers if (isNaN(Number(value.charAt(0)))) { + // @ts-expect-error filteredModule[propertyName] = value; } }); @@ -24,9 +25,12 @@ export function initializeClassHashMap() { const mappings = allClasses.reduce((acc, cur) => { Object.entries(cur).forEach(([property, value]) => { + // @ts-expect-error if (acc[property]) { + // @ts-expect-error acc[property].push(value); } else { + // @ts-expect-error acc[property] = [value]; } }); diff --git a/src/modules/theme-store/components/BrowserSearchFields.tsx b/src/modules/theme-store/components/BrowserSearchFields.tsx index 61e5093..0294a66 100644 --- a/src/modules/theme-store/components/BrowserSearchFields.tsx +++ b/src/modules/theme-store/components/BrowserSearchFields.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useEffect, useMemo } from "react"; import { useThemeBrowserSharedAction, useThemeBrowserSharedValue, @@ -23,9 +23,6 @@ export function BrowserSearchFields() { const setSearchOpts = useThemeBrowserStoreAction("setSearchOpts"); const refreshThemes = useThemeBrowserStoreAction("refreshThemes"); - const targetOverride = useThemeBrowserSharedValue("targetOverride"); - const setTargetOverride = useThemeBrowserSharedAction("setTargetOverride"); - const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); const setBrowserCardSize = useThemeBrowserSharedAction("setBrowserCardSize"); @@ -65,12 +62,10 @@ export function BrowserSearchFields() { menuLabel="Filter" rgOptions={formattedFilters} strDefaultLabel="All" - selectedOption={targetOverride ?? searchOpts.filters} + selectedOption={searchOpts.filters} onChange={(value) => { - // When you select a new target, remove the global override const newSearchOpts = { ...searchOpts, filters: value.data, page: 1 }; void setSearchOpts(newSearchOpts); - setTargetOverride(null); }} />
diff --git a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx index 8693ed7..6c4236c 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -6,11 +6,13 @@ export type ColumnNumbers = 3 | 4 | 5; interface ThemeBrowserSharedStoreValues { browserCardSize: ColumnNumbers; + currentTab: string; targetOverride: string | null; } interface ThemeBrowserSharedStoreActions { setBrowserCardSize: (value: ColumnNumbers) => void; + setCurrentTab: (value: string) => void; setTargetOverride: (value: string | null) => void; } @@ -21,8 +23,10 @@ interface IThemeBrowserSharedStore export const themeBrowserSharedStore = createStore((set) => { return { browserCardSize: 3, + currentTab: "bpm-themes", targetOverride: "", setBrowserCardSize: (value: ColumnNumbers) => set({ browserCardSize: value }), + setCurrentTab: (value: string) => set({ currentTab: value }), setTargetOverride: (value: string | null) => set({ targetOverride: value }), }; }); diff --git a/src/modules/theme-store/context/ThemeBrowserStore.tsx b/src/modules/theme-store/context/ThemeBrowserStore.tsx index 06e6fe1..c242109 100644 --- a/src/modules/theme-store/context/ThemeBrowserStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserStore.tsx @@ -23,7 +23,7 @@ interface ThemeBrowserStoreValues { interface ThemeBrowserStoreActions { initializeStore: () => Promise; getFilters: () => Promise; - setSearchOpts: (searchOpts: ThemeQueryRequest) => Promise; + setSearchOpts: (searchOpts: ThemeQueryRequest, forceRefresh?: boolean) => Promise; refreshThemes: () => Promise; getThemes: () => Promise; } @@ -88,14 +88,22 @@ export function ThemeBrowserStoreProvider({ initializeStore: async () => { try { await get().getFilters(); - await get().getThemes(); - // This ensures that it actually fetches new themed when you click on a forced target - themeBrowserSharedStore.subscribe((state, prevState) => { - if (state.targetOverride !== prevState.targetOverride) { - get().getThemes(); - } - }); + // When you navigate to the expanded view and back, it re-loads the page, which re-runs this, so we can just check if there is a target override + const { targetOverride } = getThemeBrowserSharedState(); + if (targetOverride) { + get().setSearchOpts( + { + ...get().searchOpts, + filters: targetOverride, + page: 1, + }, + true + ); + themeBrowserSharedStore.setState({ targetOverride: null }); + } else { + await get().getThemes(); + } } catch (error) {} }, getFilters: async () => { @@ -117,30 +125,27 @@ export function ThemeBrowserStoreProvider({ } } catch (error) {} }, - setSearchOpts: async (searchOpts, options: { dontResetPage?: boolean } = {}) => { + setSearchOpts: async (searchOpts, forceRefresh?: boolean) => { const { searchOpts: prevSearchOpts, themes, getThemes } = get(); set({ searchOpts, prevSearchOpts }); - if (!isEqual(prevSearchOpts, searchOpts) || themes.length === 0) { + if (!isEqual(prevSearchOpts, searchOpts) || forceRefresh || themes.length === 0) { await getThemes(); } }, refreshThemes: async () => { // setSearchOpts calls get const { searchOpts, setSearchOpts } = get(); - await setSearchOpts({ ...searchOpts, page: 1 }); + await setSearchOpts({ ...searchOpts, page: 1 }, true); }, getThemes: async () => { set({ loading: true }); try { const { searchOpts } = get(); - const { targetOverride } = getThemeBrowserSharedState(); - const formattedSearchOpts = { ...searchOpts }; - targetOverride && (formattedSearchOpts.filters = targetOverride); const { apiFetch } = getCSSLoaderState(); const response = await apiFetch( - `${themePath}?${generateParamStr(formattedSearchOpts, themeType)}`, + `${themePath}?${generateParamStr(searchOpts, themeType)}`, {}, requiresAuth ); diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx index 7dca9c5..5f34785 100644 --- a/src/modules/theme-store/pages/ThemeStoreRouter.tsx +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -1,10 +1,14 @@ import { Tabs } from "@decky/ui"; import { ThemeBrowserPage, ThemeCardCSSVariableProvider } from "../components"; -import { ThemeBrowserStoreProvider } from "../context"; -import { useState } from "react"; +import { + ThemeBrowserStoreProvider, + useThemeBrowserSharedAction, + useThemeBrowserSharedValue, +} from "../context"; export function ThemeStoreRouter() { - const [currentTab, setCurrentTab] = useState("allthemes"); + const currentTab = useThemeBrowserSharedValue("currentTab"); + const setCurrentTab = useThemeBrowserSharedAction("setCurrentTab"); return (
From e85e9cdc9dc5d004413f0348c75ffc2fc8d64ac2 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:05:22 -0700 Subject: [PATCH 37/53] add in note to futureebles --- src/modules/theme-store/pages/ThemeStoreRouter.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx index 5f34785..76c0af6 100644 --- a/src/modules/theme-store/pages/ThemeStoreRouter.tsx +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -6,6 +6,8 @@ import { useThemeBrowserSharedValue, } from "../context"; +// TODO: Make the tab definition a constant so that it isn't re-generated every page load + export function ThemeStoreRouter() { const currentTab = useThemeBrowserSharedValue("currentTab"); const setCurrentTab = useThemeBrowserSharedAction("setCurrentTab"); From 4416431918c5b585d6e91f361f8b0feb93d13713 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:01:32 -0700 Subject: [PATCH 38/53] finish account page --- src/backend/state/theme-store.ts | 50 ++++++++ src/modules/theme-store/pages/AccountPage.tsx | 69 +++++++++++ .../theme-store/pages/ThemeStoreRouter.tsx | 109 ++++++++++++------ src/styles/styles-as-string.ts | 50 +++++++- src/styles/styles.css | 50 +++++++- 5 files changed, 284 insertions(+), 44 deletions(-) create mode 100644 src/modules/theme-store/pages/AccountPage.tsx diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 7143ceb..5a6c92f 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -51,6 +51,8 @@ export interface CSSLoaderStateActions { request?: RequestInit, requiresAuth?: boolean | string ) => Promise; + logInWithShortToken: (newToken?: string) => Promise; + logOut: () => void; getThemes: () => Promise; changePreset: (presetName: string) => Promise; testBackend: () => Promise; @@ -171,6 +173,10 @@ export const createCSSLoaderStore = (backend: Backend) => const hiddenMotd = await backend.storeRead("hiddenMotd"); set({ hiddenMotdId: hiddenMotd ?? "" }); + if (shortToken) { + await get().logInWithShortToken(); + } + const { bulkThemeUpdateCheck, scheduleBulkThemeUpdateCheck } = get(); await bulkThemeUpdateCheck(); scheduleBulkThemeUpdateCheck(); @@ -212,6 +218,50 @@ export const createCSSLoaderStore = (backend: Backend) => console.error("Error Reloading Themes", error); } }, + logInWithShortToken: async (newToken?: string) => { + try { + const token = newToken ?? get().apiShortToken; + if (!token) { + throw new Error("No Token Provided"); + } + // This can't use apiFetch because it doesn't use header based auth + const json = await backend.fetch<{ token: string }>(`${apiUrl}/auth/authenticate_token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ token }), + }); + if (!json.token) { + throw new FetchError( + "Token Authentication Failed", + `${apiUrl}/auth/authenticate_token`, + "No Token in Response" + ); + } + backend.storeWrite("shortToken", token); + set({ + apiShortToken: token, + apiFullToken: json.token, + apiTokenExpireDate: new Date().valueOf() + 1000 * 10 * 60, + }); + const meJson = await apiFetch("/auth/me", undefined, true); + if (meJson) { + set({ apiMeData: meJson }); + } + } catch (error) { + backend.toast("CSSLoader", "Failed to log in"); + } + }, + logOut: () => { + set({ + apiShortToken: "", + apiFullToken: "", + apiMeData: undefined, + apiTokenExpireDate: undefined, + }); + backend.storeWrite("shortToken", ""); + }, refreshToken: async (): Promise => { const { apiFullToken, apiTokenExpireDate } = get(); if (!apiFullToken) { diff --git a/src/modules/theme-store/pages/AccountPage.tsx b/src/modules/theme-store/pages/AccountPage.tsx new file mode 100644 index 0000000..58feca7 --- /dev/null +++ b/src/modules/theme-store/pages/AccountPage.tsx @@ -0,0 +1,69 @@ +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { DialogButton, Focusable, TextField } from "@decky/ui"; +import { useState } from "react"; +import { FaArrowRightToBracket } from "react-icons/fa6"; + +export function AccountPage() { + const apiFullToken = useCSSLoaderValue("apiFullToken"); + + return ( +
+

{apiFullToken ? "Your Account" : "Log In"}

+ {apiFullToken ? : } +

+ Logging in gives you access to star themes, saving them to their own page where you can + quickly find them. +
+ To get started, create an account on deckthemes.com and generate an account key from your + profile page. +
+

+
+ ); +} +function LoggedInSection() { + const apiMeData = useCSSLoaderValue("apiMeData"); + const logOut = useCSSLoaderAction("logOut"); + return ( + + + {apiMeData ? `Logged in as ${apiMeData.username}` : "Loading..."} + + + Unlink My Deck + + + ); +} + +function LoggedOutSection() { + const apiFullToken = useCSSLoaderValue("apiFullToken"); + const logInWithShortToken = useCSSLoaderAction("logInWithShortToken"); + const apiShortToken = useCSSLoaderValue("apiShortToken"); + + const [shortTokenInterimValue, setShortTokenIntValue] = useState(apiShortToken); + + return ( + +
+ setShortTokenIntValue(e.target.value)} + /> +
+ { + logInWithShortToken(shortTokenInterimValue); + }} + > + + Log In + +
+ ); +} diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx index 76c0af6..96fde7d 100644 --- a/src/modules/theme-store/pages/ThemeStoreRouter.tsx +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -5,49 +5,90 @@ import { useThemeBrowserSharedAction, useThemeBrowserSharedValue, } from "../context"; +import { AccountPage } from "./AccountPage"; +import { useCSSLoaderValue } from "@/backend"; +import { Permissions } from "@/types"; // TODO: Make the tab definition a constant so that it isn't re-generated every page load export function ThemeStoreRouter() { const currentTab = useThemeBrowserSharedValue("currentTab"); const setCurrentTab = useThemeBrowserSharedAction("setCurrentTab"); + + const apiMeData = useCSSLoaderValue("apiMeData"); + + const tabs = [ + { + id: "bpm-themes", + title: "Deck UI Themes", + content: ( + + + + ), + }, + { + id: "desktop-themes", + title: "Desktop Themes", + content: ( + + + + ), + }, + { + id: "account", + title: "Account", + content: , + }, + ]; + + apiMeData?.permissions?.includes(Permissions.viewSubs) && + tabs.splice(2, 0, { + id: "submissions", + title: "Submissions", + content: ( + + + + ), + }); + + apiMeData?.username && + tabs.splice(2, 0, { + id: "starred-themes", + title: "Starred Themes", + content: ( + + + + ), + }); + return (
- setCurrentTab(tab)} - tabs={[ - { - id: "bpm-themes", - title: "BPM Themes", - content: ( - - - - ), - }, - { - id: "desktop-themes", - title: "Desktop Themes", - content: ( - - - - ), - }, - ]} - > + setCurrentTab(tab)} tabs={tabs}>
); } diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 52f6475..e6549d5 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -6,6 +6,10 @@ export const styles = ` /* THAT IS NEEDED FOR STATIC CLASS INJECTION */ /* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */ +/* +MARK: TAILWIND +*/ + .flex { display: flex !important; } @@ -18,6 +22,10 @@ export const styles = ` flex-wrap: wrap !important; } +.flex-1 { + flex: 1 1 0% !important; +} + .gap-1 { gap: 0.25rem !important; } @@ -93,7 +101,9 @@ export const styles = ` transform: translate(-50%, -50%) !important; } -/* Fullscreen Routes */ +/* +MARK: Fullscreen Routes +*/ .cl_fullscreenroute_container { margin-top: 40px !important; @@ -101,7 +111,15 @@ export const styles = ` background: #0e141b !important; } -/* TitleView */ +.cl_fullscrenroute_title { + font-size: 2rem !important; + font-weight: bold !important; +} + +/* +MARK: TitleView +*/ + .cl-title-view-button { height: 28px !important; @@ -131,7 +149,10 @@ export const styles = ` animation: onboardingButton 1s infinite ease-in-out !important; } -/* QAM Tab */ +/* +MARK: QAM Tab +*/ + .cl-qam-collapse-button-container > div > div > div > div > button { height: 10px !important; @@ -190,7 +211,10 @@ export const styles = ` white-space: nowrap !important; } -/* Theme Store */ +/* +MARK: Store +*/ + .cl-store-filter-field-container { display: flex !important; @@ -249,6 +273,7 @@ export const styles = ` /* The variables should be injected wherever needed */ /* This module actually is based on font-size, so EM makes sense over REM */ +/* TODO: For some reason I made half of these classes with dashes and the other half with underscores, standardize it!!! */ .cl_storeitem_notifbubble { position: absolute !important; background: linear-gradient(135deg, #fca904 50%, transparent 51%) !important; @@ -342,7 +367,10 @@ export const styles = ` font-size: 0.75em !important; } -/* Expanded View */ +/* +MARK: Expanded View +*/ + @keyframes cl_spin { to { @@ -505,4 +533,16 @@ export const styles = ` min-width: 1rem !important; position: relative; } + +/* +MARK: Account Page +*/ + +.cl_accountpage_actionbutton { + max-width: 30% !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + gap: 0.5rem !important; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index af464cf..00ab4b5 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -3,6 +3,10 @@ /* THAT IS NEEDED FOR STATIC CLASS INJECTION */ /* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */ +/* +MARK: TAILWIND +*/ + .flex { display: flex !important; } @@ -15,6 +19,10 @@ flex-wrap: wrap !important; } +.flex-1 { + flex: 1 1 0% !important; +} + .gap-1 { gap: 0.25rem !important; } @@ -90,7 +98,9 @@ transform: translate(-50%, -50%) !important; } -/* Fullscreen Routes */ +/* +MARK: Fullscreen Routes +*/ .cl_fullscreenroute_container { margin-top: 40px !important; @@ -98,7 +108,15 @@ background: #0e141b !important; } -/* TitleView */ +.cl_fullscrenroute_title { + font-size: 2rem !important; + font-weight: bold !important; +} + +/* +MARK: TitleView +*/ + .cl-title-view-button { height: 28px !important; @@ -128,7 +146,10 @@ animation: onboardingButton 1s infinite ease-in-out !important; } -/* QAM Tab */ +/* +MARK: QAM Tab +*/ + .cl-qam-collapse-button-container > div > div > div > div > button { height: 10px !important; @@ -187,7 +208,10 @@ white-space: nowrap !important; } -/* Theme Store */ +/* +MARK: Store +*/ + .cl-store-filter-field-container { display: flex !important; @@ -246,6 +270,7 @@ /* The variables should be injected wherever needed */ /* This module actually is based on font-size, so EM makes sense over REM */ +/* TODO: For some reason I made half of these classes with dashes and the other half with underscores, standardize it!!! */ .cl_storeitem_notifbubble { position: absolute !important; background: linear-gradient(135deg, #fca904 50%, transparent 51%) !important; @@ -339,7 +364,10 @@ font-size: 0.75em !important; } -/* Expanded View */ +/* +MARK: Expanded View +*/ + @keyframes cl_spin { to { @@ -501,4 +529,16 @@ width: 1rem !important; min-width: 1rem !important; position: relative; +} + +/* +MARK: Account Page +*/ + +.cl_accountpage_actionbutton { + max-width: 30% !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + gap: 0.5rem !important; } \ No newline at end of file From ad201bb109d8d3a6548cf60003c60c311dc97a64 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:38:03 -0700 Subject: [PATCH 39/53] finish author view modal --- .../author-view-modal/AuthorViewModal.tsx | 75 +++++++++++ .../author-view-modal/SupporterIcon.tsx | 48 +++++++ .../modals/author-view-modal/index.ts | 1 + src/lib/components/modals/index.ts | 1 + src/lib/primitives/Modal.tsx | 2 +- .../ExpandedViewScrollingSection.tsx | 6 +- .../theme-store/components/ThemeCard.tsx | 122 +++++++++--------- .../ThemeCardCSSVariableProvider.tsx | 6 +- src/styles/styles-as-string.ts | 28 +++- src/styles/styles.css | 23 ++++ 10 files changed, 241 insertions(+), 71 deletions(-) create mode 100644 src/lib/components/modals/author-view-modal/AuthorViewModal.tsx create mode 100644 src/lib/components/modals/author-view-modal/SupporterIcon.tsx create mode 100644 src/lib/components/modals/author-view-modal/index.ts diff --git a/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx b/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx new file mode 100644 index 0000000..4cfcd49 --- /dev/null +++ b/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx @@ -0,0 +1,75 @@ +import { ConfirmModal, Modal } from "../../../primitives"; +import { PartialCSSThemeInfo, ThemeQueryResponse, UserInfo } from "@/types"; +import { useEffect, useRef, useState } from "react"; +import { useCSSLoaderAction } from "@/backend"; +import { ThemeCardCSSVariableProvider } from "@/modules/theme-store/components"; +import { SupporterIcon } from "./SupporterIcon"; +import { ImSpinner5 } from "react-icons/im"; +import { Focusable } from "@decky/ui"; +import { ThemeCard } from "@/modules/theme-store/components/ThemeCard"; + +export function AuthorViewModal({ + closeModal, + authorData, +}: { + closeModal?: () => void; + authorData: UserInfo; +}) { + const apiFetch = useCSSLoaderAction("apiFetch"); + + const [loaded, setLoaded] = useState(false); + const [themes, setThemes] = useState([]); + + const firstThemeRef = useRef(null); + + async function fetchThemeData() { + const data: ThemeQueryResponse = await apiFetch( + `/users/${authorData.id}/themes?page=1&perPage=50&filters=CSS&order=Most Downloaded` + ); + if (data?.total && data.total > 0) { + setThemes(data.items); + setLoaded(true); + } + } + useEffect(() => { + fetchThemeData(); + }, []); + + useEffect(() => { + if (firstThemeRef?.current) { + setTimeout(() => { + firstThemeRef?.current?.focus(); + }, 10); + } + }, [loaded]); + + return ( + + + {loaded ? ( + <> +
+ + {authorData.username} +
+ +
+
+ + {themes.map((e, i) => { + return ( + + ); + })} + + + ) : ( +
+ + {/* Re-using expanded view's loading class */} + Loading +
+ )} +
+ ); +} diff --git a/src/lib/components/modals/author-view-modal/SupporterIcon.tsx b/src/lib/components/modals/author-view-modal/SupporterIcon.tsx new file mode 100644 index 0000000..1ce6478 --- /dev/null +++ b/src/lib/components/modals/author-view-modal/SupporterIcon.tsx @@ -0,0 +1,48 @@ +import { UserInfo } from "@/types"; +import { RiMedalFill } from "react-icons/ri"; + +// Not even gonna try and refactor this +export function SupporterIcon({ authorData }: { authorData: UserInfo }) { + const randId = Math.trunc(Math.random() * 69420); + return ( + <> + {authorData?.premiumTier && authorData?.premiumTier !== "None" && ( +
+ + + + + + + + {`Tier ${authorData?.premiumTier?.slice(-1)} Patreon Supporter`} +
+ )} + + ); +} diff --git a/src/lib/components/modals/author-view-modal/index.ts b/src/lib/components/modals/author-view-modal/index.ts new file mode 100644 index 0000000..b0b4256 --- /dev/null +++ b/src/lib/components/modals/author-view-modal/index.ts @@ -0,0 +1 @@ +export * from "./AuthorViewModal"; diff --git a/src/lib/components/modals/index.ts b/src/lib/components/modals/index.ts index 313e35d..526b1eb 100644 --- a/src/lib/components/modals/index.ts +++ b/src/lib/components/modals/index.ts @@ -1,2 +1,3 @@ export * from "./nav-patch-info-modal"; export * from "./optional-deps-modal"; +export * from "./author-view-modal"; diff --git a/src/lib/primitives/Modal.tsx b/src/lib/primitives/Modal.tsx index d49a0af..bc5b3eb 100644 --- a/src/lib/primitives/Modal.tsx +++ b/src/lib/primitives/Modal.tsx @@ -14,7 +14,7 @@ export function Modal({
-

{title}

+ {title &&

{title}

} {children}
diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx index cc6090a..ac325c5 100644 --- a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -1,7 +1,8 @@ -import { DialogButton, Focusable, Navigation, ScrollPanelGroup } from "@decky/ui"; +import { DialogButton, Focusable, Navigation, ScrollPanelGroup, showModal } from "@decky/ui"; import { useExpandedViewAction, useExpandedViewValue } from "../context"; import { ExpandedViewImageContainer } from "./ExpandedViewImageContainer"; import { useThemeBrowserSharedAction } from "@/modules/theme-store/context"; +import { AuthorViewModal } from "@/lib"; export function ExpandedViewScrollingSection() { const data = useExpandedViewValue("data"); @@ -37,8 +38,7 @@ export function ExpandedViewScrollingSection() { onOKActionDescription="View Profile" focusClassName="gpfocuswithin" onActivate={() => { - // TODO: MODAL - // showModal(); + showModal(); }} > By {data.specifiedAuthor} diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx index c50da9c..e0f2854 100644 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -11,6 +11,7 @@ import { useExpandedViewAction } from "@/modules/expanded-view"; interface ThemeCardProps { theme: PartialCSSThemeInfo; size?: ColumnNumbers; + onClick?: () => void; } const cardWidth = { @@ -19,67 +20,70 @@ const cardWidth = { 3: 260, }; -export const ThemeCard = forwardRef(({ theme, size }, ref) => { - const apiUrl = useCSSLoaderValue("apiUrl"); - const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); - const cols = size ?? browserCardSize; - const installStatus = useThemeInstallState(theme); +export const ThemeCard = forwardRef( + ({ theme, size, onClick }, ref) => { + const apiUrl = useCSSLoaderValue("apiUrl"); + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); + const cols = size ?? browserCardSize; + const installStatus = useThemeInstallState(theme); - const openTheme = useExpandedViewAction("openTheme"); + const openTheme = useExpandedViewAction("openTheme"); - const imageUrl = - theme?.images[0]?.id && theme.images[0].id !== "MISSING" - ? `${apiUrl}/blobs/${theme.images[0].id}` - : `https://share.deckthemes.com/cssplaceholder.png`; + const imageUrl = + theme?.images[0]?.id && theme.images[0].id !== "MISSING" + ? `${apiUrl}/blobs/${theme.images[0].id}` + : `https://share.deckthemes.com/cssplaceholder.png`; - return ( -
- {installStatus === "outdated" && ( -
- -
- )} - { - openTheme(theme.id); - }} - > -
- -
-
-
- - - {shortenNumber(theme.download.downloadCount) ?? theme.download.downloadCount} - -
-
- - {shortenNumber(theme.starCount) ?? theme.starCount} -
-
- - {theme.target} + return ( +
+ {installStatus === "outdated" && ( +
+ +
+ )} + { + onClick?.(); + openTheme(theme.id); + }} + > +
+ +
+
+
+ + + {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} -
-
-
- ); -}); +
+ {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 index 3fb7dbd..52f1d1f 100644 --- a/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx +++ b/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx @@ -1,8 +1,8 @@ import { themeCardStylesGenerator } from "@/styles"; -import { useThemeBrowserSharedValue } from "../context"; +import { ColumnNumbers, useThemeBrowserSharedValue } from "../context"; -export function ThemeCardCSSVariableProvider() { +export function ThemeCardCSSVariableProvider({ cardSize }: { cardSize?: ColumnNumbers }) { const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); - return ; + return ; } diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index e6549d5..38d6c6a 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -1,11 +1,6 @@ import { gamepadDialogClasses } from "@decky/ui"; export const styles = ` -/* THIS FILE IS NOT USED IN BUILD */ -/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO styles-as-string.ts */ -/* THAT IS NEEDED FOR STATIC CLASS INJECTION */ -/* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */ - /* MARK: TAILWIND */ @@ -545,4 +540,27 @@ MARK: Account Page justify-content: center !important; gap: 0.5rem !important; } + +/* +MARK: Author View Modal +*/ + +.cl_authorview_avatar { + margin-right: 0.25em !important; +} + +.cl_authorview_username { + font-size: 2em; + font-weight: bold; +} + +.cl_authorview_authorcontainer { + display: flex !important; + margin-bottom: 1em !important; + align-items: center !important; +} +.cl_authorview_supportericoncontainer { + margin-left: auto !important; + transform: translateY(2px) !important; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index 00ab4b5..d25897d 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -541,4 +541,27 @@ MARK: Account Page align-items: center !important; justify-content: center !important; gap: 0.5rem !important; +} + +/* +MARK: Author View Modal +*/ + +.cl_authorview_avatar { + margin-right: 0.25em !important; +} + +.cl_authorview_username { + font-size: 2em; + font-weight: bold; +} + +.cl_authorview_authorcontainer { + display: flex !important; + margin-bottom: 1em !important; + align-items: center !important; +} +.cl_authorview_supportericoncontainer { + margin-left: auto !important; + transform: translateY(2px) !important; } \ No newline at end of file From 861f81a43e0880f45815f6beec3c992ed5a49dab Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Fri, 27 Dec 2024 14:27:54 -0700 Subject: [PATCH 40/53] simplify themecard props --- src/lib/components/theme-card/ThemeCard.tsx | 89 +++++++++++++ .../ThemeCardCSSVariableProvider.tsx | 8 ++ .../theme-store/components/ThemeCard.tsx | 123 ++++++++---------- src/styles/styles-as-string.ts | 2 + src/styles/styles.css | 2 + 5 files changed, 155 insertions(+), 69 deletions(-) create mode 100644 src/lib/components/theme-card/ThemeCard.tsx create mode 100644 src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx diff --git a/src/lib/components/theme-card/ThemeCard.tsx b/src/lib/components/theme-card/ThemeCard.tsx new file mode 100644 index 0000000..e0f2854 --- /dev/null +++ b/src/lib/components/theme-card/ThemeCard.tsx @@ -0,0 +1,89 @@ +import { PartialCSSThemeInfo } from "@/types"; +import { ColumnNumbers, useThemeBrowserSharedValue, useThemeBrowserStoreValue } from "../context"; +import { forwardRef } from "react"; +import { shortenNumber, useThemeInstallState } from "@/lib"; +import { useCSSLoaderValue } from "@/backend"; +import { AiOutlineDownload } from "react-icons/ai"; +import { Focusable } from "@decky/ui"; +import { FaBullseye, FaDownload, FaStar } from "react-icons/fa6"; +import { useExpandedViewAction } from "@/modules/expanded-view"; + +interface ThemeCardProps { + theme: PartialCSSThemeInfo; + size?: ColumnNumbers; + onClick?: () => void; +} + +const cardWidth = { + 5: 152, + 4: 195, + 3: 260, +}; + +export const ThemeCard = forwardRef( + ({ theme, size, onClick }, ref) => { + const apiUrl = useCSSLoaderValue("apiUrl"); + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); + const cols = size ?? browserCardSize; + const installStatus = useThemeInstallState(theme); + + const openTheme = useExpandedViewAction("openTheme"); + + const imageUrl = + theme?.images[0]?.id && theme.images[0].id !== "MISSING" + ? `${apiUrl}/blobs/${theme.images[0].id}` + : `https://share.deckthemes.com/cssplaceholder.png`; + + return ( +
+ {installStatus === "outdated" && ( +
+ +
+ )} + { + onClick?.(); + openTheme(theme.id); + }} + > +
+ +
+
+
+ + + {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/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx b/src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx new file mode 100644 index 0000000..52f1d1f --- /dev/null +++ b/src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx @@ -0,0 +1,8 @@ +import { themeCardStylesGenerator } from "@/styles"; +import { ColumnNumbers, useThemeBrowserSharedValue } from "../context"; + +export function ThemeCardCSSVariableProvider({ cardSize }: { cardSize?: ColumnNumbers }) { + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); + + return ; +} diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx index e0f2854..e00e798 100644 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ b/src/modules/theme-store/components/ThemeCard.tsx @@ -1,5 +1,5 @@ import { PartialCSSThemeInfo } from "@/types"; -import { ColumnNumbers, useThemeBrowserSharedValue, useThemeBrowserStoreValue } from "../context"; +import { ColumnNumbers } from "../context"; import { forwardRef } from "react"; import { shortenNumber, useThemeInstallState } from "@/lib"; import { useCSSLoaderValue } from "@/backend"; @@ -14,76 +14,61 @@ interface ThemeCardProps { onClick?: () => void; } -const cardWidth = { - 5: 152, - 4: 195, - 3: 260, -}; +export const ThemeCard = forwardRef(({ theme, onClick }, ref) => { + const apiUrl = useCSSLoaderValue("apiUrl"); + const installStatus = useThemeInstallState(theme); -export const ThemeCard = forwardRef( - ({ theme, size, onClick }, ref) => { - const apiUrl = useCSSLoaderValue("apiUrl"); - const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); - const cols = size ?? browserCardSize; - const installStatus = useThemeInstallState(theme); + const openTheme = useExpandedViewAction("openTheme"); - const openTheme = useExpandedViewAction("openTheme"); + const imageUrl = + theme?.images[0]?.id && theme.images[0].id !== "MISSING" + ? `${apiUrl}/blobs/${theme.images[0].id}` + : `https://share.deckthemes.com/cssplaceholder.png`; - const imageUrl = - theme?.images[0]?.id && theme.images[0].id !== "MISSING" - ? `${apiUrl}/blobs/${theme.images[0].id}` - : `https://share.deckthemes.com/cssplaceholder.png`; - - return ( -
- {installStatus === "outdated" && ( -
- -
- )} - { - onClick?.(); - openTheme(theme.id); - }} - > -
- -
-
-
- - - {shortenNumber(theme.download.downloadCount) ?? theme.download.downloadCount} - -
-
- - {shortenNumber(theme.starCount) ?? theme.starCount} -
-
- - {theme.target} -
+ return ( +
+ {installStatus === "outdated" && ( +
+ +
+ )} + { + onClick?.(); + openTheme(theme.id); + }} + > +
+ +
+
+
+ + + {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} -
- -
- ); - } -); +
+
+ {theme.displayName} + + {theme.version} - Last Updated {new Date(theme.updated).toLocaleDateString()} + + By {theme.specifiedAuthor} +
+
+
+ ); +}); diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 38d6c6a..9de514f 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -326,6 +326,8 @@ MARK: Store font-size: var(--cl-storeitem-fontsize) !important; } .cl_storeitem_image { + width: 100%; + height: 100%; object-fit: cover !important; transition-property: filter,transform !important; transition-duration: 0.32s !important; diff --git a/src/styles/styles.css b/src/styles/styles.css index d25897d..5ed643d 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -328,6 +328,8 @@ MARK: Store font-size: var(--cl-storeitem-fontsize) !important; } .cl_storeitem_image { + width: 100%; + height: 100%; object-fit: cover !important; transition-property: filter,transform !important; transition-duration: 0.32s !important; From 321e9e157aeddac895f31032909de0389e176b0a Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Fri, 27 Dec 2024 14:37:25 -0700 Subject: [PATCH 41/53] fix require cycles and extract themecard --- src/lib/components/index.ts | 1 + .../author-view-modal/AuthorViewModal.tsx | 20 ++- src/lib/components/theme-card/ThemeCard.tsx | 125 ++++++++---------- .../ThemeCardCSSVariableProvider.tsx | 7 +- src/lib/components/theme-card/index.ts | 2 + .../components/ThemeBrowserPage.tsx | 6 +- .../theme-store/components/ThemeCard.tsx | 74 ----------- .../ThemeCardCSSVariableProvider.tsx | 8 -- src/modules/theme-store/components/index.ts | 1 - .../context/ThemeBrowserSharedStore.tsx | 3 +- .../theme-store/pages/ThemeStoreRouter.tsx | 10 +- src/styles/theme-card-styles-generator.ts | 2 +- 12 files changed, 88 insertions(+), 171 deletions(-) create mode 100644 src/lib/components/theme-card/index.ts delete mode 100644 src/modules/theme-store/components/ThemeCard.tsx delete mode 100644 src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 10bfaa9..5b8d864 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -3,3 +3,4 @@ export * from "./motd-display"; export * from "./preset-selection-dropdown"; export * from "./theme-patch"; export * from "./modals"; +export * from "./theme-card"; diff --git a/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx b/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx index 4cfcd49..c2540e1 100644 --- a/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx +++ b/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx @@ -1,12 +1,13 @@ -import { ConfirmModal, Modal } from "../../../primitives"; +import { Modal } from "../../../primitives"; import { PartialCSSThemeInfo, ThemeQueryResponse, UserInfo } from "@/types"; import { useEffect, useRef, useState } from "react"; import { useCSSLoaderAction } from "@/backend"; -import { ThemeCardCSSVariableProvider } from "@/modules/theme-store/components"; import { SupporterIcon } from "./SupporterIcon"; import { ImSpinner5 } from "react-icons/im"; import { Focusable } from "@decky/ui"; -import { ThemeCard } from "@/modules/theme-store/components/ThemeCard"; +import { ThemeCard, ThemeCardCSSVariableProvider } from "../../theme-card"; +// Hardcoded to prevent require cycle +import { useExpandedViewAction } from "@/modules/expanded-view/context"; export function AuthorViewModal({ closeModal, @@ -20,6 +21,8 @@ export function AuthorViewModal({ const [loaded, setLoaded] = useState(false); const [themes, setThemes] = useState([]); + const openTheme = useExpandedViewAction("openTheme"); + const firstThemeRef = useRef(null); async function fetchThemeData() { @@ -56,9 +59,16 @@ export function AuthorViewModal({
- {themes.map((e, i) => { + {themes.map((theme, i) => { return ( - + { + closeModal?.(); + openTheme(theme.id); + }} + /> ); })} diff --git a/src/lib/components/theme-card/ThemeCard.tsx b/src/lib/components/theme-card/ThemeCard.tsx index e0f2854..584b047 100644 --- a/src/lib/components/theme-card/ThemeCard.tsx +++ b/src/lib/components/theme-card/ThemeCard.tsx @@ -1,89 +1,68 @@ import { PartialCSSThemeInfo } from "@/types"; -import { ColumnNumbers, useThemeBrowserSharedValue, useThemeBrowserStoreValue } from "../context"; import { forwardRef } from "react"; -import { shortenNumber, useThemeInstallState } from "@/lib"; import { useCSSLoaderValue } from "@/backend"; import { AiOutlineDownload } from "react-icons/ai"; import { Focusable } from "@decky/ui"; import { FaBullseye, FaDownload, FaStar } from "react-icons/fa6"; -import { useExpandedViewAction } from "@/modules/expanded-view"; +import { useThemeInstallState } from "../../hooks"; +// Hard-coded path to prevent require cycle +import { shortenNumber } from "../../utils/shorten-number"; interface ThemeCardProps { theme: PartialCSSThemeInfo; - size?: ColumnNumbers; onClick?: () => void; } -const cardWidth = { - 5: 152, - 4: 195, - 3: 260, -}; +export const ThemeCard = forwardRef(({ theme, onClick }, ref) => { + const apiUrl = useCSSLoaderValue("apiUrl"); + const installStatus = useThemeInstallState(theme); -export const ThemeCard = forwardRef( - ({ theme, size, onClick }, ref) => { - const apiUrl = useCSSLoaderValue("apiUrl"); - const browserCardSize = useThemeBrowserSharedValue("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`; - const openTheme = useExpandedViewAction("openTheme"); - - const imageUrl = - theme?.images[0]?.id && theme.images[0].id !== "MISSING" - ? `${apiUrl}/blobs/${theme.images[0].id}` - : `https://share.deckthemes.com/cssplaceholder.png`; - - return ( -
- {installStatus === "outdated" && ( -
- -
- )} - { - onClick?.(); - openTheme(theme.id); - }} - > -
- -
-
-
- - - {shortenNumber(theme.download.downloadCount) ?? theme.download.downloadCount} - -
-
- - {shortenNumber(theme.starCount) ?? theme.starCount} -
-
- - {theme.target} -
+ return ( +
+ {installStatus === "outdated" && ( +
+ +
+ )} + +
+ +
+
+
+ + + {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} -
- -
- ); - } -); +
+
+ {theme.displayName} + + {theme.version} - Last Updated {new Date(theme.updated).toLocaleDateString()} + + By {theme.specifiedAuthor} +
+
+
+ ); +}); diff --git a/src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx b/src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx index 52f1d1f..6a32cc5 100644 --- a/src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx +++ b/src/lib/components/theme-card/ThemeCardCSSVariableProvider.tsx @@ -1,8 +1,7 @@ import { themeCardStylesGenerator } from "@/styles"; -import { ColumnNumbers, useThemeBrowserSharedValue } from "../context"; -export function ThemeCardCSSVariableProvider({ cardSize }: { cardSize?: ColumnNumbers }) { - const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); +export type ColumnNumbers = 3 | 4 | 5; - return ; +export function ThemeCardCSSVariableProvider({ cardSize }: { cardSize: ColumnNumbers }) { + return ; } diff --git a/src/lib/components/theme-card/index.ts b/src/lib/components/theme-card/index.ts new file mode 100644 index 0000000..983ed59 --- /dev/null +++ b/src/lib/components/theme-card/index.ts @@ -0,0 +1,2 @@ +export * from "./ThemeCard"; +export * from "./ThemeCardCSSVariableProvider"; diff --git a/src/modules/theme-store/components/ThemeBrowserPage.tsx b/src/modules/theme-store/components/ThemeBrowserPage.tsx index 9d5e047..c187f53 100644 --- a/src/modules/theme-store/components/ThemeBrowserPage.tsx +++ b/src/modules/theme-store/components/ThemeBrowserPage.tsx @@ -2,10 +2,11 @@ import { Focusable } from "@decky/ui"; import { useThemeBrowserStoreAction, useThemeBrowserStoreValue } from "../context"; import { BrowserSearchFields } from "./BrowserSearchFields"; import { useCSSLoaderValue } from "@/backend"; -import { ThemeCard } from "./ThemeCard"; import { useEffect, useRef } from "react"; import { ImSpinner5 } from "react-icons/im"; import { LoadMoreButton } from "./LoadMoreButton"; +import { useExpandedViewAction } from "@/modules/expanded-view"; +import { ThemeCard } from "@/lib"; export function ThemeBrowserPage() { const initializeStore = useThemeBrowserStoreAction("initializeStore"); @@ -14,6 +15,8 @@ export function ThemeBrowserPage() { const indexToSnapToOnLoad = useThemeBrowserStoreValue("indexToSnapToOnLoad"); const backendVersion = useCSSLoaderValue("backendVersion"); + const openTheme = useExpandedViewAction("openTheme"); + const endOfPageRef = useRef(null); const firstCardRef = useRef(null); @@ -42,6 +45,7 @@ export function ThemeBrowserPage() { .filter((theme) => theme.manifestVersion <= backendVersion) .map((theme, index) => ( openTheme(theme.id)} ref={ index === indexToSnapToOnLoad ? endOfPageRef diff --git a/src/modules/theme-store/components/ThemeCard.tsx b/src/modules/theme-store/components/ThemeCard.tsx deleted file mode 100644 index e00e798..0000000 --- a/src/modules/theme-store/components/ThemeCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { PartialCSSThemeInfo } from "@/types"; -import { ColumnNumbers } from "../context"; -import { forwardRef } from "react"; -import { shortenNumber, useThemeInstallState } from "@/lib"; -import { useCSSLoaderValue } from "@/backend"; -import { AiOutlineDownload } from "react-icons/ai"; -import { Focusable } from "@decky/ui"; -import { FaBullseye, FaDownload, FaStar } from "react-icons/fa6"; -import { useExpandedViewAction } from "@/modules/expanded-view"; - -interface ThemeCardProps { - theme: PartialCSSThemeInfo; - size?: ColumnNumbers; - onClick?: () => void; -} - -export const ThemeCard = forwardRef(({ theme, onClick }, ref) => { - const apiUrl = useCSSLoaderValue("apiUrl"); - const installStatus = useThemeInstallState(theme); - - const openTheme = useExpandedViewAction("openTheme"); - - const imageUrl = - theme?.images[0]?.id && theme.images[0].id !== "MISSING" - ? `${apiUrl}/blobs/${theme.images[0].id}` - : `https://share.deckthemes.com/cssplaceholder.png`; - - return ( -
- {installStatus === "outdated" && ( -
- -
- )} - { - onClick?.(); - openTheme(theme.id); - }} - > -
- -
-
-
- - - {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 deleted file mode 100644 index 52f1d1f..0000000 --- a/src/modules/theme-store/components/ThemeCardCSSVariableProvider.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { themeCardStylesGenerator } from "@/styles"; -import { ColumnNumbers, useThemeBrowserSharedValue } from "../context"; - -export function ThemeCardCSSVariableProvider({ cardSize }: { cardSize?: ColumnNumbers }) { - const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); - - return ; -} diff --git a/src/modules/theme-store/components/index.ts b/src/modules/theme-store/components/index.ts index be45aeb..1ae67d4 100644 --- a/src/modules/theme-store/components/index.ts +++ b/src/modules/theme-store/components/index.ts @@ -1,2 +1 @@ -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 6c4236c..de146b0 100644 --- a/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx +++ b/src/modules/theme-store/context/ThemeBrowserSharedStore.tsx @@ -1,8 +1,7 @@ // This is for things that are shared across the entire Theme Browser page and all tabs. import { createStore, useStore } from "zustand"; - -export type ColumnNumbers = 3 | 4 | 5; +import { ColumnNumbers } from "../../../lib/components/theme-card"; interface ThemeBrowserSharedStoreValues { browserCardSize: ColumnNumbers; diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx index 96fde7d..7e8971a 100644 --- a/src/modules/theme-store/pages/ThemeStoreRouter.tsx +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -1,5 +1,5 @@ import { Tabs } from "@decky/ui"; -import { ThemeBrowserPage, ThemeCardCSSVariableProvider } from "../components"; +import { ThemeBrowserPage } from "../components"; import { ThemeBrowserStoreProvider, useThemeBrowserSharedAction, @@ -8,6 +8,7 @@ import { import { AccountPage } from "./AccountPage"; import { useCSSLoaderValue } from "@/backend"; import { Permissions } from "@/types"; +import { ThemeCardCSSVariableProvider } from "@/lib"; // TODO: Make the tab definition a constant so that it isn't re-generated every page load @@ -87,8 +88,13 @@ export function ThemeStoreRouter() { return (
- setCurrentTab(tab)} tabs={tabs}>
); } + +function BrowserCardSizeVariableProvider() { + const browserCardSize = useThemeBrowserSharedValue("browserCardSize"); + + return ; +} diff --git a/src/styles/theme-card-styles-generator.ts b/src/styles/theme-card-styles-generator.ts index d448704..f20de58 100644 --- a/src/styles/theme-card-styles-generator.ts +++ b/src/styles/theme-card-styles-generator.ts @@ -1,4 +1,4 @@ -import { ColumnNumbers } from "@/modules/theme-store/context"; +import { ColumnNumbers } from "@/lib"; export function themeCardStylesGenerator(size: ColumnNumbers) { return ` From 600d22bf0717f12650d0d903e88d24bc7c88ef9a Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:14:11 -0700 Subject: [PATCH 42/53] refactor --- .../modals/author-view-modal/AuthorViewModal.tsx | 8 +++----- .../components/ExpandedViewScrollingSection.tsx | 8 +++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx b/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx index c2540e1..b68cca9 100644 --- a/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx +++ b/src/lib/components/modals/author-view-modal/AuthorViewModal.tsx @@ -6,23 +6,21 @@ import { SupporterIcon } from "./SupporterIcon"; import { ImSpinner5 } from "react-icons/im"; import { Focusable } from "@decky/ui"; import { ThemeCard, ThemeCardCSSVariableProvider } from "../../theme-card"; -// Hardcoded to prevent require cycle -import { useExpandedViewAction } from "@/modules/expanded-view/context"; export function AuthorViewModal({ closeModal, authorData, + onThemeClick, }: { closeModal?: () => void; authorData: UserInfo; + onThemeClick?: (themeId: string) => void; }) { const apiFetch = useCSSLoaderAction("apiFetch"); const [loaded, setLoaded] = useState(false); const [themes, setThemes] = useState([]); - const openTheme = useExpandedViewAction("openTheme"); - const firstThemeRef = useRef(null); async function fetchThemeData() { @@ -66,7 +64,7 @@ export function AuthorViewModal({ theme={theme} onClick={() => { closeModal?.(); - openTheme(theme.id); + onThemeClick?.(theme.id); }} /> ); diff --git a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx index ac325c5..446ce01 100644 --- a/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewScrollingSection.tsx @@ -7,6 +7,7 @@ import { AuthorViewModal } from "@/lib"; export function ExpandedViewScrollingSection() { const data = useExpandedViewValue("data"); const close = useExpandedViewAction("close"); + const openTheme = useExpandedViewAction("openTheme"); const setTargetOverride = useThemeBrowserSharedAction("setTargetOverride"); @@ -38,7 +39,12 @@ export function ExpandedViewScrollingSection() { onOKActionDescription="View Profile" focusClassName="gpfocuswithin" onActivate={() => { - showModal(); + showModal( + openTheme(themeId)} + /> + ); }} > By {data.specifiedAuthor} From 10b95179f25d53a42741c5e279f0961f80b3e4cf Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:14:47 -0700 Subject: [PATCH 43/53] add in theme settings modal --- src/backend/services/backend-service.ts | 4 +- src/backend/state/theme-store.ts | 29 ++++++++ .../DeleteConfirmationModal.tsx | 31 ++++++++ .../modals/delete-confirmation-modal/index.ts | 1 + src/lib/components/modals/index.ts | 2 + .../ThemeSettingsModal.tsx | 62 ++++++++++++++++ .../ThemeSettingsModalActionButtons.tsx | 73 +++++++++++++++++++ .../modals/theme-settings-modal/index.ts | 1 + src/lib/components/theme-patch/ThemePatch.tsx | 12 +-- .../components/ExpandedViewButtonsSection.tsx | 17 +---- src/modules/theme-store/pages/AccountPage.tsx | 2 +- src/styles/styles.css | 25 +++++-- 12 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx create mode 100644 src/lib/components/modals/delete-confirmation-modal/index.ts create mode 100644 src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx create mode 100644 src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx create mode 100644 src/lib/components/modals/theme-settings-modal/index.ts diff --git a/src/backend/services/backend-service.ts b/src/backend/services/backend-service.ts index 003f551..8118ade 100644 --- a/src/backend/services/backend-service.ts +++ b/src/backend/services/backend-service.ts @@ -86,7 +86,9 @@ export class Backend { async getBackendVersion() { return await Backend.repository.call<[], number>("get_backend_version", []); } - + async deleteTheme(themeName: string) { + return await Backend.repository.call<[string], void>("delete_theme", [themeName]); + } async fetch(url: string, request: RequestInit = {}) { return Backend.repository.fetch(url, request); } diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 5a6c92f..8a9be20 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -75,6 +75,9 @@ export interface CSSLoaderStateActions { enableDeps?: boolean, enableDepValues?: boolean ) => Promise; + pinTheme: (themeId: string) => Promise; + unpinTheme: (themeId: string) => Promise; + deleteTheme: (themeId: string, refreshAfter?: boolean) => Promise; } export interface ICSSLoaderState extends CSSLoaderStateValues, CSSLoaderStateActions {} @@ -487,5 +490,31 @@ export const createCSSLoaderStore = (backend: Backend) => } } catch (error) {} }, + pinTheme: async (themeId: string) => { + try { + const { unpinnedThemes } = get(); + const unpinnedClone = unpinnedThemes.filter((e) => e !== themeId); + set({ unpinnedThemes: unpinnedClone }); + backend.storeWrite("unpinnedThemes", JSON.stringify(unpinnedClone)); + } catch (error) {} + }, + unpinTheme: async (themeId: string) => { + try { + const { unpinnedThemes } = get(); + const unpinnedClone = [...unpinnedThemes, themeId]; + set({ unpinnedThemes: unpinnedClone }); + backend.storeWrite("unpinnedThemes", JSON.stringify(unpinnedClone)); + } catch (error) {} + }, + deleteTheme: async (themeId: string, refreshAfter: boolean = true) => { + try { + const { themes } = get(); + // The python defs say theme name, just gonna assume it's this and not ID + const themeName = themes.find((e) => e.id === themeId)?.name; + if (!themeName) return; + await backend.deleteTheme(themeName); + refreshAfter && (await get().getThemes()); + } catch (error) {} + }, }; }); diff --git a/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx b/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx new file mode 100644 index 0000000..88397e3 --- /dev/null +++ b/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx @@ -0,0 +1,31 @@ +import { useCSSLoaderAction } from "@/backend"; +import { ConfirmModal } from "../../../primitives"; + +export function DeleteConfirmationModal({ + closeModal, + themesToBeDeleted, + onDeleteFinish, +}: { + closeModal?: () => void; + themesToBeDeleted: string[]; + onDeleteFinish?: () => void; +}) { + const deleteTheme = useCSSLoaderAction("deleteTheme"); + async function deleteThemes() { + for (let i = 0; i < themesToBeDeleted.length; i++) { + await deleteTheme(themesToBeDeleted[i], i === themesToBeDeleted.length - 1); + } + onDeleteFinish?.(); + closeModal?.(); + } + + return ( + +
+ Are you sure you want to delete{" "} + {themesToBeDeleted.length === 1 ? `this theme` : `these ${themesToBeDeleted.length} themes`} + ? +
+
+ ); +} diff --git a/src/lib/components/modals/delete-confirmation-modal/index.ts b/src/lib/components/modals/delete-confirmation-modal/index.ts new file mode 100644 index 0000000..a06f077 --- /dev/null +++ b/src/lib/components/modals/delete-confirmation-modal/index.ts @@ -0,0 +1 @@ +export * from "./DeleteConfirmationModal"; diff --git a/src/lib/components/modals/index.ts b/src/lib/components/modals/index.ts index 526b1eb..7a8075e 100644 --- a/src/lib/components/modals/index.ts +++ b/src/lib/components/modals/index.ts @@ -1,3 +1,5 @@ export * from "./nav-patch-info-modal"; export * from "./optional-deps-modal"; export * from "./author-view-modal"; +export * from "./theme-settings-modal"; +export * from "./delete-confirmation-modal"; diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx new file mode 100644 index 0000000..e0f44a3 --- /dev/null +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx @@ -0,0 +1,62 @@ +import { useCSSLoaderValue } from "@/backend"; +import { Modal } from "../../../primitives"; +import { DialogButton, Focusable, Toggle } from "@decky/ui"; +import { toggleThemeWithModals } from "../../../utils"; +import { ThemePatch } from "../../theme-patch"; +import { ThemeSettingsModalActionButtons } from "./ThemeSettingsModalActionButtons"; + +export function ThemeSettingsModal({ + closeModal, + themeId, +}: { + closeModal?: () => void; + themeId: string; +}) { + const themes = useCSSLoaderValue("themes"); + const theme = themes.find((theme) => theme.id === themeId); + + return ( + + {!!theme ? ( + + +
+ {theme.name} + + {theme.version} | {theme.author} + +
+ { + toggleThemeWithModals(theme, checked); + }} + /> +
+ {theme.enabled && theme.patches.length > 0 ? ( + + {theme.patches.map((patch, index) => ( + + ))} + + ) : null} + + Close + + +
+ ) : ( + + Theme Not Found + Go Back + + )} +
+ ); +} diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx new file mode 100644 index 0000000..cf6dd6b --- /dev/null +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx @@ -0,0 +1,73 @@ +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { LocalThemeStatus, Theme } from "@/types"; +import { DialogButton, Focusable, showModal } from "@decky/ui"; +import { FaDownload, FaEye, FaEyeSlash, FaTrash } from "react-icons/fa6"; +import { DeleteConfirmationModal } from "../delete-confirmation-modal"; + +// TODO: Re-add star status to this modal +export function ThemeSettingsModalActionButtons({ + theme, + closeModal, +}: { + theme: Theme; + closeModal?: () => void; +}) { + const isWorking = useCSSLoaderValue("isWorking"); + + // Update Check + const updateStatuses = useCSSLoaderValue("updateStatuses"); + const installTheme = useCSSLoaderAction("installTheme"); + let updateStatus: LocalThemeStatus = "installed"; + const themeArrPlace = updateStatuses.find((f) => f[0] === theme.id); + if (themeArrPlace) updateStatus = themeArrPlace[1]; + const isOutdated = updateStatus === "outdated"; + function handleUpdate() { + void installTheme(theme.id); + } + + // Pinning + const unpinnedThemes = useCSSLoaderValue("unpinnedThemes"); + const pinTheme = useCSSLoaderAction("pinTheme"); + const unpinTheme = useCSSLoaderAction("unpinTheme"); + const isPinned = !unpinnedThemes.includes(theme.id); + function handlePinClick() { + if (isPinned) { + void unpinTheme(theme.id); + } else { + void pinTheme(theme.id); + } + } + + return ( + + {isOutdated && ( + + + Update + + )} + + {isPinned ? : } + + { + showModal( + + ); + }} + > + + + + ); +} diff --git a/src/lib/components/modals/theme-settings-modal/index.ts b/src/lib/components/modals/theme-settings-modal/index.ts new file mode 100644 index 0000000..ae9aacf --- /dev/null +++ b/src/lib/components/modals/theme-settings-modal/index.ts @@ -0,0 +1 @@ +export * from "./ThemeSettingsModal"; diff --git a/src/lib/components/theme-patch/ThemePatch.tsx b/src/lib/components/theme-patch/ThemePatch.tsx index 18c3bc5..154d120 100644 --- a/src/lib/components/theme-patch/ThemePatch.tsx +++ b/src/lib/components/theme-patch/ThemePatch.tsx @@ -4,16 +4,16 @@ import { useState } from "react"; import { DropdownItem, PanelSectionRow, SliderField, ToggleField } from "@decky/ui"; import { ThemePatchComponent } from "./ThemePatchComponent"; -const modal = false; - export function ThemePatch({ patch, shouldHaveBottomSeparator, themeName, + inModal, }: { patch: Patch; shouldHaveBottomSeparator: boolean; themeName: string; + inModal?: boolean; }) { const bottomSeparatorValue = shouldHaveBottomSeparator ? "standard" : "none"; @@ -35,7 +35,7 @@ export function ThemePatch({ {patch.type === "slider" && ( } + label={inModal ? patch.name : } min={0} max={patch.options.length - 1} value={selectedValueIndex} @@ -53,7 +53,7 @@ export function ThemePatch({ {patch.type === "checkbox" && ( } + label={inModal ? patch.name : } checked={patch.value === "Yes"} onChange={(value) => { // TODO: TEST THIS @@ -65,7 +65,7 @@ export function ThemePatch({ {patch.type === "dropdown" && ( } + label={inModal ? patch.name : } menuLabel={patch.name} rgOptions={patch.options.map((option, index) => ({ label: option, data: index }))} selectedOption={selectedValueIndex} @@ -76,7 +76,7 @@ export function ThemePatch({ )} {patch.type === "none" && ( <> - {modal ? ( + {inModal ? ( {patch.name} ) : ( diff --git a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx index ab6f48f..dccddeb 100644 --- a/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx +++ b/src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx @@ -1,7 +1,7 @@ -import { shortenNumber, useThemeInstallState } from "@/lib"; +import { shortenNumber, useThemeInstallState, ThemeSettingsModal } from "@/lib"; import { useExpandedViewAction, useExpandedViewValue } from "../context"; import { FaRegStar, FaStar } from "react-icons/fa"; -import { DialogButton, Focusable } from "@decky/ui"; +import { DialogButton, Focusable, showModal } from "@decky/ui"; import { useEffect, useRef, useState } from "react"; import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; import { ImCog } from "react-icons/im"; @@ -36,8 +36,6 @@ export function ExpandedViewButtonsSection() { } }, [downloadButtonRef, hasBeenFocused]); - console.log("INSTALL STATUS, ", installStatus); - return ( {/* Star */} @@ -93,16 +91,7 @@ export function ExpandedViewButtonsSection() { {(installStatus === "installed" || installStatus === "local") && ( { - // TODO: THEME SETTINGS MODAL - // showModal( - // e.id === fullThemeData.id)?.id || - // // using name here because in submissions id is different - // installedThemes.find((e) => e.name === fullThemeData.name)!.id - // } - // /> - // ); + showModal(); }} className="cl_expandedview_configure_button" > diff --git a/src/modules/theme-store/pages/AccountPage.tsx b/src/modules/theme-store/pages/AccountPage.tsx index 58feca7..1101548 100644 --- a/src/modules/theme-store/pages/AccountPage.tsx +++ b/src/modules/theme-store/pages/AccountPage.tsx @@ -8,7 +8,7 @@ export function AccountPage() { return (
-

{apiFullToken ? "Your Account" : "Log In"}

+

{apiFullToken ? "Your Account" : "Log In"}

{apiFullToken ? : }

Logging in gives you access to star themes, saving them to their own page where you can diff --git a/src/styles/styles.css b/src/styles/styles.css index 5ed643d..3873d2a 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -91,6 +91,10 @@ MARK: TAILWIND font-weight: bold !important; } +.text-xs { + font-size: 0.75rem !important; +} + .absolute-center { position: absolute !important; top: 50% !important; @@ -98,6 +102,11 @@ MARK: TAILWIND transform: translate(-50%, -50%) !important; } +.cl_title { + font-size: 2rem !important; + font-weight: bold !important; +} + /* MARK: Fullscreen Routes */ @@ -108,11 +117,6 @@ MARK: Fullscreen Routes background: #0e141b !important; } -.cl_fullscrenroute_title { - font-size: 2rem !important; - font-weight: bold !important; -} - /* MARK: TitleView */ @@ -566,4 +570,15 @@ MARK: Author View Modal .cl_authorview_supportericoncontainer { margin-left: auto !important; transform: translateY(2px) !important; +} + +/* +MARK: Theme Settings Modal +*/ + +.cl_themesettingsmodal_smallbutton { + width: fit-content !important; + min-width: fit-content !important; + height: fit-content !important; + padding: 10px 12px !important; } \ No newline at end of file From 98f252ceeda5f12164e12ccd6992705af2506b5b Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:14:54 -0700 Subject: [PATCH 44/53] oops --- src/modules/qam-tab-page/components/QamThemeToggle.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/qam-tab-page/components/QamThemeToggle.tsx b/src/modules/qam-tab-page/components/QamThemeToggle.tsx index 0521ffc..c75eb3c 100644 --- a/src/modules/qam-tab-page/components/QamThemeToggle.tsx +++ b/src/modules/qam-tab-page/components/QamThemeToggle.tsx @@ -1,5 +1,5 @@ import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; -import { ThemePatch, toggleThemeWithModals, useForcedRerender } from "@/lib"; +import { ThemePatch, ThemeSettingsModal, toggleThemeWithModals, useForcedRerender } from "@/lib"; import { useEffect, useState } from "react"; import { LocalThemeStatus, Theme } from "@/types"; import { ButtonItem, Focusable, PanelSectionRow, ToggleField, showModal } from "@decky/ui"; @@ -32,8 +32,7 @@ export function QamThemeToggle({ theme }: { theme: Theme }) { onOKActionDescription="Toggle Theme" onOptionsActionDescription="Expand Settings" onOptionsButton={() => { - // @ts-ignore - showModal(); + showModal(); }} onSecondaryActionDescription={isOutdated ? "Update Theme" : undefined} onSecondaryButton={ From 13f1b4f7ef11bb38e9e13e72059c32f982d03dce Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:32:31 -0700 Subject: [PATCH 45/53] fix theme toggling --- src/backend/services/backend-service.ts | 2 +- src/backend/state/theme-store.ts | 13 ++++++++-- src/lib/utils/toggleThemeWithModals.tsx | 4 ++- .../components/QamThemeToggle.tsx | 2 +- src/styles/styles-as-string.ts | 25 +++++++++++++++---- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/backend/services/backend-service.ts b/src/backend/services/backend-service.ts index 8118ade..30d3ade 100644 --- a/src/backend/services/backend-service.ts +++ b/src/backend/services/backend-service.ts @@ -40,7 +40,7 @@ export class Backend { return await Backend.repository.call<[], Theme[]>("get_themes", []); } async getThemeErrors() { - return await Backend.repository.call<[], { fails: ThemeError[] }>("get_theme_errors", []); + return await Backend.repository.call<[], { fails: ThemeError[] }>("get_last_load_errors", []); } async setThemeState( themeName: string, diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 8a9be20..3c8264b 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -303,8 +303,11 @@ export const createCSSLoaderStore = (backend: Backend) => const { fails: themeErrors } = await backend.getThemeErrors(); set({ themeErrors }); const themes = await backend.getThemes(); + console.log(themes); set({ themes }); - } catch (error) {} + } catch (error) { + console.error("Error Fetching Themes", error); + } }, changePreset: async (presetName: string) => { try { @@ -463,8 +466,12 @@ export const createCSSLoaderStore = (backend: Backend) => enableDepValues?: boolean ) => { try { + console.log("call"); + await backend.setThemeState(theme.name, value, enableDeps, enableDepValues); + console.log("backend call"); await get().getThemes(); + console.log("get themes"); if (!enableDeps && theme.dependencies.length > 0) { if (value) { @@ -488,7 +495,9 @@ export const createCSSLoaderStore = (backend: Backend) => await get().regenerateCurrentPreset(); await get().getThemes(); } - } catch (error) {} + } catch (error) { + console.error(error); + } }, pinTheme: async (themeId: string) => { try { diff --git a/src/lib/utils/toggleThemeWithModals.tsx b/src/lib/utils/toggleThemeWithModals.tsx index b44e128..7b73cf7 100644 --- a/src/lib/utils/toggleThemeWithModals.tsx +++ b/src/lib/utils/toggleThemeWithModals.tsx @@ -2,7 +2,9 @@ import { Flags, Theme } from "@/types"; import { showModal } from "@decky/ui"; import { getCSSLoaderState } from "@/backend"; import { getDeckyPatchState } from "../../decky-patches"; -import { NavPatchInfoModal, OptionalDepsModal } from "../components/modals"; +// Hardcoded to prevent dep cycle +import { OptionalDepsModal } from "../components/modals/optional-deps-modal"; +import { NavPatchInfoModal } from "../components/modals/nav-patch-info-modal"; export async function toggleThemeWithModals(theme: Theme, value: boolean, rerender?: () => void) { const { toggleTheme } = getCSSLoaderState(); diff --git a/src/modules/qam-tab-page/components/QamThemeToggle.tsx b/src/modules/qam-tab-page/components/QamThemeToggle.tsx index c75eb3c..f7f00aa 100644 --- a/src/modules/qam-tab-page/components/QamThemeToggle.tsx +++ b/src/modules/qam-tab-page/components/QamThemeToggle.tsx @@ -52,7 +52,7 @@ export function QamThemeToggle({ theme }: { theme: Theme }) { description={`${updateStatus === "outdated" ? "Update Available" : theme.version} | ${ theme.author }`} - onChange={async (switchValue: boolean) => { + onChange={(switchValue: boolean) => { toggleThemeWithModals(theme, switchValue, rerender); }} /> diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 9de514f..92015d0 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -89,6 +89,10 @@ MARK: TAILWIND font-weight: bold !important; } +.text-xs { + font-size: 0.75rem !important; +} + .absolute-center { position: absolute !important; top: 50% !important; @@ -96,6 +100,11 @@ MARK: TAILWIND transform: translate(-50%, -50%) !important; } +.cl_title { + font-size: 2rem !important; + font-weight: bold !important; +} + /* MARK: Fullscreen Routes */ @@ -106,11 +115,6 @@ MARK: Fullscreen Routes background: #0e141b !important; } -.cl_fullscrenroute_title { - font-size: 2rem !important; - font-weight: bold !important; -} - /* MARK: TitleView */ @@ -565,4 +569,15 @@ MARK: Author View Modal margin-left: auto !important; transform: translateY(2px) !important; } + +/* +MARK: Theme Settings Modal +*/ + +.cl_themesettingsmodal_smallbutton { + width: fit-content !important; + min-width: fit-content !important; + height: fit-content !important; + padding: 10px 12px !important; +} `; From 8cb71eb497c41deee3e144ef7cbf94ef81f3ed8e Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:40:04 -0700 Subject: [PATCH 46/53] misc fixes --- src/backend/state/theme-store.ts | 5 ----- src/decky-patches/nav-patch/nav-patch.ts | 1 - .../preset-selection-dropdown/PresetSelectionDropdown.tsx | 2 -- src/modules/qam-tab-page/components/QamRefreshButton.tsx | 4 ---- src/modules/theme-store/pages/ThemeStoreRouter.tsx | 1 + 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 3c8264b..8d3afb9 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -303,7 +303,6 @@ export const createCSSLoaderStore = (backend: Backend) => const { fails: themeErrors } = await backend.getThemeErrors(); set({ themeErrors }); const themes = await backend.getThemes(); - console.log(themes); set({ themes }); } catch (error) { console.error("Error Fetching Themes", error); @@ -466,12 +465,8 @@ export const createCSSLoaderStore = (backend: Backend) => enableDepValues?: boolean ) => { try { - console.log("call"); - await backend.setThemeState(theme.name, value, enableDeps, enableDepValues); - console.log("backend call"); await get().getThemes(); - console.log("get themes"); if (!enableDeps && theme.dependencies.length > 0) { if (value) { diff --git a/src/decky-patches/nav-patch/nav-patch.ts b/src/decky-patches/nav-patch/nav-patch.ts index cea8973..3efb62a 100644 --- a/src/decky-patches/nav-patch/nav-patch.ts +++ b/src/decky-patches/nav-patch/nav-patch.ts @@ -16,7 +16,6 @@ export function enableNavPatch(): Patch { for (let t = e + n; t >= 0 && t < this.m_rgChildren.length; t += n) { // @ts-ignore const e = this.m_rgChildren[t].FindFocusableNode(r); - console.log(e.m_element); if (e && window.getComputedStyle(e.m_element).display !== "none") return e; } return null; diff --git a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx index 23edb86..7e8cf11 100644 --- a/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx +++ b/src/lib/components/preset-selection-dropdown/PresetSelectionDropdown.tsx @@ -14,8 +14,6 @@ export function PresetSelectionDropdown() { const [render, rerender] = useForcedRerender(); - console.log(themes, presets, selectedPreset, hasInvalidPresetState); - return ( <> {render && ( diff --git a/src/modules/qam-tab-page/components/QamRefreshButton.tsx b/src/modules/qam-tab-page/components/QamRefreshButton.tsx index 7153616..9e9850e 100644 --- a/src/modules/qam-tab-page/components/QamRefreshButton.tsx +++ b/src/modules/qam-tab-page/components/QamRefreshButton.tsx @@ -8,10 +8,6 @@ export function QamRefreshButton() { const refreshButtonRef = useRef(null); - useEffect(() => { - console.log(refreshButtonRef.current); - }); - async function handleRefresh() { await reloadPlugin(); // This just ensures focus isn't lost diff --git a/src/modules/theme-store/pages/ThemeStoreRouter.tsx b/src/modules/theme-store/pages/ThemeStoreRouter.tsx index 7e8971a..0cb2505 100644 --- a/src/modules/theme-store/pages/ThemeStoreRouter.tsx +++ b/src/modules/theme-store/pages/ThemeStoreRouter.tsx @@ -88,6 +88,7 @@ export function ThemeStoreRouter() { return (

+ setCurrentTab(tab)} tabs={tabs}>
); From 77ca9e602194999ccd5a3bf995ed8ff196cd6357 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Wed, 1 Jan 2025 15:57:21 -0700 Subject: [PATCH 47/53] work. --- .../decky-backend-repository-impl.ts | 5 +- .../repositories/backend-repository.ts | 2 +- src/backend/services/backend-service.ts | 19 ++++- src/backend/state/theme-store.ts | 70 ++++++++++++++++++- .../steam-tab-elements-finder.ts | 2 +- src/index.tsx | 7 ++ src/modules/settings/SettingsPageRouter.tsx | 45 ++++++++++++ src/modules/settings/credits/CreditsPage.tsx | 39 +++++++++++ src/modules/settings/credits/index.ts | 1 + src/modules/settings/donate/DonatePage.tsx | 64 +++++++++++++++++ src/modules/settings/index.ts | 1 + .../settings/plugin/PluginSettingsPage.tsx | 69 ++++++++++++++++++ src/modules/settings/plugin/index.ts | 1 + .../settings/profile/ProfileSettings.tsx | 70 +++++++++++++++++++ src/modules/settings/profile/index.ts | 1 + src/styles/styles-as-string.ts | 45 ++++++++++++ src/styles/styles.css | 40 +++++++++++ 17 files changed, 473 insertions(+), 8 deletions(-) create mode 100644 src/modules/settings/SettingsPageRouter.tsx create mode 100644 src/modules/settings/credits/CreditsPage.tsx create mode 100644 src/modules/settings/credits/index.ts create mode 100644 src/modules/settings/donate/DonatePage.tsx create mode 100644 src/modules/settings/index.ts create mode 100644 src/modules/settings/plugin/PluginSettingsPage.tsx create mode 100644 src/modules/settings/plugin/index.ts create mode 100644 src/modules/settings/profile/ProfileSettings.tsx create mode 100644 src/modules/settings/profile/index.ts diff --git a/src/backend-impl/decky-backend-repository-impl.ts b/src/backend-impl/decky-backend-repository-impl.ts index 4735fb0..b14341c 100644 --- a/src/backend-impl/decky-backend-repository-impl.ts +++ b/src/backend-impl/decky-backend-repository-impl.ts @@ -14,7 +14,7 @@ class DeckyBackendRepository implements IBackendRepository { ); } } - async fetch(url: string, request: RequestInit) { + async fetch(url: string, request: RequestInit, mode: "json" | "text" = "json") { try { console.debug("CSSLOADER FETCH", url, request); // TODO: Think this is a decky types issue @@ -23,6 +23,9 @@ class DeckyBackendRepository implements IBackendRepository { if (!res.ok) { throw new Error(`Res Not Okay - Code ${res.status}`); } + if (mode === "text") { + return res.text() as Return; + } return res.json() as Return; } catch (error: unknown) { throw new FetchError( diff --git a/src/backend/repositories/backend-repository.ts b/src/backend/repositories/backend-repository.ts index 3bf97c3..e1433a2 100644 --- a/src/backend/repositories/backend-repository.ts +++ b/src/backend/repositories/backend-repository.ts @@ -1,5 +1,5 @@ export interface IBackendRepository { call: (methodName: string, args: Args) => Promise; toast: (title: string, body?: string) => void; - fetch: (url: string, request: RequestInit) => Promise; + fetch: (url: string, request: RequestInit, mode?: "json" | "text") => Promise; } diff --git a/src/backend/services/backend-service.ts b/src/backend/services/backend-service.ts index 30d3ade..304a1cf 100644 --- a/src/backend/services/backend-service.ts +++ b/src/backend/services/backend-service.ts @@ -36,6 +36,21 @@ export class Backend { async storeWrite(key: string, value: string) { await Backend.repository.call<[string, string], void>("store_write", [key, value]); } + async getServerState() { + return await Backend.repository.call<[], boolean>("get_server_state", []); + } + async enableServer() { + return await Backend.repository.call<[], void>("enable_server", []); + } + async getWatchState() { + return await Backend.repository.call<[], boolean>("get_watch_state", []); + } + async toggleWatchState(bool: boolean, onlyThisSession: boolean) { + return await Backend.repository.call<[boolean, boolean], void>("toggle_watch_state", [ + bool, + onlyThisSession, + ]); + } async getThemes() { return await Backend.repository.call<[], Theme[]>("get_themes", []); } @@ -89,8 +104,8 @@ export class Backend { async deleteTheme(themeName: string) { return await Backend.repository.call<[string], void>("delete_theme", [themeName]); } - async fetch(url: string, request: RequestInit = {}) { - return Backend.repository.fetch(url, request); + async fetch(url: string, request: RequestInit = {}, mode: "json" | "text" = "json") { + return Backend.repository.fetch(url, request, mode); } toast(title: string, body?: string) { diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index 8d3afb9..d2fd2ad 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -23,7 +23,7 @@ export interface CSSLoaderStateValues { // Theme Metadata updateStatuses: UpdateStatus[]; - nextUpdateCheckTime: number; // Unix timestamp; + nextUpdateCheckTime: number; // Unix time stamp; updateCheckTimeout: NodeJS.Timeout | undefined; unpinnedThemes: string[]; isWorking: boolean; @@ -37,6 +37,10 @@ export interface CSSLoaderStateValues { backendVersion: number; motd: Motd | undefined; hiddenMotdId: string; + serverState: boolean; + watchState: boolean; + translationsBranch: "-1" | "0" | "1"; + patrons: string[]; } export interface CSSLoaderStateActions { @@ -78,6 +82,9 @@ export interface CSSLoaderStateActions { pinTheme: (themeId: string) => Promise; unpinTheme: (themeId: string) => Promise; deleteTheme: (themeId: string, refreshAfter?: boolean) => Promise; + setTranslationBranch: (branch: "-1" | "0" | "1") => Promise; + setServerState: (state: boolean) => Promise; + setWatchState: (state: boolean) => Promise; } export interface ICSSLoaderState extends CSSLoaderStateValues, CSSLoaderStateActions {} @@ -112,6 +119,18 @@ export const createCSSLoaderStore = (backend: Backend) => } } + async function getPatrons() { + try { + const data = await backend.fetch(`${apiUrl}/patrons`, {}, "text"); + if (data) { + return data.split("\n"); + } + } catch (error) { + console.error(error); + } + return []; + } + return { apiUrl: apiUrl, // Account Data @@ -136,8 +155,10 @@ export const createCSSLoaderStore = (backend: Backend) => backendVersion: 9, motd: undefined, hiddenMotdId: "", - unminifyModeOn: false, - navPatchInstance: undefined, + serverState: false, + watchState: false, + translationsBranch: "-1", + patrons: [], initializeStore: async () => { try { @@ -180,9 +201,22 @@ export const createCSSLoaderStore = (backend: Backend) => await get().logInWithShortToken(); } + const serverState = await backend.getServerState(); + const watchState = await backend.getWatchState(); + set({ serverState, watchState }); + const translationsBranch = await backend.storeRead("beta_translations"); + set({ + translationsBranch: ["-1", "0", "1"].includes(translationsBranch) + ? (translationsBranch as "-1" | "0" | "1") + : "-1", + }); + const { bulkThemeUpdateCheck, scheduleBulkThemeUpdateCheck } = get(); await bulkThemeUpdateCheck(); scheduleBulkThemeUpdateCheck(); + + const patrons = await getPatrons(); + set({ patrons }); } catch (error) { console.log("Error During Initialzation", error); } @@ -511,6 +545,7 @@ export const createCSSLoaderStore = (backend: Backend) => } catch (error) {} }, deleteTheme: async (themeId: string, refreshAfter: boolean = true) => { + set({ isWorking: true }); try { const { themes } = get(); // The python defs say theme name, just gonna assume it's this and not ID @@ -519,6 +554,35 @@ export const createCSSLoaderStore = (backend: Backend) => await backend.deleteTheme(themeName); refreshAfter && (await get().getThemes()); } catch (error) {} + set({ isWorking: false }); + }, + setTranslationBranch: async (branch: "-1" | "0" | "1") => { + try { + await backend.storeWrite("beta_translations", branch); + const newValue = await backend.storeRead("beta_translations"); + set({ + translationsBranch: ["-1", "0", "1"].includes(newValue) + ? (newValue as "-1" | "0" | "1") + : "-1", + }); + } catch (error) {} + }, + setServerState: async (state: boolean) => { + try { + if (state) { + await backend.enableServer(); + } + await backend.storeWrite("server", state ? "1" : "0"); + const newValue = await backend.getServerState(); + set({ serverState: newValue }); + } catch (error) {} + }, + setWatchState: async (state: boolean) => { + try { + await backend.toggleWatchState(state, false); + const newValue = await backend.getWatchState(); + set({ watchState: newValue }); + } catch (error) {} }, }; }); diff --git a/src/decky-patches/unminify-mode/steam-tab-elements-finder.ts b/src/decky-patches/unminify-mode/steam-tab-elements-finder.ts index 292a82e..70d1a10 100644 --- a/src/decky-patches/unminify-mode/steam-tab-elements-finder.ts +++ b/src/decky-patches/unminify-mode/steam-tab-elements-finder.ts @@ -17,5 +17,5 @@ export function getMainMenu() { return getElementFromNavID("MainNavMenuContainer"); } export function getRootElements() { - return [getSP(), getQAM(), getMainMenu()].filter((e) => e); + return [getSP(), getQAM(), getMainMenu()].filter(Boolean); } diff --git a/src/index.tsx b/src/index.tsx index 463fce6..b2f96bd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,6 +6,7 @@ import { getCSSLoaderState } from "@/backend"; import { getDeckyPatchState } from "./decky-patches"; import { ThemeStoreRouter } from "./modules/theme-store"; import { ExpandedViewPage } from "./modules/expanded-view"; +import { SettingsPageRouter } from "./modules/settings"; export default definePlugin(() => { getCSSLoaderState().initializeStore(); @@ -23,6 +24,12 @@ export default definePlugin(() => { )); + routerHook.addRoute("/cssloader/settings", () => ( + + + + )); + return { name: "SDH-CSSLoader", titleView: ( diff --git a/src/modules/settings/SettingsPageRouter.tsx b/src/modules/settings/SettingsPageRouter.tsx new file mode 100644 index 0000000..89bdfbf --- /dev/null +++ b/src/modules/settings/SettingsPageRouter.tsx @@ -0,0 +1,45 @@ +import { SidebarNavigation } from "@decky/ui"; +import { FaFolder, FaGear, FaGithub, FaHeart, FaPaintRoller } from "react-icons/fa6"; +import { CreditsPage } from "./credits"; +import { DonatePage } from "./donate/DonatePage"; +import { PluginSettingsPage } from "./plugin"; +import { ProfileSettings } from "./profile"; + +export function SettingsPageRouter() { + return ( + , + route: "/cssloader/settings/themes", + content: null, + }, + { + title: "Profiles", + icon: , + route: "/cssloader/settings/profiles", + content: , + }, + { + title: "Settings", + icon: , + route: "/cssloader/settings/plugin", + content: , + }, + { + title: "Donate", + icon: , + route: "/cssloader/settings/donate", + content: , + }, + { + title: "Credits", + icon: , + route: "/cssloader/settings/credits", + content: , + }, + ]} + /> + ); +} diff --git a/src/modules/settings/credits/CreditsPage.tsx b/src/modules/settings/credits/CreditsPage.tsx new file mode 100644 index 0000000..4d95181 --- /dev/null +++ b/src/modules/settings/credits/CreditsPage.tsx @@ -0,0 +1,39 @@ +export function CreditsPage() { + return ( +
+
+
+ Developers +
    +
  • + SuchMeme - github.com/suchmememanyskill +
  • +
  • + Beebles - github.com/beebls +
  • +
  • + EMERALD - github.com/EMERALD0874 +
  • +
+
+
+ Support + + See the DeckThemes Discord server for support. +
+ deckthemes.com/discord +
+
+
+ Create and Submit Your Own Theme + + Instructions for theme creation/submission are available DeckThemes' documentation + website. +
+ docs.deckthemes.com +
+
+
+
+ ); +} diff --git a/src/modules/settings/credits/index.ts b/src/modules/settings/credits/index.ts new file mode 100644 index 0000000..3f85546 --- /dev/null +++ b/src/modules/settings/credits/index.ts @@ -0,0 +1 @@ +export * from "./CreditsPage"; diff --git a/src/modules/settings/donate/DonatePage.tsx b/src/modules/settings/donate/DonatePage.tsx new file mode 100644 index 0000000..016ea86 --- /dev/null +++ b/src/modules/settings/donate/DonatePage.tsx @@ -0,0 +1,64 @@ +import { useCSSLoaderValue } from "@/backend"; +import { Focusable, Navigation, PanelSection } from "@decky/ui"; +import { SiKofi, SiPatreon } from "react-icons/si"; + +export function DonatePage() { + const patrons = useCSSLoaderValue("patrons"); + return ( + +

+ Donations help to cover the costs of hosting the store, as well as funding development for + CSS Loader and its related projects. +

+ + Navigation.NavigateToExternalWeb("https://patreon.com/deckthemes")} + className="flex flex-col gap-2" + focusWithinClassName="gpfocuswithin" + > +
+
+ + Patreon +
+ Recurring Donation + patreon.com/deckthemes +
+
    +
  • + {/* Potentially could expand this to add it to deckthemes and audioloader */} + Your name in CSS Loader +
  • +
  • Patreon badge on deckthemes.com
  • +
  • + {/* Could also impl. this on deck store to make it more meaningful */} + Colored name + VIP channel in the DeckThemes Discord +
  • +
+
+ Navigation.NavigateToExternalWeb("https://ko-fi.com/suchmememanyskill")} + className="flex flex-col" + focusWithinClassName="gpfocuswithin" + > +
+ + Kofi +
+ One-time Donation + ko-fi.com/suchmememanyskill +
+
+ {patrons.length > 0 && ( +
+ Patreon Supporters + {patrons.map((patron) => ( + {}} focusWithinClassName="gpfocuswithin"> +

{patron}

+
+ ))} +
+ )} +
+ ); +} diff --git a/src/modules/settings/index.ts b/src/modules/settings/index.ts new file mode 100644 index 0000000..8c8f223 --- /dev/null +++ b/src/modules/settings/index.ts @@ -0,0 +1 @@ +export * from "./SettingsPageRouter"; diff --git a/src/modules/settings/plugin/PluginSettingsPage.tsx b/src/modules/settings/plugin/PluginSettingsPage.tsx new file mode 100644 index 0000000..cce5b73 --- /dev/null +++ b/src/modules/settings/plugin/PluginSettingsPage.tsx @@ -0,0 +1,69 @@ +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { useDeckyPatchStateAction, useDeckyPatchStateValue } from "@/decky-patches"; +import { DropdownItem, Focusable, ToggleField } from "@decky/ui"; + +export function PluginSettingsPage() { + const serverState = useCSSLoaderValue("serverState"); + const watchState = useCSSLoaderValue("watchState"); + const translationsBranch = useCSSLoaderValue("translationsBranch"); + + const setServerState = useCSSLoaderAction("setServerState"); + const setWatchState = useCSSLoaderAction("setWatchState"); + const setTranslationBranch = useCSSLoaderAction("setTranslationBranch"); + + const unminifyModeOn = useDeckyPatchStateValue("unminifyModeOn"); + const navPatchInstance = useDeckyPatchStateValue("navPatchInstance"); + const setNavPatchState = useDeckyPatchStateAction("setNavPatchState"); + const setUnminifyModeState = useDeckyPatchStateAction("setUnminifyModeState"); + return ( +
+ + setTranslationBranch(data.data)} + /> + + + { + setServerState(value); + }} + /> + + + setNavPatchState(value, true)} + /> + + + + + + setUnminifyModeState(value, true)} + /> + +
+ ); +} diff --git a/src/modules/settings/plugin/index.ts b/src/modules/settings/plugin/index.ts new file mode 100644 index 0000000..3c43dec --- /dev/null +++ b/src/modules/settings/plugin/index.ts @@ -0,0 +1 @@ +export * from "./PluginSettingsPage"; diff --git a/src/modules/settings/profile/ProfileSettings.tsx b/src/modules/settings/profile/ProfileSettings.tsx new file mode 100644 index 0000000..dabab80 --- /dev/null +++ b/src/modules/settings/profile/ProfileSettings.tsx @@ -0,0 +1,70 @@ +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { PresetSelectionDropdown } from "@/lib"; +import { Flags, LocalThemeStatus, Theme } from "@/types"; +import { DialogButton, Focusable, PanelSectionRow } from "@decky/ui"; +import { AiOutlineDownload } from "react-icons/ai"; +import { FaTrash } from "react-icons/fa"; + +export function ProfileSettings() { + const themes = useCSSLoaderValue("themes"); + const profiles = themes.filter((e) => e.flags.includes(Flags.isPreset)); + + return ( + + + + {profiles.map((profile) => ( + + ))} + + + ); +} + +function ProfileEntry({ data }: { data: Theme }) { + const isWorking = useCSSLoaderValue("isWorking"); + const updateStatuses = useCSSLoaderValue("updateStatuses"); + + let updateStatus: LocalThemeStatus = "installed"; + const themeArrPlace = updateStatuses.find((f) => f[0] === data.id); + if (themeArrPlace) updateStatus = themeArrPlace[1]; + const isOutdated = updateStatus === "outdated"; + + const installTheme = useCSSLoaderAction("installTheme"); + const deleteTheme = useCSSLoaderAction("deleteTheme"); + + return ( + +
+ {data.name} +
+ {isOutdated && ( + installTheme(data.id)} + disabled={isWorking} + > + + + )} + deleteTheme(data.id)} + disabled={isWorking} + > + + +
+
+
+ ); +} diff --git a/src/modules/settings/profile/index.ts b/src/modules/settings/profile/index.ts new file mode 100644 index 0000000..d57089f --- /dev/null +++ b/src/modules/settings/profile/index.ts @@ -0,0 +1 @@ +export * from "./ProfileSettings"; diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 92015d0..aa3b077 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -1,6 +1,11 @@ import { gamepadDialogClasses } from "@decky/ui"; export const styles = ` +/* THIS FILE IS NOT USED IN BUILD */ +/* ANY MODIFICATIONS HERE MUST BE COPY PASTED INTO styles-as-string.ts */ +/* THAT IS NEEDED FOR STATIC CLASS INJECTION */ +/* LINT ERRORS ARE TO BE EXPECTED, BECAUSE THIS USES TEMPLATE LITERALS THAT WILL BE FILLED IN BY styles-as-string.ts */ + /* MARK: TAILWIND */ @@ -21,6 +26,18 @@ MARK: TAILWIND flex: 1 1 0% !important; } +.grid { + display: grid !important; +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; +} + +.grid-cols-\\[1fr\\,2fr\\] { + grid-template-columns: 1fr 2fr !important; +} + .gap-1 { gap: 0.25rem !important; } @@ -65,14 +82,30 @@ MARK: TAILWIND padding: 0 !important; } +.pl-4 { + padding-left: 1rem !important; +} + .m-0 { margin: 0 !important; } +.ml-auto { + margin-left: auto !important; +} + .mb-0 { margin-bottom: 0 !important; } +.mb-4 { + margin-bottom: 1rem !important; +} + +.mt-4 { + margin-top: 1rem !important; +} + .w-full { width: 100% !important; } @@ -93,6 +126,18 @@ MARK: TAILWIND font-size: 0.75rem !important; } +.text-lg { + font-size: 1.125rem !important; +} + +.text-xl { + font-size: 1.25rem !important; +} + +.text-2xl { + font-size: 1.5rem !important; +} + .absolute-center { position: absolute !important; top: 50% !important; diff --git a/src/styles/styles.css b/src/styles/styles.css index 3873d2a..f9e2d99 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -23,6 +23,18 @@ MARK: TAILWIND flex: 1 1 0% !important; } +.grid { + display: grid !important; +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; +} + +.grid-cols-\\[1fr\\,2fr\\] { + grid-template-columns: 1fr 2fr !important; +} + .gap-1 { gap: 0.25rem !important; } @@ -67,14 +79,30 @@ MARK: TAILWIND padding: 0 !important; } +.pl-4 { + padding-left: 1rem !important; +} + .m-0 { margin: 0 !important; } +.ml-auto { + margin-left: auto !important; +} + .mb-0 { margin-bottom: 0 !important; } +.mb-4 { + margin-bottom: 1rem !important; +} + +.mt-4 { + margin-top: 1rem !important; +} + .w-full { width: 100% !important; } @@ -95,6 +123,18 @@ MARK: TAILWIND font-size: 0.75rem !important; } +.text-lg { + font-size: 1.125rem !important; +} + +.text-xl { + font-size: 1.25rem !important; +} + +.text-2xl { + font-size: 1.5rem !important; +} + .absolute-center { position: absolute !important; top: 50% !important; From f279ee31756d4769cc014573306671af04b9ccbf Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:16:08 -0700 Subject: [PATCH 48/53] finish theme settings page --- src/backend/state/theme-store.ts | 8 +- src/lib/components/index.ts | 1 + .../optional-deps-modal/OptionalDepsModal.tsx | 6 +- .../ThemeSettingsModal.tsx | 2 +- .../ThemeSettingsModalActionButtons.tsx | 12 ++- .../theme-error-card/ThemeErrorCard.tsx | 24 ++++++ src/lib/components/theme-error-card/index.ts | 1 + .../settings/plugin/PluginSettingsPage.tsx | 4 +- .../settings/theme/ThemeDeleteMenu.tsx | 41 ++++++++++ src/modules/settings/theme/ThemeSettings.tsx | 63 +++++++++++++++ .../settings/theme/ThemeSettingsEntry.tsx | 76 +++++++++++++++++++ .../settings/theme/UpdateAllThemesButton.tsx | 27 +++++++ src/modules/settings/theme/index.ts | 0 src/styles/styles-as-string.ts | 28 ++++++- src/styles/styles.css | 28 ++++++- 15 files changed, 305 insertions(+), 16 deletions(-) create mode 100644 src/lib/components/theme-error-card/ThemeErrorCard.tsx create mode 100644 src/lib/components/theme-error-card/index.ts create mode 100644 src/modules/settings/theme/ThemeDeleteMenu.tsx create mode 100644 src/modules/settings/theme/ThemeSettings.tsx create mode 100644 src/modules/settings/theme/ThemeSettingsEntry.tsx create mode 100644 src/modules/settings/theme/UpdateAllThemesButton.tsx create mode 100644 src/modules/settings/theme/index.ts diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index d2fd2ad..d3874b5 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -547,11 +547,17 @@ export const createCSSLoaderStore = (backend: Backend) => deleteTheme: async (themeId: string, refreshAfter: boolean = true) => { set({ isWorking: true }); try { - const { themes } = get(); + const { themes, unpinnedThemes } = get(); // The python defs say theme name, just gonna assume it's this and not ID const themeName = themes.find((e) => e.id === themeId)?.name; if (!themeName) return; await backend.deleteTheme(themeName); + + // This doesn't actually 'pin' the theme, it just removes it from the unpinned list so that if it's ever reinstalled it isn't hidden + if (unpinnedThemes.includes(themeId)) { + get().pinTheme(themeId); + } + refreshAfter && (await get().getThemes()); } catch (error) {} set({ isWorking: false }); diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 5b8d864..aa048af 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -4,3 +4,4 @@ export * from "./preset-selection-dropdown"; export * from "./theme-patch"; export * from "./modals"; export * from "./theme-card"; +export * from "./theme-error-card"; diff --git a/src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx b/src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx index bd4d878..93d0f8e 100644 --- a/src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx +++ b/src/lib/components/modals/optional-deps-modal/OptionalDepsModal.tsx @@ -20,9 +20,9 @@ export function OptionalDepsModal({

- {theme.name} enables optional themes to enhance this theme. Disabling these may break the - theme, or make the theme look completely different. Specific optional themes can be - configured and or enabled/disabled anytime via the Quick Access Menu. + {theme.display_name} enables optional themes to enhance this theme. Disabling these may + break the theme, or make the theme look completely different. Specific optional themes can + be configured and or enabled/disabled anytime via the Quick Access Menu.

Enable without configuration will enable optional themes but not overwrite their diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx index e0f44a3..3e4c154 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx @@ -21,7 +21,7 @@ export function ThemeSettingsModal({

- {theme.name} + {theme.display_name} {theme.version} | {theme.author} diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx index cf6dd6b..9e143e0 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx @@ -17,10 +17,12 @@ export function ThemeSettingsModalActionButtons({ // Update Check const updateStatuses = useCSSLoaderValue("updateStatuses"); const installTheme = useCSSLoaderAction("installTheme"); + let updateStatus: LocalThemeStatus = "installed"; const themeArrPlace = updateStatuses.find((f) => f[0] === theme.id); if (themeArrPlace) updateStatus = themeArrPlace[1]; const isOutdated = updateStatus === "outdated"; + function handleUpdate() { void installTheme(theme.id); } @@ -44,22 +46,18 @@ export function ThemeSettingsModalActionButtons({ Update )} - + {isPinned ? : } { showModal( diff --git a/src/lib/components/theme-error-card/ThemeErrorCard.tsx b/src/lib/components/theme-error-card/ThemeErrorCard.tsx new file mode 100644 index 0000000..a8849b8 --- /dev/null +++ b/src/lib/components/theme-error-card/ThemeErrorCard.tsx @@ -0,0 +1,24 @@ +import { ThemeError } from "@/types"; +import { Focusable } from "@decky/ui"; + +export function ThemeErrorCard({ error }: { error: ThemeError }) { + return ( + {}} + className="w-full m-0 p-0" + > +
+ + {error[0]} + + {error[1]} +
+
+ ); +} diff --git a/src/lib/components/theme-error-card/index.ts b/src/lib/components/theme-error-card/index.ts new file mode 100644 index 0000000..815602e --- /dev/null +++ b/src/lib/components/theme-error-card/index.ts @@ -0,0 +1 @@ +export * from "./ThemeErrorCard"; diff --git a/src/modules/settings/plugin/PluginSettingsPage.tsx b/src/modules/settings/plugin/PluginSettingsPage.tsx index cce5b73..da2067f 100644 --- a/src/modules/settings/plugin/PluginSettingsPage.tsx +++ b/src/modules/settings/plugin/PluginSettingsPage.tsx @@ -16,7 +16,7 @@ export function PluginSettingsPage() { const setNavPatchState = useDeckyPatchStateAction("setNavPatchState"); const setUnminifyModeState = useDeckyPatchStateAction("setUnminifyModeState"); return ( -
+ setUnminifyModeState(value, true)} /> -
+ ); } diff --git a/src/modules/settings/theme/ThemeDeleteMenu.tsx b/src/modules/settings/theme/ThemeDeleteMenu.tsx new file mode 100644 index 0000000..ce2ff04 --- /dev/null +++ b/src/modules/settings/theme/ThemeDeleteMenu.tsx @@ -0,0 +1,41 @@ +import { DeleteConfirmationModal } from "@/lib"; +import { Theme } from "@/types"; +import { DialogButton, DialogCheckbox, Focusable, showModal } from "@decky/ui"; +import { useState } from "react"; + +export function ThemeDeleteMenu({ + sortedThemeList, + onLeave, +}: { + sortedThemeList: Theme[]; + onLeave: () => void; +}) { + const [choppingBlock, setChoppingBlock] = useState([]); + + return ( + + {sortedThemeList.map((theme) => ( + { + if (checked) { + setChoppingBlock([...choppingBlock, theme.name]); + } else { + setChoppingBlock(choppingBlock.filter((f) => f !== theme.name)); + } + }} + checked={choppingBlock.includes(theme.name)} + label={theme.display_name} + /> + ))} + { + showModal( + + ); + }} + > + Delete + + + ); +} diff --git a/src/modules/settings/theme/ThemeSettings.tsx b/src/modules/settings/theme/ThemeSettings.tsx new file mode 100644 index 0000000..da2f303 --- /dev/null +++ b/src/modules/settings/theme/ThemeSettings.tsx @@ -0,0 +1,63 @@ +import { useCSSLoaderValue } from "@/backend"; +import { ThemeErrorCard } from "@/lib"; +import { Flags } from "@/types"; +import { DialogButton, Focusable } from "@decky/ui"; +import { useMemo, useState } from "react"; +import { UpdateAllThemesButton } from "./UpdateAllThemesButton"; +import { ThemeSettingsEntry } from "./ThemeSettingsEntry"; +import { ThemeDeleteMenu } from "./ThemeDeleteMenu"; + +export function ThemeSettings() { + const [deleteMode, setDeleteMode] = useState(false); + const themes = useCSSLoaderValue("themes"); + const unpinnedThemes = useCSSLoaderValue("unpinnedThemes"); + const themeErrors = useCSSLoaderValue("themeErrors"); + + // This sorts the themes as pinned first, then unpinned, but it freezes it so that if you pin a theme the list doesn't jump around + const sortedList = useMemo(() => { + return themes + .filter((e) => !e.flags.includes(Flags.isPreset)) + .sort((a, b) => { + const aPinned = !unpinnedThemes.includes(a.id); + const bPinned = !unpinnedThemes.includes(b.id); + if (aPinned === bPinned) { + return a.name.localeCompare(b.name); + } + return Number(bPinned) - Number(aPinned); + }); + }, [themes.length]); + + return ( + + {deleteMode ? "Delete" : "Installed"} Themes + + setDeleteMode(!deleteMode)} + > + {deleteMode ? "Go Back" : "Delete Themes"} + + + + {deleteMode ? ( + setDeleteMode(false)} /> + ) : ( + + {sortedList.map((theme) => ( + + ))} + + )} + {themeErrors.length > 0 && ( + + Theme Errors + + {themeErrors.map((e) => ( + + ))} + + + )} + + ); +} diff --git a/src/modules/settings/theme/ThemeSettingsEntry.tsx b/src/modules/settings/theme/ThemeSettingsEntry.tsx new file mode 100644 index 0000000..4b15584 --- /dev/null +++ b/src/modules/settings/theme/ThemeSettingsEntry.tsx @@ -0,0 +1,76 @@ +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { ThemeSettingsModal, toggleThemeWithModals, useThemeInstallState } from "@/lib"; +import { Theme } from "@/types"; +import { DialogButton, Focusable, showModal, ToggleField } from "@decky/ui"; +import { BsGearFill } from "react-icons/bs"; +import { FaEye, FaEyeSlash } from "react-icons/fa6"; + +export function ThemeSettingsEntry({ theme }: { theme: Theme }) { + const unpinnedThemes = useCSSLoaderValue("unpinnedThemes"); + const isWorking = useCSSLoaderValue("isWorking"); + const isPinned = !unpinnedThemes.includes(theme.id); + const updateStatus = useThemeInstallState(theme); + const isOutdated = updateStatus === "outdated"; + + const installTheme = useCSSLoaderAction("installTheme"); + const pinTheme = useCSSLoaderAction("pinTheme"); + const unpinTheme = useCSSLoaderAction("unpinTheme"); + + return ( +
+ {updateStatus === "outdated" && ( +
+ )} + { + showModal(); + }} + onSecondaryActionDescription={isOutdated ? "Update Theme" : undefined} + onSecondaryButton={isOutdated ? () => installTheme(theme.id) : undefined} + > + {theme.display_name}} + checked={theme.enabled} + onChange={(bool) => toggleThemeWithModals(theme, bool)} + /> + { + if (isPinned) { + unpinTheme(theme.id); + } else { + pinTheme(theme.id); + } + }} + > + {isPinned ? : } + + { + showModal(); + }} + > + + + +
+ ); +} diff --git a/src/modules/settings/theme/UpdateAllThemesButton.tsx b/src/modules/settings/theme/UpdateAllThemesButton.tsx new file mode 100644 index 0000000..3aa6da6 --- /dev/null +++ b/src/modules/settings/theme/UpdateAllThemesButton.tsx @@ -0,0 +1,27 @@ +import { useCSSLoaderAction, useCSSLoaderValue } from "@/backend"; +import { DialogButton } from "@decky/ui"; +import { FaDownload } from "react-icons/fa6"; + +export function UpdateAllThemesButton() { + const updateStatuses = useCSSLoaderValue("updateStatuses"); + const themes = useCSSLoaderValue("themes"); + const installTheme = useCSSLoaderAction("installTheme"); + + async function updateAll() { + const outdatedThemes = updateStatuses.filter((f) => f[1] === "outdated").map((f) => f[0]); + for (const themeId of outdatedThemes) { + await installTheme(themeId); + } + } + + return ( + <> + {updateStatuses.filter((e) => e[1] === "outdated").length > 0 && ( + + + Update All Themes + + )} + + ); +} diff --git a/src/modules/settings/theme/index.ts b/src/modules/settings/theme/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index aa3b077..ecc9b90 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -619,10 +619,36 @@ MARK: Author View Modal MARK: Theme Settings Modal */ -.cl_themesettingsmodal_smallbutton { +.cl_squaredialogbutton { width: fit-content !important; min-width: fit-content !important; height: fit-content !important; padding: 10px 12px !important; } + +/* +MARK: Settings Page +*/ + +.cl_themesettings_togglecontainer { + flex-grow: 1; + position: relative; +} +/* The actual element of the ToggleContainer with the BG */ +.cl_themesettings_togglecontainer > div { + background: rgba(255,255,255,.15); + border-radius: 2px; + padding-left: 5px; + padding-right: 5px; + margin-left: 0; + margin-right: 0; + height: 1.25em !important; +} + +.cl_themesettings_themelabel { + white-space: nowrap; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index f9e2d99..10cd1e7 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -616,9 +616,35 @@ MARK: Author View Modal MARK: Theme Settings Modal */ -.cl_themesettingsmodal_smallbutton { +.cl_squaredialogbutton { width: fit-content !important; min-width: fit-content !important; height: fit-content !important; padding: 10px 12px !important; +} + +/* +MARK: Settings Page +*/ + +.cl_themesettings_togglecontainer { + flex-grow: 1; + position: relative; +} +/* The actual element of the ToggleContainer with the BG */ +.cl_themesettings_togglecontainer > div { + background: rgba(255,255,255,.15); + border-radius: 2px; + padding-left: 5px; + padding-right: 5px; + margin-left: 0; + margin-right: 0; + height: 1.25em !important; +} + +.cl_themesettings_themelabel { + white-space: nowrap; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; } \ No newline at end of file From 7775bb025bfd0c0f2b240081efd8ad132f75a7ba Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:18:40 -0700 Subject: [PATCH 49/53] fix theme settings page and modals --- .../delete-confirmation-modal/DeleteConfirmationModal.tsx | 2 +- .../modals/theme-settings-modal/ThemeSettingsModal.tsx | 2 +- src/modules/settings/SettingsPageRouter.tsx | 3 ++- src/modules/settings/theme/ThemeSettingsEntry.tsx | 2 +- src/modules/settings/theme/index.ts | 1 + src/styles/styles-as-string.ts | 7 +++++-- src/styles/styles.css | 7 +++++-- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx b/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx index 88397e3..48d3719 100644 --- a/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx +++ b/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx @@ -20,7 +20,7 @@ export function DeleteConfirmationModal({ } return ( - +
Are you sure you want to delete{" "} {themesToBeDeleted.length === 1 ? `this theme` : `these ${themesToBeDeleted.length} themes`} diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx index 3e4c154..e993f07 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx @@ -16,7 +16,7 @@ export function ThemeSettingsModal({ const theme = themes.find((theme) => theme.id === themeId); return ( - + {!!theme ? ( diff --git a/src/modules/settings/SettingsPageRouter.tsx b/src/modules/settings/SettingsPageRouter.tsx index 89bdfbf..38357e7 100644 --- a/src/modules/settings/SettingsPageRouter.tsx +++ b/src/modules/settings/SettingsPageRouter.tsx @@ -4,6 +4,7 @@ import { CreditsPage } from "./credits"; import { DonatePage } from "./donate/DonatePage"; import { PluginSettingsPage } from "./plugin"; import { ProfileSettings } from "./profile"; +import { ThemeSettings } from "./theme"; export function SettingsPageRouter() { return ( @@ -13,7 +14,7 @@ export function SettingsPageRouter() { title: "Themes", icon: , route: "/cssloader/settings/themes", - content: null, + content: , }, { title: "Profiles", diff --git a/src/modules/settings/theme/ThemeSettingsEntry.tsx b/src/modules/settings/theme/ThemeSettingsEntry.tsx index 4b15584..207a466 100644 --- a/src/modules/settings/theme/ThemeSettingsEntry.tsx +++ b/src/modules/settings/theme/ThemeSettingsEntry.tsx @@ -17,7 +17,7 @@ export function ThemeSettingsEntry({ theme }: { theme: Theme }) { const unpinTheme = useCSSLoaderAction("unpinTheme"); return ( -
+
{updateStatus === "outdated" && (
div { @@ -643,6 +645,7 @@ MARK: Settings Page margin-left: 0; margin-right: 0; height: 1.25em !important; + flex: 1 1 0% !important; } .cl_themesettings_themelabel { diff --git a/src/styles/styles.css b/src/styles/styles.css index 10cd1e7..e685956 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -628,8 +628,10 @@ MARK: Settings Page */ .cl_themesettings_togglecontainer { - flex-grow: 1; - position: relative; + flex-grow: 1 !important; + position: relative !important; + display: flex !important; + gap: 0.25rem !important; } /* The actual element of the ToggleContainer with the BG */ .cl_themesettings_togglecontainer > div { @@ -640,6 +642,7 @@ MARK: Settings Page margin-left: 0; margin-right: 0; height: 1.25em !important; + flex: 1 1 0% !important; } .cl_themesettings_themelabel { From caf5b9c7dc3488830c05dec1f3a3359202bb1872 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:25:31 -0700 Subject: [PATCH 50/53] fix mass-deletion of themes --- .../DeleteConfirmationModal.tsx | 12 +++++++----- .../theme-settings-modal/ThemeSettingsModal.tsx | 2 +- .../ThemeSettingsModalActionButtons.tsx | 2 +- src/modules/settings/profile/ProfileSettings.tsx | 4 ++-- src/modules/settings/theme/ThemeDeleteMenu.tsx | 8 ++++---- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx b/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx index 48d3719..060d623 100644 --- a/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx +++ b/src/lib/components/modals/delete-confirmation-modal/DeleteConfirmationModal.tsx @@ -3,17 +3,17 @@ import { ConfirmModal } from "../../../primitives"; export function DeleteConfirmationModal({ closeModal, - themesToBeDeleted, + themeIdsToBeDeleted, onDeleteFinish, }: { closeModal?: () => void; - themesToBeDeleted: string[]; + themeIdsToBeDeleted: string[]; onDeleteFinish?: () => void; }) { const deleteTheme = useCSSLoaderAction("deleteTheme"); async function deleteThemes() { - for (let i = 0; i < themesToBeDeleted.length; i++) { - await deleteTheme(themesToBeDeleted[i], i === themesToBeDeleted.length - 1); + for (let i = 0; i < themeIdsToBeDeleted.length; i++) { + await deleteTheme(themeIdsToBeDeleted[i], i === themeIdsToBeDeleted.length - 1); } onDeleteFinish?.(); closeModal?.(); @@ -23,7 +23,9 @@ export function DeleteConfirmationModal({
Are you sure you want to delete{" "} - {themesToBeDeleted.length === 1 ? `this theme` : `these ${themesToBeDeleted.length} themes`} + {themeIdsToBeDeleted.length === 1 + ? `this theme` + : `these ${themeIdsToBeDeleted.length} themes`} ?
diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx index e993f07..e25b26d 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModal.tsx @@ -46,7 +46,7 @@ export function ThemeSettingsModal({ ))} ) : null} - + Close diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx index 9e143e0..8725d71 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx @@ -60,7 +60,7 @@ export function ThemeSettingsModalActionButtons({ className="cl_squaredialogbutton" onClick={() => { showModal( - + ); }} > diff --git a/src/modules/settings/profile/ProfileSettings.tsx b/src/modules/settings/profile/ProfileSettings.tsx index dabab80..03c5057 100644 --- a/src/modules/settings/profile/ProfileSettings.tsx +++ b/src/modules/settings/profile/ProfileSettings.tsx @@ -12,7 +12,7 @@ export function ProfileSettings() { return ( - + {profiles.map((profile) => ( ))} @@ -35,7 +35,7 @@ function ProfileEntry({ data }: { data: Theme }) { return ( -
+
{data.name}
{isOutdated && ( diff --git a/src/modules/settings/theme/ThemeDeleteMenu.tsx b/src/modules/settings/theme/ThemeDeleteMenu.tsx index ce2ff04..e7440ed 100644 --- a/src/modules/settings/theme/ThemeDeleteMenu.tsx +++ b/src/modules/settings/theme/ThemeDeleteMenu.tsx @@ -18,19 +18,19 @@ export function ThemeDeleteMenu({ { if (checked) { - setChoppingBlock([...choppingBlock, theme.name]); + setChoppingBlock([...choppingBlock, theme.id]); } else { - setChoppingBlock(choppingBlock.filter((f) => f !== theme.name)); + setChoppingBlock(choppingBlock.filter((f) => f !== theme.id)); } }} - checked={choppingBlock.includes(theme.name)} + checked={choppingBlock.includes(theme.id)} label={theme.display_name} /> ))} { showModal( - + ); }} > From 8c7ab87ee054190a758a1670433ea71a2daadf5c Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:32:16 -0700 Subject: [PATCH 51/53] fix icons in square buttons --- .../ThemeSettingsModalActionButtons.tsx | 10 +++++++--- src/modules/settings/theme/ThemeSettingsEntry.tsx | 8 ++++++-- src/styles/styles.css | 10 +++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx index 8725d71..884c93d 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx @@ -48,12 +48,16 @@ export function ThemeSettingsModalActionButtons({ onClick={handleUpdate} className="cl_squaredialogbutton flex gap-1" > - + Update )} - {isPinned ? : } + {isPinned ? ( + + ) : ( + + )} - + ); diff --git a/src/modules/settings/theme/ThemeSettingsEntry.tsx b/src/modules/settings/theme/ThemeSettingsEntry.tsx index 207a466..288f441 100644 --- a/src/modules/settings/theme/ThemeSettingsEntry.tsx +++ b/src/modules/settings/theme/ThemeSettingsEntry.tsx @@ -59,7 +59,11 @@ export function ThemeSettingsEntry({ theme }: { theme: Theme }) { } }} > - {isPinned ? : } + {isPinned ? ( + + ) : ( + + )} ); }} > - +
diff --git a/src/styles/styles.css b/src/styles/styles.css index e685956..4745275 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -176,13 +176,13 @@ MARK: TitleView @keyframes onboardingButton { 0% { - transform: scale(1) !important; + transform: scale(1); } 50% { - transform: scale(1.1) !important; + transform: scale(1.1); } 100% { - transform: scale(1) !important; + transform: scale(1); } } @@ -650,4 +650,8 @@ MARK: Settings Page max-width: 300px; overflow: hidden; text-overflow: ellipsis; +} + +.cl_squarebutton_icontranslate { + transform: translateY(-2px) !important; } \ No newline at end of file From ea5c8f884740aa02973a2f32a71c07ae9f6cea69 Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:38:44 -0700 Subject: [PATCH 52/53] fix onboarding animation --- .../ThemeSettingsModalActionButtons.tsx | 8 ++++---- src/lib/components/title-view/TitleView.tsx | 2 +- src/modules/settings/theme/ThemeSettingsEntry.tsx | 6 +++--- src/styles/styles-as-string.ts | 10 +++++++--- src/styles/styles.css | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx index 884c93d..3e04190 100644 --- a/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx +++ b/src/lib/components/modals/theme-settings-modal/ThemeSettingsModalActionButtons.tsx @@ -48,15 +48,15 @@ export function ThemeSettingsModalActionButtons({ onClick={handleUpdate} className="cl_squaredialogbutton flex gap-1" > - + Update )} {isPinned ? ( - + ) : ( - + )} - + ); diff --git a/src/lib/components/title-view/TitleView.tsx b/src/lib/components/title-view/TitleView.tsx index 45b4099..5a25b9b 100644 --- a/src/lib/components/title-view/TitleView.tsx +++ b/src/lib/components/title-view/TitleView.tsx @@ -26,7 +26,7 @@ export function TitleView() { >
CSS Loader
0 && "cl-animate-onboarding")} + className={cn("cl-title-view-button", themes.length === 0 && "cl-animate-onboarding")} onClick={onStoreClick} > diff --git a/src/modules/settings/theme/ThemeSettingsEntry.tsx b/src/modules/settings/theme/ThemeSettingsEntry.tsx index 288f441..79f96da 100644 --- a/src/modules/settings/theme/ThemeSettingsEntry.tsx +++ b/src/modules/settings/theme/ThemeSettingsEntry.tsx @@ -60,9 +60,9 @@ export function ThemeSettingsEntry({ theme }: { theme: Theme }) { }} > {isPinned ? ( - + ) : ( - + )} ); }} > - +
diff --git a/src/styles/styles-as-string.ts b/src/styles/styles-as-string.ts index 5128ea1..7e3aa77 100644 --- a/src/styles/styles-as-string.ts +++ b/src/styles/styles-as-string.ts @@ -179,13 +179,13 @@ MARK: TitleView @keyframes onboardingButton { 0% { - transform: scale(1) !important; + transform: scale(1); } 50% { - transform: scale(1.1) !important; + transform: scale(1.1); } 100% { - transform: scale(1) !important; + transform: scale(1); } } @@ -654,4 +654,8 @@ MARK: Settings Page overflow: hidden; text-overflow: ellipsis; } + +.cl_squaredialogbutton_icontranslate { + transform: translateY(-2px) !important; +} `; diff --git a/src/styles/styles.css b/src/styles/styles.css index 4745275..12b1171 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -652,6 +652,6 @@ MARK: Settings Page text-overflow: ellipsis; } -.cl_squarebutton_icontranslate { +.cl_squaredialogbutton_icontranslate { transform: translateY(-2px) !important; } \ No newline at end of file From ff03adb6bd78558b51782b9dde0c9ba2f53aae2d Mon Sep 17 00:00:00 2001 From: Beebles <102569435+beebls@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:42:38 -0700 Subject: [PATCH 53/53] fix selected preset dropdown not being updated --- src/backend/state/theme-store.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/state/theme-store.ts b/src/backend/state/theme-store.ts index d3874b5..fb09732 100644 --- a/src/backend/state/theme-store.ts +++ b/src/backend/state/theme-store.ts @@ -337,7 +337,10 @@ export const createCSSLoaderStore = (backend: Backend) => const { fails: themeErrors } = await backend.getThemeErrors(); set({ themeErrors }); const themes = await backend.getThemes(); - set({ themes }); + set({ + themes, + selectedPreset: themes.find((e) => e.flags.includes(Flags.isPreset) && e.enabled), + }); } catch (error) { console.error("Error Fetching Themes", error); }