Skip to content

Commit

Permalink
lots of work on profile uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
beebls committed Mar 7, 2025
1 parent aded419 commit 1888236
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 68 deletions.
11 changes: 10 additions & 1 deletion src/backend/services/backend-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Theme, ThemeError } from "../../types";
import { APIBlob, BlobType, Theme, ThemeError } from "../../types";
import { IBackendRepository } from "../repositories";

export class Backend {
Expand Down Expand Up @@ -114,9 +114,18 @@ export class Backend {
async getMappings() {
return Backend.repository.call<[], object>("get_webpack_mappings", []);
}
async getMappingsVersion() {
return Backend.repository.call<[], string>("get_mappings_version", []);
}
async saveMappings(string: string) {
return Backend.repository.call<[string], void>("save_mappings", [string]);
}
async uploadThemeBlob(themeName: string, apiBaseUrl: string, authToken: string) {
return Backend.repository.call<
[string, string, string],
{ success: boolean; message: APIBlob }
>("upload_theme", [themeName, apiBaseUrl, authToken]);
}

toast(title: string, body?: string) {
Backend.repository.toast(title, body);
Expand Down
68 changes: 67 additions & 1 deletion src/backend/state/theme-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import {
FullAccountData,
MinimalCSSThemeInfo,
Motd,
PartialCSSThemeInfo,
Theme,
ThemeError,
ThemeQueryResponse,
UpdateStatus,
} from "../../types";
import { createStore } from "zustand";
Expand Down Expand Up @@ -35,6 +37,7 @@ export interface CSSLoaderStateValues {
// Plugin Settings
dummyFunctionResult: boolean;
backendVersion: number;
mappingsVersionStr: string;
motd: Motd | undefined;
hiddenMotdId: string;
serverState: boolean;
Expand Down Expand Up @@ -81,6 +84,8 @@ export interface CSSLoaderStateActions {
setTranslationBranch: (branch: "-1" | "0" | "1") => Promise<void>;
setServerState: (state: boolean) => Promise<void>;
setWatchState: (state: boolean) => Promise<void>;
getUploadedThemes: () => Promise<PartialCSSThemeInfo[]>;
publishProfile: (profileName: string, isPublic: boolean) => Promise<void>;
}

export interface FetchOptions {
Expand Down Expand Up @@ -163,6 +168,7 @@ export const createCSSLoaderStore = (backend: Backend) =>

// Plugin Settings
dummyFunctionResult: false,
mappingsVersionStr: "",
backendVersion: 9,
motd: undefined,
hiddenMotdId: "",
Expand Down Expand Up @@ -222,6 +228,8 @@ export const createCSSLoaderStore = (backend: Backend) =>
? (translationsBranch as "-1" | "0" | "1")
: "-1",
});
const mappingsVersionStr = await backend.getMappingsVersion();
set({ mappingsVersionStr });

const { bulkThemeUpdateCheck, scheduleBulkThemeUpdateCheck } = get();
await bulkThemeUpdateCheck();
Expand Down Expand Up @@ -255,6 +263,8 @@ export const createCSSLoaderStore = (backend: Backend) =>
set({ dummyFunctionResult });
await reloadThemes();
await bulkThemeUpdateCheck();
const mappingsVersionStr = await backend.getMappingsVersion();
set({ mappingsVersionStr });
}
} catch (error) {}
set({ isWorking: false });
Expand Down Expand Up @@ -294,7 +304,7 @@ export const createCSSLoaderStore = (backend: Backend) =>
apiFullToken: json.token,
apiTokenExpireDate: new Date().valueOf() + 1000 * 10 * 60,
});
const meJson = await apiFetch<FullAccountData>("/auth/me", undefined, {
const meJson = await apiFetch<FullAccountData>("/auth/me_full", undefined, {
requiresAuth: true,
});
if (meJson) {
Expand Down Expand Up @@ -609,5 +619,61 @@ export const createCSSLoaderStore = (backend: Backend) =>
set({ watchState: newValue });
} catch (error) {}
},
async getUploadedThemes() {
try {
if (!get().apiShortToken) {
return [];
}
const publicThemesRes = await apiFetch<ThemeQueryResponse>(
"/users/me/themes?filters=CSS",
{},
{ requiresAuth: true }
);
const privateThemesRes = await apiFetch<ThemeQueryResponse>(
"/users/me/themes/private?filters=CSS",
{},
{ requiresAuth: true }
);
return [...publicThemesRes.items, ...privateThemesRes.items].sort((a, b) => {
const dateA = new Date(a.updated);
const dateB = new Date(b.updated);
return dateB.getTime() - dateA.getTime();
});
} catch {
return [];
}
},
async publishProfile(profileName: string, isPublic: boolean) {
try {
if (!get().themes.some((e) => e.name === profileName)) return;
const refreshedToken = await get().refreshToken();
if (!refreshedToken) return;

const blobRes = await backend.uploadThemeBlob(profileName, apiUrl, refreshedToken);
if (!blobRes?.success) return;

const submissionRes = await apiFetch(
"/submissions/css_zip",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
blob: blobRes.message.id,
meta: {
imageBlobs: [],
description: "Uploaded from Decky CSS Loader",
privateSubmission: !isPublic,
},
}),
},
{ requiresAuth: true }
);
console.log(submissionRes);
} catch (error) {
console.log(error);
}
},
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FiPlusCircle } from "react-icons/fi";
import { useForcedRerender } from "../../hooks";
import { useCSSLoaderActions, useCSSLoaderValues } from "@/backend";

export function PresetSelectionDropdown() {
export function PresetSelectionDropdown({ noBottomSeparator }: { noBottomSeparator?: boolean }) {
const { themes, selectedPreset } = useCSSLoaderValues();
const { changePreset } = useCSSLoaderActions();
const presets = themes.filter((e) => e.flags.includes(Flags.isPreset));
Expand All @@ -18,6 +18,7 @@ export function PresetSelectionDropdown() {
{render && (
<PanelSectionRow>
<DropdownItem
bottomSeparator={noBottomSeparator ? "none" : "standard"}
label="Selected Profile"
selectedOption={
hasInvalidPresetState ? "Invalid State" : selectedPreset?.name || "None"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function ExpandedViewScrollingSection() {
<span className="cl_expandedview_version">{data.version}</span>
</div>
{/* Author / Modified Date */}
<Focusable className="flex gap-1 cl_expandedview_graytext">
<Focusable className="flex gap-1 cl_graytext">
<Focusable
onOKActionDescription="View Profile"
focusClassName="gpfocuswithin"
Expand All @@ -56,9 +56,7 @@ export function ExpandedViewScrollingSection() {
<Selectable className="flex flex-col gap-1">
<span className="font-bold">Description</span>
<span className={data.description.length > 400 ? "text-sm" : ""}>
{data.description || (
<i className="cl_expandedview_graytext">No description provided.</i>
)}
{data.description || <i className="cl_graytext">No description provided.</i>}
</span>
</Selectable>
{/* Targets */}
Expand Down
4 changes: 2 additions & 2 deletions src/modules/settings/credits/CreditsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Selectable } from "../../../lib/primitives";

export function CreditsPage() {
return (
<div>
<Focusable className="cl_settingspage_container">
<div className="flex flex-col gap-4">
<Selectable className="flex flex-col">
<span className="text-2xl font-bold">Developers</span>
Expand Down Expand Up @@ -49,6 +49,6 @@ export function CreditsPage() {
</span>
</Selectable>
</div>
</div>
</Focusable>
);
}
2 changes: 1 addition & 1 deletion src/modules/settings/donate/DonatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Selectable } from "../../../lib/primitives";
export function DonatePage() {
const { patrons } = useCSSLoaderValues();
return (
<Focusable>
<Focusable className="cl_settingspage_container">
<Selectable>
<p>
Donations help to cover the costs of hosting the store, as well as funding development for
Expand Down
36 changes: 21 additions & 15 deletions src/modules/settings/plugin/PluginSettingsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import { useCSSLoaderActions, useCSSLoaderValues } from "@/backend";
import { useDeckyPatchStateActions, useDeckyPatchStateValues } from "@/decky-patches";
import { ButtonItem, DropdownItem, Focusable, ToggleField } from "@decky/ui";
import { Selectable } from "../../../lib/primitives";

export function PluginSettingsPage() {
const { serverState, watchState, translationsBranch } = useCSSLoaderValues();
const { serverState, watchState, translationsBranch, mappingsVersionStr } = useCSSLoaderValues();
const { setServerState, setWatchState, setTranslationBranch } = useCSSLoaderActions();

const { unminifyModeOn, navPatchInstance } = useDeckyPatchStateValues();
const { setNavPatchState, setUnminifyModeState, dumpMappings } = useDeckyPatchStateActions();
return (
<Focusable>
<Focusable>
<DropdownItem
rgOptions={[
{ data: "-1", label: "Auto-Detect" },
{ data: "0", label: "Force Stable" },
{ data: "1", label: "Force Beta" },
]}
selectedOption={translationsBranch}
label="Steam Client Branch"
description="This allows us to provide the correct translations for your system."
onChange={(data) => setTranslationBranch(data.data)}
/>
</Focusable>
<Focusable className="cl_settingspage_container">
<Focusable>
<ToggleField
checked={serverState}
Expand Down Expand Up @@ -66,6 +54,24 @@ export function PluginSettingsPage() {
Save
</ButtonItem>
</Focusable>
<Focusable>
<DropdownItem
rgOptions={[
{ data: "-1", label: "Auto-Detect" },
{ data: "0", label: "Force Stable" },
{ data: "1", label: "Force Beta" },
]}
selectedOption={translationsBranch}
label="Steam Client Branch"
description="This allows us to provide the correct translations for your system."
onChange={(data) => setTranslationBranch(data.data)}
/>
</Focusable>
<Selectable className="mt-4">
<span>
<b>Mappings Version:</b> <span className="font-mono">{mappingsVersionStr}</span>
</span>
</Selectable>
</Focusable>
);
}
41 changes: 41 additions & 0 deletions src/modules/settings/profile/ProfileInstalledEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useCSSLoaderActions, useCSSLoaderValues } from "@/backend";
import { useThemeInstallState } from "@/lib";
import { Theme } from "@/types";
import { DialogButton, Focusable, PanelSectionRow } from "@decky/ui";
import { AiOutlineDownload } from "react-icons/ai";
import { FaTrash } from "react-icons/fa";

export function ProfileInstalledEntry({ data }: { data: Theme }) {
const { isWorking } = useCSSLoaderValues();
const { installTheme, deleteTheme } = useCSSLoaderActions();

const updateStatus = useThemeInstallState(data);
const isOutdated = updateStatus === "outdated";

return (
<PanelSectionRow>
<Focusable className="flex gap-2 py-0 px-[5px]">
<div className="cl_profileentry_backdrop">
<span>{data.display_name}</span>
</div>
{isOutdated && (
<DialogButton
className="cl_profileentry_actionbutton"
onClick={() => installTheme(data.id)}
disabled={isWorking}
>
<AiOutlineDownload />
Update
</DialogButton>
)}
<DialogButton
className="cl_squaredialogbutton"
onClick={() => deleteTheme(data.id)}
disabled={isWorking}
>
<FaTrash />
</DialogButton>
</Focusable>
</PanelSectionRow>
);
}
Loading

0 comments on commit 1888236

Please sign in to comment.