Skip to content

Commit f724bb5

Browse files
committed
finish profile page view (excluding uploading)
1 parent bbb054a commit f724bb5

File tree

6 files changed

+93
-168
lines changed

6 files changed

+93
-168
lines changed

src/modules/settings/profile/components/LoggedInView.tsx

-42
This file was deleted.

src/modules/settings/profile/components/LoggedOutView.tsx

-29
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Focusable } from "@decky/ui";
2+
import { ProfileInstalledEntry } from "./ProfileInstalledEntry";
3+
import { useProfileContext } from "../state";
4+
import { SectionTitle } from "./SectionTitle";
5+
import { SectionSubtitle } from "./SectionSubtitle";
6+
import { ProfileUploadedEntry } from "./ProfileUploadedEntry";
7+
8+
export function OnlineView() {
9+
const { downloadedProfiles, localProfiles, uploadedProfiles } = useProfileContext();
10+
return (
11+
<Focusable className="flex flex-col gap-4">
12+
{downloadedProfiles.length > 0 && (
13+
<>
14+
<Focusable className="flex flex-col gap-2">
15+
<SectionTitle>Downloaded Profiles</SectionTitle>
16+
<Focusable className="flex flex-col gap-1">
17+
{downloadedProfiles.map((profile) => (
18+
<ProfileInstalledEntry key={profile.id} data={profile} />
19+
))}
20+
</Focusable>
21+
</Focusable>
22+
<div className="cl_divider" />
23+
</>
24+
)}
25+
{(localProfiles.length > 0 || uploadedProfiles.length > 0) && (
26+
<Focusable className="flex flex-col gap-2">
27+
<SectionTitle>Your Profiles</SectionTitle>
28+
{localProfiles.length > 0 && (
29+
<Focusable className="flex flex-col">
30+
<SectionSubtitle>Local</SectionSubtitle>
31+
<Focusable className="flex flex-col gap-1">
32+
{localProfiles.map((profile) => (
33+
<ProfileInstalledEntry key={profile.id} data={profile} />
34+
))}
35+
</Focusable>
36+
</Focusable>
37+
)}
38+
{uploadedProfiles.length > 0 && (
39+
<Focusable className="flex flex-col">
40+
<SectionSubtitle>On DeckThemes</SectionSubtitle>
41+
<Focusable className="flex flex-col gap-1">
42+
{uploadedProfiles.map((profile) => (
43+
<ProfileUploadedEntry key={profile.id} data={profile} />
44+
))}
45+
</Focusable>
46+
</Focusable>
47+
)}
48+
</Focusable>
49+
)}
50+
</Focusable>
51+
);
52+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
export * from "./ProfileInstalledEntry";
2-
export * from "./ProfileUploadedEntry";
3-
export * from "./SectionSubtitle";
4-
export * from "./SectionTitle";
1+
export * from "./OfflineView";
2+
export * from "./OnlineView";
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,37 @@
1-
import { useCSSLoaderActions, useCSSLoaderValues } from "@/backend";
21
import { PresetSelectionDropdown } from "@/lib";
3-
import { Flags, PartialCSSThemeInfo, Theme } from "@/types";
4-
import { DialogButton, Focusable } from "@decky/ui";
5-
import { useEffect, useMemo, useState } from "react";
2+
import { Focusable } from "@decky/ui";
63
import { ProfileContextProvider, useProfileContext } from "../state";
7-
import { OfflineView } from "../components/OfflineView";
8-
import { LoggedInView } from "../components/LoggedInView";
9-
import { LoggedOutView } from "../components/LoggedOutView";
4+
import { OfflineView, OnlineView } from "../components";
5+
import { ImSpinner5 } from "react-icons/im";
106

117
export function ProfileSettingsPage() {
128
return (
139
<ProfileContextProvider>
14-
<Focusable className="cl_settingspage_container flex flex-col gap-4">
15-
<PresetSelectionDropdown noBottomSeparator />
16-
<div className="cl_divider" />
17-
<ProfileSettingsContent />
18-
</Focusable>
10+
<ProfileSettingsPageContent />
1911
</ProfileContextProvider>
2012
);
2113
}
2214

23-
function ProfileSettingsContent() {
15+
function ProfileSettingsPageContent() {
16+
const { loading } = useProfileContext();
17+
return (
18+
<Focusable className="cl_settingspage_container flex flex-col gap-4">
19+
<PresetSelectionDropdown noBottomSeparator />
20+
<div className="cl_divider" />
21+
<ProfileSettingsModeSwitcher />
22+
{loading && (
23+
<div className="h-full w-full flex items-center justify-center gap-4">
24+
<ImSpinner5 className="cl_spinny" size={48} />
25+
</div>
26+
)}
27+
</Focusable>
28+
);
29+
}
30+
31+
function ProfileSettingsModeSwitcher() {
2432
const { displayMode } = useProfileContext();
25-
if (displayMode === "loggedin") {
26-
return <LoggedInView />;
27-
}
28-
if (displayMode === "loggedout") {
29-
return <LoggedOutView />;
33+
if (displayMode === "online") {
34+
return <OnlineView />;
3035
}
3136
return <OfflineView />;
3237
}
33-
34-
// function UploadProfileButton() {
35-
// const { publishProfile } = useCSSLoaderActions();
36-
// return (
37-
// <DialogButton
38-
// onClick={() => {
39-
// publishProfile("LCD Hero.profile", false);
40-
// }}
41-
// >
42-
// Upload Profile
43-
// </DialogButton>
44-
// );
45-
// }

src/modules/settings/profile/state/ProfileContext.tsx

+16-62
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const ProfileContext = createContext<IProfileContext>({} as IProfileContext);
77
// TODO: Potentially this should be moved to @cssloader as it isn't decky dependent
88
// TODO: Also, this should be zustand using .subscribe on the cssloader store, I just was lazy implementing it this way here
99
interface IProfileContextValues {
10-
displayMode: "offline" | "loggedout" | "loggedin";
10+
displayMode: "offline" | "online";
11+
loading: boolean;
1112
downloadedProfiles: Theme[];
1213
localProfiles: Theme[];
1314
uploadedProfiles: PartialCSSThemeInfo[];
@@ -24,15 +25,20 @@ export function ProfileContextProvider({ children }: { children: React.ReactNode
2425
const { getUploadedThemes } = useCSSLoaderActions();
2526
const profiles = themes.filter((e) => e.flags.includes(Flags.isPreset));
2627

27-
const [displayMode, setDisplayMode] = useState<"offline" | "loggedout" | "loggedin">("offline");
28-
const localProfiles = profiles.filter(
29-
(e) => updateStatuses.find((status) => status[0] === e.id)?.[1] === "local"
30-
);
28+
const [displayMode, setDisplayMode] = useState<"offline" | "online">("offline");
29+
const [loading, setLoading] = useState(true);
3130

3231
const [uploadedProfileRemoteEntries, setUploadedProfileRemoteEntries] = useState<
3332
PartialCSSThemeInfo[]
3433
>([]);
3534

35+
const localProfiles = profiles.filter((e) => {
36+
const isLocal = updateStatuses.find((status) => status[0] === e.id)?.[1] === "local";
37+
const isInUploaded = uploadedProfileRemoteEntries.some(
38+
(uploadedProfile) => uploadedProfile.id === e.id
39+
);
40+
return isLocal && !isInUploaded;
41+
});
3642
const downloadedProfiles = profiles.filter((profile) => {
3743
const isInLocal = localProfiles.some((localProfile) => localProfile.id === profile.id);
3844
const isInUploaded = uploadedProfileRemoteEntries.some(
@@ -46,18 +52,20 @@ export function ProfileContextProvider({ children }: { children: React.ReactNode
4652
setDisplayMode("offline");
4753
return;
4854
}
55+
56+
setDisplayMode("online");
4957
if (!apiMeData) {
50-
setDisplayMode("loggedout");
5158
return;
5259
}
5360

61+
setLoading(true);
5462
// Logged in mode, separate downloaded and local, and show uploaded profiles
5563
const uploadedThemes = await getUploadedThemes();
5664
const uploadedProfileRemoteEntries = uploadedThemes.filter((theme) =>
5765
theme.targets.includes("Profile")
5866
);
5967
setUploadedProfileRemoteEntries(uploadedProfileRemoteEntries);
60-
setDisplayMode("loggedin");
68+
setLoading(false);
6169
}
6270

6371
useEffect(() => {
@@ -68,6 +76,7 @@ export function ProfileContextProvider({ children }: { children: React.ReactNode
6876
<ProfileContext.Provider
6977
value={{
7078
displayMode,
79+
loading,
7180
downloadedProfiles,
7281
localProfiles,
7382
uploadedProfiles: uploadedProfileRemoteEntries,
@@ -80,58 +89,3 @@ export function ProfileContextProvider({ children }: { children: React.ReactNode
8089
}
8190

8291
export const useProfileContext = () => useContext(ProfileContext);
83-
84-
// const profileStore = createStore<IProfileStore>((set, get) => {
85-
// return {
86-
// displayMode: "offline",
87-
// downloadedProfiles: [],
88-
// localProfiles: [],
89-
// uploadedProfiles: [],
90-
// async initialize() {
91-
// const { apiMeData, updateStatuses, themes, getUploadedThemes } = getCSSLoaderState();
92-
// const profiles = themes.filter((e) => e.flags.includes(Flags.isPreset));
93-
// if (!updateStatuses) {
94-
// // Offline mode, no profile sorting, just one list
95-
// set({ displayMode: "offline", downloadedProfiles: profiles });
96-
// return;
97-
// }
98-
99-
// let downloadedProfiles: Theme[] = [];
100-
// let localProfiles: Theme[] = [];
101-
// profiles.forEach((e) => {
102-
// if (updateStatuses.find((status) => status[0] === e.id)?.[1] === "local") {
103-
// localProfiles.push(e);
104-
// } else {
105-
// downloadedProfiles.push(e);
106-
// }
107-
// });
108-
109-
// if (!apiMeData) {
110-
// // Logged out mode, separate downloaded and local, but no 'Uploaded' section
111-
// set({ displayMode: "loggedout", downloadedProfiles, localProfiles });
112-
// return;
113-
// }
114-
115-
// // Logged in mode, separate downloaded and local, and show uploaded profiles
116-
// const uploadedThemes = await getUploadedThemes();
117-
// const uploadedProfileRemoteEntries = uploadedThemes.filter((theme) =>
118-
// theme.targets.includes("Profile")
119-
// );
120-
121-
// // Since uploaded profiles are also technically 'downloaded', we have to manually filter them out
122-
// downloadedProfiles = downloadedProfiles.filter(
123-
// (profile) =>
124-
// !uploadedProfileRemoteEntries.some((uploadedProfile) => uploadedProfile.id === profile.id)
125-
// );
126-
// set({
127-
// displayMode: "loggedin",
128-
// downloadedProfiles,
129-
// localProfiles,
130-
// uploadedProfiles: uploadedProfileRemoteEntries,
131-
// });
132-
// },
133-
// };
134-
// });
135-
136-
// export const useProfileStoreValues = generateStoreSelector<IProfileStoreValues>(profileStore);
137-
// export const useProfileStoreActions = generateStoreSelector<IProfileStoreActions>(profileStore);

0 commit comments

Comments
 (0)