Skip to content

Commit

Permalink
finish upload profile backend code
Browse files Browse the repository at this point in the history
  • Loading branch information
beebls committed Mar 9, 2025
1 parent 63e5a94 commit aa39f37
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 15 deletions.
69 changes: 60 additions & 9 deletions src/backend/state/theme-store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { sleep } from "@decky/ui";
import { createStore } from "zustand";
import {
Flags,
FullAccountData,
MinimalCSSThemeInfo,
Motd,
PartialCSSThemeInfo,
TaskQueryResponse,
Theme,
ThemeError,
ThemeQueryResponse,
Expand Down Expand Up @@ -96,7 +98,11 @@ export interface CSSLoaderStateActions {
getMotd: () => Promise<void>;
hideMotd: () => Promise<void>;
getUploadedThemes: () => Promise<PartialCSSThemeInfo[]>;
publishProfile: (profileName: string, isPublic: boolean) => Promise<void>;
publishProfile: (
profileName: string,
isPublic: boolean,
description?: string
) => Promise<{ success: boolean; message: string }>;

// Settings related actions
setTranslationBranch: (branch: "-1" | "0" | "1") => Promise<void>;
Expand Down Expand Up @@ -659,16 +665,22 @@ export const createCSSLoaderStore = (backend: Backend) =>
return [];
}
},
async publishProfile(profileName: string, isPublic: boolean) {
async publishProfile(profileName: string, isPublic: boolean, description?: string) {
try {
if (!get().themes.some((e) => e.name === profileName)) return;
if (!get().themes.some((e) => e.name === profileName)) {
throw new Error("Profile not found");
}
const refreshedToken = await get().refreshToken();
if (!refreshedToken) return;
if (!refreshedToken) {
throw new Error("Couldn't refresh auth token");
}

const blobRes = await backend.uploadThemeBlob(profileName, apiUrl, refreshedToken);
if (!blobRes?.success) return;
if (!blobRes?.success) {
throw new Error("Failed to upload blob");
}

const submissionRes = await apiFetch(
const submissionRes = await apiFetch<{ task: string }>(
"/submissions/css_zip",
{
method: "POST",
Expand All @@ -679,16 +691,55 @@ export const createCSSLoaderStore = (backend: Backend) =>
blob: blobRes.message.id,
meta: {
imageBlobs: [],
description: "Uploaded from Decky CSS Loader",
description: description,
privateSubmission: !isPublic,
},
}),
},
{ requiresAuth: true }
);
console.log(submissionRes);

if (!submissionRes?.task) {
throw new Error("No task returned");
}

let taskResult = null;
while (!taskResult) {
const currentTaskStatus = await apiFetch<TaskQueryResponse>(
`/tasks/${submissionRes.task}`,
{},
{ requiresAuth: true }
);
if (currentTaskStatus?.status === "complete") {
taskResult = currentTaskStatus;
} else if (currentTaskStatus?.status === "failed") {
throw new Error(`Submission failed, ${currentTaskStatus?.status}`);
} else {
await sleep(1000);
}
}

return {
success: true,
message: taskResult.status,
};
} catch (error) {
console.log(error);
if (error instanceof FetchError) {
return {
success: false,
message: JSON.stringify(error.getError()),
};
}
if (error instanceof Error) {
return {
success: false,
message: error.message,
};
}
return {
success: false,
message: "Unknown Error",
};
}
},

Expand Down
1 change: 0 additions & 1 deletion src/lib/components/modals/create-preset-modal/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TextField } from "@decky/ui";
import { useState } from "react";
import { ConfirmModal } from "../../../primitives";

export function CreatePresetModal({ closeModal }: { closeModal?: () => void }) {
export function CreateProfileModal({ closeModal }: { closeModal?: () => void }) {
const { themes } = useCSSLoaderValues();
const { createPreset } = useCSSLoaderActions();
const numOfEnabledThemes = themes.filter((e) => e.enabled).length;
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/modals/create-profile-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./CreateProfileModal";
3 changes: 2 additions & 1 deletion src/lib/components/modals/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./author-view-modal";
export * from "./create-preset-modal";
export * from "./create-profile-modal";
export * from "./delete-confirmation-modal";
export * from "./nav-patch-info-modal";
export * from "./optional-deps-modal";
export * from "./theme-settings-modal";
export * from "./upload-profile-modal";
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useCSSLoaderActions } from "@/backend";
import { Theme } from "@/types";
import { DropdownItem, PanelSectionRow, TextField, ToggleField } from "@decky/ui";
import { useState } from "react";
import { ConfirmModal } from "../../../primitives";

export function UploadProfileModal({
closeModal,
eligibleProfiles,
}: {
closeModal?: () => void;
eligibleProfiles: Theme[];
}) {
const { publishProfile } = useCSSLoaderActions();

const [selectedProfileId, setSelectedProfileId] = useState<string | null>(null);
const [isPublic, setPublic] = useState(false);
const [description, setDescription] = useState("");

const [loading, setLoading] = useState(false);

async function handleUpload() {
if (!selectedProfileId) return;
setLoading(true);
await publishProfile(selectedProfileId, isPublic, description);
setLoading(false);

// closeModal?.();
}

return (
<ConfirmModal
title="Upload Profile"
confirmText="Upload"
onConfirm={handleUpload}
closeModal={closeModal}
cancelDisabled={loading}
confirmDisabled={loading || !selectedProfileId}
>
<PanelSectionRow>
<DropdownItem
selectedOption={selectedProfileId}
rgOptions={eligibleProfiles.map((e) => ({ data: e.id, label: e.display_name }))}
onChange={(option) => {
setSelectedProfileId(option.data);
}}
label="Profile to Upload"
/>
</PanelSectionRow>
<PanelSectionRow>
<ToggleField checked={isPublic} onChange={setPublic} label="Make Profile Public" />
</PanelSectionRow>
<PanelSectionRow>
<TextField
label="Description (Optional)"
value={description}
onChange={(e) => {
setDescription(e.target.value);
}}
/>
</PanelSectionRow>
</ConfirmModal>
);
}
1 change: 1 addition & 0 deletions src/lib/components/modals/upload-profile-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./UploadProfileModal";
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Flags } from "@/types";
import { DropdownItem, PanelSectionRow, showModal } from "@decky/ui";
import { FiPlusCircle } from "react-icons/fi";
import { useForcedRerender } from "../../hooks";
import { CreatePresetModal } from "../modals";
import { CreateProfileModal } from "../modals";

export function PresetSelectionDropdown({ noBottomSeparator }: { noBottomSeparator?: boolean }) {
const { themes, selectedPreset } = useCSSLoaderValues();
Expand Down Expand Up @@ -46,7 +46,7 @@ export function PresetSelectionDropdown({ noBottomSeparator }: { noBottomSeparat
]}
onChange={async ({ data }) => {
if (data === "New Profile") {
showModal(<CreatePresetModal />);
showModal(<CreateProfileModal />);
rerender();
return;
}
Expand Down
6 changes: 6 additions & 0 deletions src/lib/primitives/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export function ConfirmModal({
description,
confirmText,
cancelText,
confirmDisabled,
cancelDisabled,
}: {
closeModal?: () => void;
children: React.ReactNode;
Expand All @@ -17,6 +19,8 @@ export function ConfirmModal({
description?: string;
confirmText?: string;
cancelText?: string;
confirmDisabled?: boolean;
cancelDisabled?: boolean;
}) {
return (
<CM
Expand All @@ -27,6 +31,8 @@ export function ConfirmModal({
onOK={onConfirm}
onCancel={closeModal}
onEscKeypress={closeModal}
bOKDisabled={confirmDisabled}
bCancelDisabled={cancelDisabled}
>
<StyleProvider>{children}</StyleProvider>
</CM>
Expand Down
10 changes: 9 additions & 1 deletion src/modules/settings/profile/components/OnlineView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Focusable } from "@decky/ui";
import { UploadProfileModal } from "@/lib";
import { DialogButton, Focusable, showModal } from "@decky/ui";
import { useProfileContext } from "../state";
import { ProfileInstalledEntry } from "./ProfileInstalledEntry";
import { ProfileUploadedEntry } from "./ProfileUploadedEntry";
Expand Down Expand Up @@ -47,6 +48,13 @@ export function OnlineView() {
)}
</Focusable>
)}
<DialogButton
onClick={() => {
showModal(<UploadProfileModal eligibleProfiles={localProfiles} />);
}}
>
Upload Profile
</DialogButton>
</Focusable>
);
}

0 comments on commit aa39f37

Please sign in to comment.