mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: adding change hero
This commit is contained in:
parent
586df616e8
commit
035e424a76
48 changed files with 520 additions and 500 deletions
|
@ -26,6 +26,10 @@ globalStyle("::-webkit-scrollbar-thumb", {
|
|||
borderRadius: "24px",
|
||||
});
|
||||
|
||||
globalStyle("::-webkit-scrollbar-thumb:hover", {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.16)",
|
||||
});
|
||||
|
||||
globalStyle("html, body, #root, main", {
|
||||
height: "100%",
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
|||
|
||||
const handleHover = useCallback(() => {
|
||||
if (!stats) {
|
||||
window.electron.getGameStats(game.objectID, game.shop).then((stats) => {
|
||||
window.electron.getGameStats(game.objectId, game.shop).then((stats) => {
|
||||
setStats(stats);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -140,7 +140,10 @@ export function Sidebar() {
|
|||
event: React.MouseEvent,
|
||||
game: LibraryGame
|
||||
) => {
|
||||
const path = buildGameDetailsPath(game);
|
||||
const path = buildGameDetailsPath({
|
||||
...game,
|
||||
objectId: game.objectID,
|
||||
});
|
||||
if (path !== location.pathname) {
|
||||
navigate(path);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export const gameDetailsContext = createContext<GameDetailsContext>({
|
|||
gameTitle: "",
|
||||
isGameRunning: false,
|
||||
isLoading: false,
|
||||
objectID: undefined,
|
||||
objectId: undefined,
|
||||
gameColor: "",
|
||||
showRepacksModal: false,
|
||||
showGameOptionsModal: false,
|
||||
|
@ -97,7 +97,7 @@ export function GameDetailsContextProvider({
|
|||
|
||||
const updateGame = useCallback(async () => {
|
||||
return window.electron
|
||||
.getGameByObjectID(objectId!)
|
||||
.getGameByObjectId(objectId!)
|
||||
.then((result) => setGame(result));
|
||||
}, [setGame, objectId]);
|
||||
|
||||
|
@ -199,7 +199,7 @@ export function GameDetailsContextProvider({
|
|||
gameTitle,
|
||||
isGameRunning,
|
||||
isLoading,
|
||||
objectID: objectId,
|
||||
objectId,
|
||||
gameColor,
|
||||
showGameOptionsModal,
|
||||
showRepacksModal,
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface GameDetailsContext {
|
|||
gameTitle: string;
|
||||
isGameRunning: boolean;
|
||||
isLoading: boolean;
|
||||
objectID: string | undefined;
|
||||
objectId: string | undefined;
|
||||
gameColor: string;
|
||||
showRepacksModal: boolean;
|
||||
showGameOptionsModal: boolean;
|
||||
|
|
|
@ -50,6 +50,7 @@ export function RepacksContextProvider({ children }: RepacksContextProps) {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("CALLED");
|
||||
indexRepacks();
|
||||
}, [indexRepacks]);
|
||||
|
||||
|
|
13
src/renderer/src/declaration.d.ts
vendored
13
src/renderer/src/declaration.d.ts
vendored
|
@ -51,27 +51,24 @@ declare global {
|
|||
searchGames: (query: string) => Promise<CatalogueEntry[]>;
|
||||
getCatalogue: (category: CatalogueCategory) => Promise<CatalogueEntry[]>;
|
||||
getGameShopDetails: (
|
||||
objectID: string,
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
language: string
|
||||
) => Promise<ShopDetails | null>;
|
||||
getRandomGame: () => Promise<Steam250Game>;
|
||||
getHowLongToBeat: (
|
||||
objectID: string,
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
title: string
|
||||
) => Promise<HowLongToBeatCategory[] | null>;
|
||||
getGames: (
|
||||
take?: number,
|
||||
prevCursor?: number
|
||||
) => Promise<{ results: CatalogueEntry[]; cursor: number }>;
|
||||
getGames: (take?: number, skip?: number) => Promise<CatalogueEntry[]>;
|
||||
searchGameRepacks: (query: string) => Promise<GameRepack[]>;
|
||||
getGameStats: (objectId: string, shop: GameShop) => Promise<GameStats>;
|
||||
getTrendingGames: () => Promise<TrendingGame[]>;
|
||||
|
||||
/* Library */
|
||||
addGameToLibrary: (
|
||||
objectID: string,
|
||||
objectId: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
) => Promise<void>;
|
||||
|
@ -87,7 +84,7 @@ declare global {
|
|||
removeGameFromLibrary: (gameId: number) => Promise<void>;
|
||||
removeGame: (gameId: number) => Promise<void>;
|
||||
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
||||
getGameByObjectID: (objectID: string) => Promise<Game | null>;
|
||||
getGameByObjectId: (objectId: string) => Promise<Game | null>;
|
||||
onGamesRunning: (
|
||||
cb: (
|
||||
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||
|
|
|
@ -27,11 +27,11 @@ export const getSteamLanguage = (language: string) => {
|
|||
};
|
||||
|
||||
export const buildGameDetailsPath = (
|
||||
game: { shop: GameShop; objectID: string; title: string },
|
||||
game: { shop: GameShop; objectId: string; title: string },
|
||||
params: Record<string, string> = {}
|
||||
) => {
|
||||
const searchParams = new URLSearchParams({ title: game.title, ...params });
|
||||
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
|
||||
return `/game/${game.shop}/${game.objectId}?${searchParams.toString()}`;
|
||||
};
|
||||
|
||||
export const darkenColor = (color: string, amount: number, alpha: number = 1) =>
|
||||
|
|
|
@ -64,7 +64,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||
<Route path="/" Component={Home} />
|
||||
<Route path="/catalogue" Component={Catalogue} />
|
||||
<Route path="/downloads" Component={Downloads} />
|
||||
<Route path="/game/:shop/:objectID" Component={GameDetails} />
|
||||
<Route path="/game/:shop/:objectId" Component={GameDetails} />
|
||||
<Route path="/search" Component={SearchResults} />
|
||||
<Route path="/settings" Component={Settings} />
|
||||
<Route path="/profile/:userId" Component={Profile} />
|
||||
|
|
|
@ -24,12 +24,10 @@ export function Catalogue() {
|
|||
|
||||
const contentRef = useRef<HTMLElement>(null);
|
||||
|
||||
const cursorRef = useRef<number>(0);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const cursor = Number(searchParams.get("cursor") ?? 0);
|
||||
const skip = Number(searchParams.get("skip") ?? 0);
|
||||
|
||||
const handleGameClick = (game: CatalogueEntry) => {
|
||||
dispatch(clearSearch());
|
||||
|
@ -42,11 +40,10 @@ export function Catalogue() {
|
|||
setSearchResults([]);
|
||||
|
||||
window.electron
|
||||
.getGames(24, cursor)
|
||||
.then(({ results, cursor }) => {
|
||||
.getGames(24, skip)
|
||||
.then((results) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
cursorRef.current = cursor;
|
||||
setSearchResults(results);
|
||||
resolve(null);
|
||||
}, 500);
|
||||
|
@ -55,11 +52,11 @@ export function Catalogue() {
|
|||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [dispatch, cursor, searchParams]);
|
||||
}, [dispatch, skip, searchParams]);
|
||||
|
||||
const handleNextPage = () => {
|
||||
const params = new URLSearchParams({
|
||||
cursor: cursorRef.current.toString(),
|
||||
skip: String(skip + 24),
|
||||
});
|
||||
|
||||
navigate(`/catalogue?${params.toString()}`);
|
||||
|
@ -80,7 +77,7 @@ export function Catalogue() {
|
|||
<Button
|
||||
onClick={() => navigate(-1)}
|
||||
theme="outline"
|
||||
disabled={cursor === 0 || isLoading}
|
||||
disabled={skip === 0 || isLoading}
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
{t("previous_page")}
|
||||
|
@ -103,7 +100,7 @@ export function Catalogue() {
|
|||
<>
|
||||
{searchResults.map((game) => (
|
||||
<GameCard
|
||||
key={game.objectID}
|
||||
key={game.objectId}
|
||||
game={game}
|
||||
onClick={() => handleGameClick(game)}
|
||||
/>
|
||||
|
|
|
@ -93,6 +93,7 @@ export const downloadRightContent = style({
|
|||
padding: `${SPACING_UNIT * 2}px`,
|
||||
flex: "1",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
background: "linear-gradient(90deg, transparent 20%, rgb(0 0 0 / 20%) 100%)",
|
||||
});
|
||||
|
||||
export const downloadActions = style({
|
||||
|
|
|
@ -227,7 +227,14 @@ export function DownloadGroup({
|
|||
<button
|
||||
type="button"
|
||||
className={styles.downloadTitle}
|
||||
onClick={() => navigate(buildGameDetailsPath(game))}
|
||||
onClick={() =>
|
||||
navigate(
|
||||
buildGameDetailsPath({
|
||||
...game,
|
||||
objectId: game.objectID,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
{game.title}
|
||||
</button>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Modal, ModalProps, TextField } from "@renderer/components";
|
||||
import { Modal, ModalProps } from "@renderer/components";
|
||||
import { useContext, useMemo } from "react";
|
||||
import { cloudSyncContext } from "@renderer/context";
|
||||
|
||||
|
@ -11,6 +11,8 @@ export function CloudSyncFilesModal({
|
|||
}: CloudSyncFilesModalProps) {
|
||||
const { backupPreview } = useContext(cloudSyncContext);
|
||||
|
||||
console.log(backupPreview);
|
||||
|
||||
const files = useMemo(() => {
|
||||
if (!backupPreview) {
|
||||
return [];
|
||||
|
@ -51,22 +53,24 @@ export function CloudSyncFilesModal({
|
|||
))}
|
||||
</div> */}
|
||||
|
||||
<ul
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
listStyle: "none",
|
||||
gap: 16,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{files.map((file) => (
|
||||
<li key={file.path}>
|
||||
<TextField value={file.path} readOnly />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: "left" }}>Arquivo</th>
|
||||
<th style={{ textAlign: "left" }}>Hash</th>
|
||||
<th style={{ textAlign: "left" }}>Tamanho</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
<tr key={file.path}>
|
||||
<td style={{ textAlign: "left" }}>{file.path}</td>
|
||||
<td style={{ textAlign: "left" }}>{file.change}</td>
|
||||
<td style={{ textAlign: "left" }}>{file.path}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
|||
setShowCloudSyncFilesModal,
|
||||
} = useContext(cloudSyncContext);
|
||||
|
||||
const { objectID, shop, gameTitle } = useContext(gameDetailsContext);
|
||||
const { objectId, shop, gameTitle } = useContext(gameDetailsContext);
|
||||
|
||||
const { showSuccessToast, showErrorToast } = useToast();
|
||||
|
||||
|
@ -63,13 +63,13 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
|||
|
||||
useEffect(() => {
|
||||
gameBackupsTable
|
||||
.where({ shop: shop, objectId: objectID })
|
||||
.where({ shop: shop, objectId })
|
||||
.last()
|
||||
.then((lastBackup) => setLastBackup(lastBackup || null));
|
||||
|
||||
const removeBackupDownloadProgressListener =
|
||||
window.electron.onBackupDownloadProgress(
|
||||
objectID!,
|
||||
objectId!,
|
||||
shop,
|
||||
(progressEvent) => {
|
||||
setBackupDownloadProgress(progressEvent);
|
||||
|
@ -79,7 +79,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
|||
return () => {
|
||||
removeBackupDownloadProgressListener();
|
||||
};
|
||||
}, [backupPreview, objectID, shop]);
|
||||
}, [backupPreview, objectId, shop]);
|
||||
|
||||
const handleBackupInstallClick = async (artifactId: string) => {
|
||||
setBackupDownloadProgress(null);
|
||||
|
|
|
@ -26,7 +26,7 @@ export function GameDetailsContent() {
|
|||
const { t } = useTranslation("game_details");
|
||||
|
||||
const {
|
||||
objectID,
|
||||
objectId,
|
||||
shopDetails,
|
||||
game,
|
||||
gameColor,
|
||||
|
@ -42,7 +42,7 @@ export function GameDetailsContent() {
|
|||
const [backdropOpactiy, setBackdropOpacity] = useState(1);
|
||||
|
||||
const handleHeroLoad = async () => {
|
||||
const output = await average(steamUrlBuilder.libraryHero(objectID!), {
|
||||
const output = await average(steamUrlBuilder.libraryHero(objectId!), {
|
||||
amount: 1,
|
||||
format: "hex",
|
||||
});
|
||||
|
@ -56,7 +56,7 @@ export function GameDetailsContent() {
|
|||
|
||||
useEffect(() => {
|
||||
setBackdropOpacity(1);
|
||||
}, [objectID]);
|
||||
}, [objectId]);
|
||||
|
||||
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
|
||||
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
|
||||
|
@ -90,7 +90,7 @@ export function GameDetailsContent() {
|
|||
return (
|
||||
<div className={styles.wrapper({ blurredContent: hasNSFWContentBlocked })}>
|
||||
<img
|
||||
src={steamUrlBuilder.libraryHero(objectID!)}
|
||||
src={steamUrlBuilder.libraryHero(objectId!)}
|
||||
className={styles.heroImage}
|
||||
alt={game?.title}
|
||||
onLoad={handleHeroLoad}
|
||||
|
@ -116,7 +116,7 @@ export function GameDetailsContent() {
|
|||
>
|
||||
<div className={styles.heroContent}>
|
||||
<img
|
||||
src={steamUrlBuilder.logo(objectID!)}
|
||||
src={steamUrlBuilder.logo(objectId!)}
|
||||
className={styles.gameLogo}
|
||||
alt={game?.title}
|
||||
/>
|
||||
|
|
|
@ -33,7 +33,7 @@ export function GameDetails() {
|
|||
const [randomGame, setRandomGame] = useState<Steam250Game | null>(null);
|
||||
const [randomizerLocked, setRandomizerLocked] = useState(false);
|
||||
|
||||
const { objectID, shop } = useParams();
|
||||
const { objectId, shop } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const fromRandomizer = searchParams.get("fromRandomizer");
|
||||
|
@ -50,7 +50,7 @@ export function GameDetails() {
|
|||
window.electron.getRandomGame().then((randomGame) => {
|
||||
setRandomGame(randomGame);
|
||||
});
|
||||
}, [objectID]);
|
||||
}, [objectId]);
|
||||
|
||||
const handleRandomizerClick = () => {
|
||||
if (randomGame) {
|
||||
|
@ -82,7 +82,7 @@ export function GameDetails() {
|
|||
<GameDetailsContextProvider
|
||||
gameTitle={gameTitle!}
|
||||
shop={shop! as GameShop}
|
||||
objectId={objectID!}
|
||||
objectId={objectId!}
|
||||
>
|
||||
<GameDetailsContextConsumer>
|
||||
{({
|
||||
|
@ -105,7 +105,7 @@ export function GameDetails() {
|
|||
) => {
|
||||
await startDownload({
|
||||
repackId: repack.id,
|
||||
objectID: objectID!,
|
||||
objectId: objectId!,
|
||||
title: gameTitle,
|
||||
downloader,
|
||||
shop: shop as GameShop,
|
||||
|
@ -125,7 +125,7 @@ export function GameDetails() {
|
|||
|
||||
return (
|
||||
<CloudSyncContextProvider
|
||||
objectId={objectID!}
|
||||
objectId={objectId!}
|
||||
shop={shop! as GameShop}
|
||||
>
|
||||
<CloudSyncContextConsumer>
|
||||
|
|
|
@ -18,7 +18,7 @@ export function HeroPanelActions() {
|
|||
game,
|
||||
repacks,
|
||||
isGameRunning,
|
||||
objectID,
|
||||
objectId,
|
||||
gameTitle,
|
||||
setShowGameOptionsModal,
|
||||
setShowRepacksModal,
|
||||
|
@ -39,7 +39,7 @@ export function HeroPanelActions() {
|
|||
setToggleLibraryGameDisabled(true);
|
||||
|
||||
try {
|
||||
await window.electron.addGameToLibrary(objectID!, gameTitle, "steam");
|
||||
await window.electron.addGameToLibrary(objectId!, gameTitle, "steam");
|
||||
|
||||
updateLibrary();
|
||||
updateGame();
|
||||
|
|
|
@ -24,11 +24,11 @@ export function Sidebar() {
|
|||
const { numberFormatter } = useFormat();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (objectID) {
|
||||
// if (objectId) {
|
||||
// setHowLongToBeat({ isLoading: true, data: null });
|
||||
|
||||
// window.electron
|
||||
// .getHowLongToBeat(objectID, "steam", gameTitle)
|
||||
// .getHowLongToBeat(objectId, "steam", gameTitle)
|
||||
// .then((howLongToBeat) => {
|
||||
// setHowLongToBeat({ isLoading: false, data: howLongToBeat });
|
||||
// })
|
||||
|
@ -36,7 +36,7 @@ export function Sidebar() {
|
|||
// setHowLongToBeat({ isLoading: false, data: null });
|
||||
// });
|
||||
// }
|
||||
// }, [objectID, gameTitle]);
|
||||
// }, [objectId, gameTitle]);
|
||||
|
||||
return (
|
||||
<aside className={styles.contentSidebar}>
|
||||
|
|
|
@ -186,7 +186,7 @@ export function Home() {
|
|||
))
|
||||
: catalogue[currentCatalogueCategory].map((result) => (
|
||||
<GameCard
|
||||
key={result.objectID}
|
||||
key={result.objectId}
|
||||
game={result}
|
||||
onClick={() => navigate(buildGameDetailsPath(result))}
|
||||
/>
|
||||
|
|
|
@ -115,7 +115,7 @@ export function SearchResults() {
|
|||
<>
|
||||
{searchResults.map((game) => (
|
||||
<GameCard
|
||||
key={game.objectID}
|
||||
key={game.objectId}
|
||||
game={game}
|
||||
onClick={() => handleGameClick(game)}
|
||||
/>
|
||||
|
|
|
@ -15,7 +15,7 @@ export const gameCover = style({
|
|||
height: "172%",
|
||||
position: "absolute",
|
||||
background:
|
||||
"linear-gradient(35deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.07) 51.5%, rgba(255, 255, 255, 0.15) 54%, rgba(255, 255, 255, 0.15) 100%);",
|
||||
"linear-gradient(35deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.07) 51.5%, rgba(255, 255, 255, 0.15) 54%, rgba(255, 255, 255, 0.15) 100%)",
|
||||
transition: "all ease 0.3s",
|
||||
transform: "translateY(-36%)",
|
||||
opacity: "0.5",
|
||||
|
@ -189,3 +189,15 @@ export const defaultAvatarWrapper = style({
|
|||
border: `solid 1px ${vars.color.border}`,
|
||||
borderRadius: "4px",
|
||||
});
|
||||
|
||||
export const achievementsProgressBar = style({
|
||||
width: "100%",
|
||||
height: "4px",
|
||||
transition: "all ease 0.2s",
|
||||
"::-webkit-progress-bar": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||
},
|
||||
"::-webkit-progress-value": {
|
||||
backgroundColor: vars.color.muted,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ import { steamUrlBuilder } from "@shared";
|
|||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
|
||||
import * as styles from "./profile-content.css";
|
||||
import { ClockIcon, TelescopeIcon } from "@primer/octicons-react";
|
||||
import { ClockIcon, TelescopeIcon, TrophyIcon } from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { LockedProfile } from "./locked-profile";
|
||||
|
@ -15,7 +15,10 @@ import { ReportProfile } from "../report-profile/report-profile";
|
|||
import { FriendsBox } from "./friends-box";
|
||||
import { RecentGamesBox } from "./recent-games-box";
|
||||
import { UserGame } from "@types";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
import {
|
||||
buildGameDetailsPath,
|
||||
formatDownloadProgress,
|
||||
} from "@renderer/helpers";
|
||||
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
||||
|
||||
export function ProfileContent() {
|
||||
|
@ -44,7 +47,7 @@ export function ProfileContent() {
|
|||
const buildUserGameDetailsPath = (game: UserGame) =>
|
||||
buildGameDetailsPath({
|
||||
...game,
|
||||
objectID: game.objectId,
|
||||
objectId: game.objectId,
|
||||
});
|
||||
|
||||
const formatPlayTime = useCallback(
|
||||
|
@ -115,6 +118,7 @@ export function ProfileContent() {
|
|||
borderRadius: 4,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
}}
|
||||
className={styles.game}
|
||||
>
|
||||
|
@ -126,23 +130,85 @@ export function ProfileContent() {
|
|||
className={styles.gameCover}
|
||||
onClick={() => navigate(buildUserGameDetailsPath(game))}
|
||||
>
|
||||
<div style={{ position: "absolute", padding: 4 }}>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
background:
|
||||
"linear-gradient(0deg, rgba(0, 0, 0, 0.7) 20%, transparent 100%)",
|
||||
padding: 8,
|
||||
}}
|
||||
>
|
||||
<small
|
||||
style={{
|
||||
backgroundColor: vars.color.background,
|
||||
color: vars.color.muted,
|
||||
// border: `solid 1px ${vars.color.border}`,
|
||||
border: `solid 1px ${vars.color.border}`,
|
||||
borderRadius: 4,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
padding: "4px 4px",
|
||||
padding: "4px",
|
||||
}}
|
||||
>
|
||||
<ClockIcon size={11} />
|
||||
{formatPlayTime(game.playTimeInSeconds)}
|
||||
</small>
|
||||
|
||||
<div
|
||||
style={{
|
||||
color: "white",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 8,
|
||||
color: vars.color.muted,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<TrophyIcon size={13} />
|
||||
<span>
|
||||
{game.unlockedAchievementCount} /{" "}
|
||||
{game.achievementCount}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span>
|
||||
{formatDownloadProgress(
|
||||
game.unlockedAchievementCount /
|
||||
game.achievementCount
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<progress
|
||||
max={1}
|
||||
value={
|
||||
game.unlockedAchievementCount /
|
||||
game.achievementCount
|
||||
}
|
||||
className={styles.achievementsProgressBar}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={steamUrlBuilder.cover(game.objectId)}
|
||||
alt={game.title}
|
||||
|
@ -178,6 +244,7 @@ export function ProfileContent() {
|
|||
userStats,
|
||||
numberFormatter,
|
||||
t,
|
||||
formatPlayTime,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ export function RecentGamesBox() {
|
|||
const buildUserGameDetailsPath = (game: UserGame) =>
|
||||
buildGameDetailsPath({
|
||||
...game,
|
||||
objectID: game.objectId,
|
||||
objectId: game.objectId,
|
||||
});
|
||||
|
||||
if (!userProfile?.recentGames.length) return null;
|
||||
|
|
|
@ -70,7 +70,7 @@ export const heroPanel = style({
|
|||
|
||||
export const userInformation = style({
|
||||
display: "flex",
|
||||
padding: `${SPACING_UNIT * 4}px ${SPACING_UNIT * 3}px`,
|
||||
padding: `${SPACING_UNIT * 6}px ${SPACING_UNIT * 3}px`,
|
||||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
|
||||
import * as styles from "./profile-hero.css";
|
||||
import { useCallback, useContext, useMemo, useState } from "react";
|
||||
|
@ -10,6 +10,7 @@ import {
|
|||
PersonAddIcon,
|
||||
PersonIcon,
|
||||
SignOutIcon,
|
||||
UploadIcon,
|
||||
XCircleFillIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
|
@ -36,8 +37,7 @@ export function ProfileHero() {
|
|||
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
||||
const [isPerformingAction, setIsPerformingAction] = useState(false);
|
||||
|
||||
const { isMe, heroBackground, getUserProfile, userProfile } =
|
||||
useContext(userProfileContext);
|
||||
const { isMe, getUserProfile, userProfile } = useContext(userProfileContext);
|
||||
const {
|
||||
signOut,
|
||||
updateFriendRequestState,
|
||||
|
@ -48,6 +48,8 @@ export function ProfileHero() {
|
|||
|
||||
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||
|
||||
const [hero, setHero] = useState("");
|
||||
|
||||
const { t } = useTranslation("user_profile");
|
||||
const { formatDistance } = useDate();
|
||||
|
||||
|
@ -124,6 +126,7 @@ export function ProfileHero() {
|
|||
theme="outline"
|
||||
onClick={() => setShowEditProfileModal(true)}
|
||||
disabled={isPerformingAction}
|
||||
style={{ borderColor: vars.color.body }}
|
||||
>
|
||||
<PencilIcon />
|
||||
{t("edit_profile")}
|
||||
|
@ -148,6 +151,7 @@ export function ProfileHero() {
|
|||
theme="outline"
|
||||
onClick={() => handleFriendAction(userProfile.id, "SEND")}
|
||||
disabled={isPerformingAction}
|
||||
style={{ borderColor: vars.color.body }}
|
||||
>
|
||||
<PersonAddIcon />
|
||||
{t("add_friend")}
|
||||
|
@ -198,6 +202,7 @@ export function ProfileHero() {
|
|||
handleFriendAction(userProfile.relation!.BId, "CANCEL")
|
||||
}
|
||||
disabled={isPerformingAction}
|
||||
style={{ borderColor: vars.color.body }}
|
||||
>
|
||||
<XCircleFillIcon /> {t("cancel_request")}
|
||||
</Button>
|
||||
|
@ -212,11 +217,12 @@ export function ProfileHero() {
|
|||
handleFriendAction(userProfile.relation!.AId, "ACCEPTED")
|
||||
}
|
||||
disabled={isPerformingAction}
|
||||
style={{ borderColor: vars.color.body }}
|
||||
>
|
||||
<CheckCircleFillIcon /> {t("accept_request")}
|
||||
</Button>
|
||||
<Button
|
||||
theme="outline"
|
||||
theme="danger"
|
||||
onClick={() =>
|
||||
handleFriendAction(userProfile.relation!.AId, "REFUSED")
|
||||
}
|
||||
|
@ -246,7 +252,6 @@ export function ProfileHero() {
|
|||
if (gameRunning)
|
||||
return {
|
||||
...gameRunning,
|
||||
objectId: gameRunning.objectID,
|
||||
sessionDurationInSeconds: gameRunning.sessionDurationInMillis / 1000,
|
||||
};
|
||||
|
||||
|
@ -255,6 +260,43 @@ export function ProfileHero() {
|
|||
return userProfile?.currentGame;
|
||||
}, [isMe, userProfile, gameRunning]);
|
||||
|
||||
const handleChangeCoverClick = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: "Image",
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
|
||||
const { imagePath } = await window.electron
|
||||
.processProfileImage(path)
|
||||
.catch(() => {
|
||||
showErrorToast(t("image_process_failure"));
|
||||
return { imagePath: null };
|
||||
});
|
||||
|
||||
console.log("imagePath", imagePath);
|
||||
setHero(imagePath);
|
||||
|
||||
// onChange(imagePath);
|
||||
}
|
||||
};
|
||||
|
||||
const getImageUrl = () => {
|
||||
if (hero) return `local:${hero}`;
|
||||
// if (userDetails?.profileImageUrl) return userDetails.profileImageUrl;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// const imageUrl = getImageUrl();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ConfirmationModal
|
||||
|
@ -270,12 +312,9 @@ export function ProfileHero() {
|
|||
onClose={() => setShowEditProfileModal(false)}
|
||||
/>
|
||||
|
||||
<section
|
||||
className={styles.profileContentBox}
|
||||
// style={{ background: heroBackground }}
|
||||
>
|
||||
<section className={styles.profileContentBox}>
|
||||
<img
|
||||
src="https://wallpapers.com/images/featured/cyberpunk-anime-dfyw8eb7bqkw278u.jpg"
|
||||
src={getImageUrl()}
|
||||
alt=""
|
||||
style={{
|
||||
position: "absolute",
|
||||
|
@ -286,7 +325,8 @@ export function ProfileHero() {
|
|||
/>
|
||||
<div
|
||||
style={{
|
||||
background: heroBackground,
|
||||
background:
|
||||
"linear-gradient(135deg, rgb(0 0 0 / 70%), rgb(0 0 0 / 60%))",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: 1,
|
||||
|
@ -324,7 +364,7 @@ export function ProfileHero() {
|
|||
<Link
|
||||
to={buildGameDetailsPath({
|
||||
...currentGame,
|
||||
objectID: currentGame.objectId,
|
||||
objectId: currentGame.objectID,
|
||||
})}
|
||||
>
|
||||
{currentGame.title}
|
||||
|
@ -345,12 +385,30 @@ export function ProfileHero() {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
theme="outline"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 16,
|
||||
right: 16,
|
||||
borderColor: vars.color.body,
|
||||
}}
|
||||
onClick={handleChangeCoverClick}
|
||||
>
|
||||
<UploadIcon />
|
||||
Upload cover
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.heroPanel}
|
||||
style={{ background: heroBackground }}
|
||||
// style={{ background: heroBackground }}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(135deg, rgb(0 0 0 / 70%), rgb(0 0 0 / 60%))",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
@ -5,14 +5,14 @@ import flexSearch from "flexsearch";
|
|||
|
||||
const index = new flexSearch.Index();
|
||||
|
||||
const state = {
|
||||
repacks: [] as any[],
|
||||
};
|
||||
|
||||
interface SerializedGameRepack extends Omit<GameRepack, "uris"> {
|
||||
uris: string;
|
||||
}
|
||||
|
||||
const state = {
|
||||
repacks: [] as SerializedGameRepack[],
|
||||
};
|
||||
|
||||
self.onmessage = async (
|
||||
event: MessageEvent<[string, string] | "INDEX_REPACKS">
|
||||
) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue