Skip to content

Commit

Permalink
WIP: further work on expanded view
Browse files Browse the repository at this point in the history
  • Loading branch information
beebls committed Aug 22, 2024
1 parent fd0767e commit 013ee9b
Show file tree
Hide file tree
Showing 8 changed files with 492 additions and 84 deletions.
2 changes: 1 addition & 1 deletion src/backend-impl/decky-theme-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
102 changes: 102 additions & 0 deletions src/modules/expanded-view/components/ExpandedViewButtonsSection.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);

const isWorking = useCSSLoaderValue("isWorking");

const installTheme = useCSSLoaderAction("installTheme");

const installStatus = useThemeInstallState(data);

async function handleStar() {
setStarButtonBlurred(true);
await toggleStar();
setStarButtonBlurred(false);
}

return (
<div className="cl_expandedview_buttonscontainer">
{/* Star */}
<div className="cl_expandedview_singlebuttoncontainer">
<div className="w-full justify-between">
<div className="flex gap-1 items-center">
{isStarred ? <FaStar /> : <FaRegStar />}
{/* Need to make the text size smaller or else it wraps */}
<span style={{ fontSize: data.starCount >= 100 ? "0.75rem" : "1rem" }}>
{shortenNumber(data.starCount) ?? data.starCount} Star
{data.starCount === 1 ? "" : "s"}
</span>
</div>
<DialogButton
className="cl_expandedview_starbutton"
onClick={() => void handleStar()}
disabled={starButtonBlurred || !apiFullToken}
>
<div className="flex items-center justify-center gap-1/4">
<span>
{!apiFullToken ? "Log In to Star" : isStarred ? "Unstar Theme" : "Star Theme"}
</span>
</div>
</DialogButton>
</div>
</div>

{/* Download / Configure */}
<div className="cl_expandedview_singlebuttoncontainer">
<div className="flex flex-col gap-1">
<span className="cl_expandedview_installtext">Install {data.displayName}</span>
<span className="font-bold">
{shortenNumber(data.download.downloadCount) ?? data.download.downloadCount} Download
{data.download.downloadCount === 1 ? "" : "s"}
</span>
<DialogButton
// @ts-ignore
ref={downloadButtonRef}
className="cl_expandedview_bluebutton"
disabled={isWorking}
onClick={() => {
installTheme(data.id);
}}
>
<span className="CssLoader_ThemeBrowser_ExpandedView_InstallText">
{installStatus === "installed" && "Reinstall"}
{installStatus === "outdated" && "Update"}
{installStatus === "notinstalled" && "Install"}
</span>
</DialogButton>
{installStatus === "installed" && (
<DialogButton
onClick={() => {
// TODO: THEME SETTINGS MODAL
// showModal(
// <ThemeSettingsModalRoot
// selectedTheme={
// installedThemes.find((e) => e.id === fullThemeData.id)?.id ||
// // using name here because in submissions id is different
// installedThemes.find((e) => e.name === fullThemeData.name)!.id
// }
// />
// );
}}
className="relative"
>
<ImCog className="absolute-center" />
</DialogButton>
)}
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -21,6 +22,61 @@ export function ExpandedViewScrollingSection() {
>
<div className="cl_expandedview_themedatacontainer">
<ExpandedViewImageContainer />
<div className="flex flex-col gap-1">
{/* Title / Version */}
<div className="flex gap-2">
<span className="cl_expandedview_title">{data.displayName}</span>
<span className="cl_expandedview_version">{data.version}</span>
</div>
{/* Author / Modified Date */}
<div className="flex gap-1 cl_expandedview_graytext">
<Focusable
onOKActionDescription="View Profile"
focusClassName="gpfocuswithin"
onActivate={() => {
// TODO: MODAL
// showModal(<AuthorViewModalRoot authorData={fullThemeData.author} />);
}}
>
By <span className="cl_expandedview_bluetext">{data.specifiedAuthor}</span>
</Focusable>
<span>Last Updated {new Date(data.updated).toLocaleDateString()}</span>
</div>
{/* Description */}
<Focusable
focusWithinClassName="gpfocuswihtin"
className="flex flex-col gap-1"
onActivate={() => {}}
>
<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>
)}
</span>
</Focusable>
{/* Targets */}
<div className="flex flex-col gap-1">
<span className="font-bold">Targets</span>
<div className="flex gap-1">
{data.targets.map((target) => (
<DialogButton
onOKActionDescription={`View Other '${target}' Themes`}
onClick={() => {
// TODO: target navigation
// setGlobalState("themeSearchOpts", { ...themeSearchOpts, filters: e });
// setGlobalState("currentTab", "ThemeBrowser");
// setGlobalState("forceScrollBackUp", true);
// Navigation.NavigateBack();
}}
className="cl_expandedview_targetbutton"
>
{target}
</DialogButton>
))}
</div>
</div>
</div>
</div>
</ScrollPanelGroup>
);
Expand Down
1 change: 1 addition & 0 deletions src/modules/expanded-view/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./ExpandedViewLoadingPage";
export * from "./ExpandedViewScrollingSection";
export * from "./ExpandedViewCssVariables";
export * from "./ExpandedViewButtonsSection";
65 changes: 50 additions & 15 deletions src/modules/expanded-view/context/ExpandedViewStore.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -7,6 +7,7 @@ interface IExpandedViewStoreValues {
loaded: boolean;
error: string | null;
openedId: string | null;
isStarred: boolean;
data: FullCSSThemeInfo;
focusedImageId: string;
imageAreaStyleKeys: {
Expand All @@ -22,7 +23,7 @@ interface IExpandedViewStoreValues {

interface IExpandedViewStoreActions {
openTheme: (themeId: string) => Promise<void>;
downloadTheme: () => Promise<void>;
toggleStar: () => Promise<void>;
setFocusedImage: (imageId: string) => void;
close: () => void;
}
Expand Down Expand Up @@ -55,6 +56,7 @@ const expandedViewStore = createStore<IExpandedViewStore>((set, get) => {
loaded: false,
openedId: null,
data: {} as FullCSSThemeInfo,
isStarred: false,
error: null,
focusedImageId: "",
imageAreaStyleKeys: {
Expand All @@ -69,31 +71,64 @@ const expandedViewStore = createStore<IExpandedViewStore>((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<FullCSSThemeInfo>(`/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,
Expand Down
2 changes: 2 additions & 0 deletions src/modules/expanded-view/pages/ExpandedViewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ExpandedViewLoadingPage,
ExpandedViewCssVariables,
ExpandedViewScrollingSection,
ExpandedViewButtonsSection,
} from "../components";
import { useExpandedViewValue } from "../context";

Expand All @@ -17,6 +18,7 @@ export function ExpandedViewPage() {
<div className="cl_expandedview_container">
<ExpandedViewCssVariables />
<ExpandedViewScrollingSection />
<ExpandedViewButtonsSection />
</div>
);
}
Loading

0 comments on commit 013ee9b

Please sign in to comment.