mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-13 03:32:13 +00:00
Merge branch 'rc/v2.0' of github.com:hydralauncher/hydra into rc/v2.0
This commit is contained in:
commit
0ac17e95ff
24 changed files with 291 additions and 74 deletions
|
@ -82,6 +82,7 @@
|
|||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@swc/core": "^1.4.16",
|
||||
"@types/auto-launch": "^5.0.5",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.12.7",
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"filter": "Filter library",
|
||||
"home": "Home",
|
||||
"queued": "{{title}} (Queued)",
|
||||
"game_has_no_executable": "Game has no executable selected"
|
||||
"game_has_no_executable": "Game has no executable selected",
|
||||
"sign_in": "Sign in"
|
||||
},
|
||||
"header": {
|
||||
"search": "Search games",
|
||||
|
@ -232,7 +233,6 @@
|
|||
"amount_hours": "{{amount}} hours",
|
||||
"amount_minutes": "{{amount}} minutes",
|
||||
"last_time_played": "Last played {{period}}",
|
||||
"sign_out": "Sign out",
|
||||
"activity": "Recent activity",
|
||||
"library": "Library",
|
||||
"total_play_time": "Total playtime: {{amount}}",
|
||||
|
@ -244,9 +244,11 @@
|
|||
"edit_profile": "Edit Profile",
|
||||
"saved_successfully": "Saved successfully",
|
||||
"try_again": "Please, try again",
|
||||
"signout_modal_title": "Are you sure?",
|
||||
"sign_out_modal_title": "Are you sure?",
|
||||
"cancel": "Cancel",
|
||||
"signout": "Sign Out",
|
||||
"successfully_signed_out": "Successfully signed out"
|
||||
"successfully_signed_out": "Successfully signed out",
|
||||
"sign_out": "Sign out",
|
||||
"playing_for": "Playing for {{amount}}",
|
||||
"sign_out_modal_text": "Your library is linked with your current account. When signing out, your library will not be visible anymore, and any progress will not be saved. Continue with sign out?"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"filter": "Filtrar biblioteca",
|
||||
"home": "Início",
|
||||
"queued": "{{title}} (Na fila)",
|
||||
"game_has_no_executable": "Jogo não possui executável selecionado"
|
||||
"game_has_no_executable": "Jogo não possui executável selecionado",
|
||||
"sign_in": "Login"
|
||||
},
|
||||
"header": {
|
||||
"search": "Buscar jogos",
|
||||
|
@ -232,7 +233,6 @@
|
|||
"amount_hours": "{{amount}} horas",
|
||||
"amount_minutes": "{{amount}} minutos",
|
||||
"last_time_played": "Jogou {{period}}",
|
||||
"sign_out": "Sair da conta",
|
||||
"activity": "Atividade recente",
|
||||
"library": "Biblioteca",
|
||||
"total_play_time": "Tempo total de jogo: {{amount}}",
|
||||
|
@ -245,8 +245,10 @@
|
|||
"saved_successfully": "Salvo com sucesso",
|
||||
"try_again": "Por favor, tente novamente",
|
||||
"cancel": "Cancelar",
|
||||
"signout": "Sair da conta",
|
||||
"signout_modal_title": "Tem certeza?",
|
||||
"successfully_signed_out": "Deslogado com sucesso"
|
||||
"successfully_signed_out": "Deslogado com sucesso",
|
||||
"sign_out": "Sair da conta",
|
||||
"sign_out_modal_title": "Tem certeza?",
|
||||
"playing_for": "Jogando por {{amount}}",
|
||||
"sign_out_modal_text": "Sua biblioteca de jogos está associada com a sua conta atual. Ao sair, sua biblioteca não aparecerá mais no Hydra e qualquer progresso não será salvo. Deseja continuar?"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { userAuthRepository } from "@main/repository";
|
||||
import { gameRepository, userAuthRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services/hydra-api";
|
||||
|
||||
const signOut = async (_event: Electron.IpcMainInvokeEvent): Promise<void> => {
|
||||
await Promise.all([
|
||||
userAuthRepository.delete({ id: 1 }),
|
||||
HydraApi.post("/auth/logout"),
|
||||
gameRepository.delete({}),
|
||||
HydraApi.post("/auth/logout").catch(),
|
||||
]);
|
||||
};
|
||||
|
|
@ -40,7 +40,7 @@ import "./download-sources/validate-download-source";
|
|||
import "./download-sources/add-download-source";
|
||||
import "./download-sources/remove-download-source";
|
||||
import "./download-sources/sync-download-sources";
|
||||
import "./auth/signout";
|
||||
import "./auth/sign-out";
|
||||
import "./auth/open-auth-window";
|
||||
import "./user/get-user";
|
||||
import "./profile/get-me";
|
||||
|
|
|
@ -5,6 +5,7 @@ import { gameRepository } from "@main/repository";
|
|||
import { getProcesses } from "@main/helpers";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { createGame, updateGamePlaytime } from "./library-sync";
|
||||
import { GameRunning } from "@types";
|
||||
|
||||
const gamesPlaytime = new Map<
|
||||
number,
|
||||
|
@ -46,10 +47,6 @@ export const watchProcesses = async () => {
|
|||
const zero = gamePlaytime.lastTick;
|
||||
const delta = performance.now() - zero;
|
||||
|
||||
if (WindowManager.mainWindow) {
|
||||
WindowManager.mainWindow.webContents.send("on-playtime", game.id);
|
||||
}
|
||||
|
||||
await gameRepository.update(game.id, {
|
||||
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
||||
lastTimePlayed: new Date(),
|
||||
|
@ -92,10 +89,20 @@ export const watchProcesses = async () => {
|
|||
gameRepository.update({ objectID: game.objectID }, { remoteId });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WindowManager.mainWindow) {
|
||||
WindowManager.mainWindow.webContents.send("on-game-close", game.id);
|
||||
}
|
||||
}
|
||||
const gamesRunning = Array.from(gamesPlaytime.entries()).map((entry) => {
|
||||
return {
|
||||
id: entry[0],
|
||||
sessionDurationInMillis: performance.now() - entry[1].firstTick,
|
||||
};
|
||||
});
|
||||
|
||||
WindowManager.mainWindow.webContents.send(
|
||||
"on-games-running",
|
||||
gamesRunning as Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import type {
|
|||
UserPreferences,
|
||||
AppUpdaterEvent,
|
||||
StartGameDownloadPayload,
|
||||
GameRunning,
|
||||
} from "@types";
|
||||
|
||||
contextBridge.exposeInMainWorld("electron", {
|
||||
|
@ -84,17 +85,15 @@ contextBridge.exposeInMainWorld("electron", {
|
|||
ipcRenderer.invoke("deleteGameFolder", gameId),
|
||||
getGameByObjectID: (objectID: string) =>
|
||||
ipcRenderer.invoke("getGameByObjectID", objectID),
|
||||
onPlaytime: (cb: (gameId: number) => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, gameId: number) =>
|
||||
cb(gameId);
|
||||
ipcRenderer.on("on-playtime", listener);
|
||||
return () => ipcRenderer.removeListener("on-playtime", listener);
|
||||
},
|
||||
onGameClose: (cb: (gameId: number) => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, gameId: number) =>
|
||||
cb(gameId);
|
||||
ipcRenderer.on("on-game-close", listener);
|
||||
return () => ipcRenderer.removeListener("on-game-close", listener);
|
||||
onGamesRunning: (
|
||||
cb: (
|
||||
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||
) => void
|
||||
) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, gamesRunning) =>
|
||||
cb(gamesRunning);
|
||||
ipcRenderer.on("on-games-running", listener);
|
||||
return () => ipcRenderer.removeListener("on-games-running", listener);
|
||||
},
|
||||
onLibraryBatchComplete: (cb: () => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent) => cb();
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
closeToast,
|
||||
setUserDetails,
|
||||
setProfileBackground,
|
||||
setGameRunning,
|
||||
} from "@renderer/features";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
@ -31,7 +32,7 @@ export interface AppProps {
|
|||
|
||||
export function App() {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const { updateLibrary } = useLibrary();
|
||||
const { updateLibrary, library } = useLibrary();
|
||||
|
||||
const { t } = useTranslation("app");
|
||||
|
||||
|
@ -110,6 +111,32 @@ export function App() {
|
|||
});
|
||||
}, [fetchUserDetails, t, showSuccessToast, updateUserDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onGamesRunning((gamesRunning) => {
|
||||
if (gamesRunning.length) {
|
||||
const lastGame = gamesRunning[gamesRunning.length - 1];
|
||||
const libraryGame = library.find(
|
||||
(library) => library.id === lastGame.id
|
||||
);
|
||||
|
||||
if (libraryGame) {
|
||||
dispatch(
|
||||
setGameRunning({
|
||||
...libraryGame,
|
||||
sessionDurationInMillis: lastGame.sessionDurationInMillis,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
dispatch(setGameRunning(null));
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [dispatch, library]);
|
||||
|
||||
useEffect(() => {
|
||||
const listeners = [
|
||||
window.electron.onSignIn(onSignIn),
|
||||
|
|
|
@ -9,7 +9,7 @@ export const profileButton = style({
|
|||
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
|
||||
color: vars.color.muted,
|
||||
borderBottom: `solid 1px ${vars.color.border}`,
|
||||
boxShadow: "0px 0px 15px 0px #000000",
|
||||
boxShadow: "0px 0px 15px 0px rgb(0 0 0 / 70%)",
|
||||
":hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||
},
|
||||
|
@ -20,6 +20,7 @@ export const profileButtonContent = style({
|
|||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`,
|
||||
height: "40px",
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const profileAvatar = style({
|
||||
|
@ -39,6 +40,8 @@ export const profileButtonInformation = style({
|
|||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
flex: "1",
|
||||
minWidth: 0,
|
||||
});
|
||||
|
||||
export const statusBadge = style({
|
||||
|
@ -55,4 +58,9 @@ export const statusBadge = style({
|
|||
export const profileButtonTitle = style({
|
||||
fontWeight: "bold",
|
||||
fontSize: vars.size.body,
|
||||
width: "100%",
|
||||
textAlign: "left",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
});
|
||||
|
|
|
@ -2,14 +2,19 @@ import { useNavigate } from "react-router-dom";
|
|||
import { PersonIcon } from "@primer/octicons-react";
|
||||
import * as styles from "./sidebar-profile.css";
|
||||
|
||||
import { useUserDetails } from "@renderer/hooks";
|
||||
import { useAppSelector, useUserDetails } from "@renderer/hooks";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function SidebarProfile() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation("sidebar");
|
||||
|
||||
const { userDetails, profileBackground } = useUserDetails();
|
||||
|
||||
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
if (userDetails === null) {
|
||||
window.electron.openAuthWindow();
|
||||
|
@ -46,9 +51,24 @@ export function SidebarProfile() {
|
|||
|
||||
<div className={styles.profileButtonInformation}>
|
||||
<p className={styles.profileButtonTitle}>
|
||||
{userDetails ? userDetails.displayName : "Sign in"}
|
||||
{userDetails ? userDetails.displayName : t("sign_in")}
|
||||
</p>
|
||||
|
||||
{userDetails && gameRunning && (
|
||||
<div>
|
||||
<small>{gameRunning.title}</small>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{userDetails && gameRunning && (
|
||||
<img
|
||||
alt={gameRunning.title}
|
||||
width={24}
|
||||
style={{ borderRadius: 4 }}
|
||||
src={gameRunning.iconUrl}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -109,20 +109,19 @@ export function GameDetailsContextProvider({
|
|||
}, [objectID, gameTitle, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const listeners = [
|
||||
window.electron.onGameClose(() => {
|
||||
if (isGameRunning) setisGameRunning(false);
|
||||
}),
|
||||
window.electron.onPlaytime((gameId) => {
|
||||
if (gameId === game?.id) {
|
||||
if (!isGameRunning) setisGameRunning(true);
|
||||
const unsubscribe = window.electron.onGamesRunning((gamesIds) => {
|
||||
const updatedIsGameRunning =
|
||||
!!game?.id &&
|
||||
!!gamesIds.find((gameRunning) => gameRunning.id == game.id);
|
||||
|
||||
if (isGameRunning != updatedIsGameRunning) {
|
||||
updateGame();
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
setisGameRunning(updatedIsGameRunning);
|
||||
});
|
||||
return () => {
|
||||
listeners.forEach((unsubscribe) => unsubscribe());
|
||||
unsubscribe();
|
||||
};
|
||||
}, [game?.id, isGameRunning, updateGame]);
|
||||
|
||||
|
|
7
src/renderer/src/declaration.d.ts
vendored
7
src/renderer/src/declaration.d.ts
vendored
|
@ -71,8 +71,11 @@ declare global {
|
|||
removeGame: (gameId: number) => Promise<void>;
|
||||
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
||||
getGameByObjectID: (objectID: string) => Promise<Game | null>;
|
||||
onPlaytime: (cb: (gameId: number) => void) => () => Electron.IpcRenderer;
|
||||
onGameClose: (cb: (gameId: number) => void) => () => Electron.IpcRenderer;
|
||||
onGamesRunning: (
|
||||
cb: (
|
||||
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||
) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
|
||||
|
||||
/* User preferences */
|
||||
|
|
|
@ -5,3 +5,4 @@ export * from "./download-slice";
|
|||
export * from "./window-slice";
|
||||
export * from "./toast-slice";
|
||||
export * from "./user-details-slice";
|
||||
export * from "./running-game-slice";
|
||||
|
|
22
src/renderer/src/features/running-game-slice.ts
Normal file
22
src/renderer/src/features/running-game-slice.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { GameRunning } from "@types";
|
||||
|
||||
export interface GameRunningState {
|
||||
gameRunning: GameRunning | null;
|
||||
}
|
||||
|
||||
const initialState: GameRunningState = {
|
||||
gameRunning: null,
|
||||
};
|
||||
|
||||
export const gameRunningSlice = createSlice({
|
||||
name: "running-game",
|
||||
initialState,
|
||||
reducers: {
|
||||
setGameRunning: (state, action: PayloadAction<GameRunning | null>) => {
|
||||
state.gameRunning = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setGameRunning } = gameRunningSlice.actions;
|
|
@ -43,5 +43,5 @@ export const buildGameDetailsPath = (
|
|||
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
|
||||
};
|
||||
|
||||
export const darkenColor = (color: string, amount: number) =>
|
||||
new Color(color).darken(amount).toString();
|
||||
export const darkenColor = (color: string, amount: number, alpha: number = 1) =>
|
||||
new Color(color).darken(amount).alpha(alpha).toString();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { formatDistance } from "date-fns";
|
||||
import { formatDistance, subMilliseconds } from "date-fns";
|
||||
import type { FormatDistanceOptions } from "date-fns";
|
||||
import {
|
||||
ptBR,
|
||||
|
@ -52,5 +52,20 @@ export function useDate() {
|
|||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
formatDiffInMillis: (
|
||||
millis: number,
|
||||
baseDate: string | number | Date,
|
||||
options?: FormatDistanceOptions
|
||||
) => {
|
||||
try {
|
||||
return formatDistance(subMilliseconds(new Date(), millis), baseDate, {
|
||||
...options,
|
||||
locale: getDateLocale(),
|
||||
});
|
||||
} catch (err) {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,8 +36,7 @@ export function useUserDetails() {
|
|||
format: "hex",
|
||||
});
|
||||
|
||||
const profileBackground = `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.8)})`;
|
||||
|
||||
const profileBackground = `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.8, 0.7)})`;
|
||||
dispatch(setProfileBackground(profileBackground));
|
||||
|
||||
window.localStorage.setItem(
|
||||
|
@ -45,9 +44,13 @@ export function useUserDetails() {
|
|||
JSON.stringify({ ...userDetails, profileBackground })
|
||||
);
|
||||
} else {
|
||||
dispatch(setProfileBackground(null));
|
||||
const profileBackground = `#151515B3`;
|
||||
dispatch(setProfileBackground(profileBackground));
|
||||
|
||||
window.localStorage.setItem("userDetails", JSON.stringify(userDetails));
|
||||
window.localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify({ ...userDetails, profileBackground })
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
|
|
|
@ -138,8 +138,9 @@ export const randomizerButton = style({
|
|||
bottom: `${26 + SPACING_UNIT * 2}px`,
|
||||
/* Scroll bar + spacing */
|
||||
right: `${9 + SPACING_UNIT * 2}px`,
|
||||
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 10px 3px",
|
||||
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 10px 1px",
|
||||
border: `solid 2px ${vars.color.border}`,
|
||||
zIndex: "1",
|
||||
backgroundColor: vars.color.background,
|
||||
":hover": {
|
||||
backgroundColor: vars.color.background,
|
||||
|
|
|
@ -6,9 +6,14 @@ import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
|||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||
import { useDate, useToast, useUserDetails } from "@renderer/hooks";
|
||||
import {
|
||||
useAppSelector,
|
||||
useDate,
|
||||
useToast,
|
||||
useUserDetails,
|
||||
} from "@renderer/hooks";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
import { buildGameDetailsPath, steamUrlBuilder } from "@renderer/helpers";
|
||||
import { PersonIcon, TelescopeIcon } from "@primer/octicons-react";
|
||||
import { Button } from "@renderer/components";
|
||||
import { UserEditProfileModal } from "./user-edit-modal";
|
||||
|
@ -33,6 +38,8 @@ export function UserContent({
|
|||
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
||||
const [showSignOutModal, setShowSignOutModal] = useState(false);
|
||||
|
||||
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const numberFormatter = useMemo(() => {
|
||||
|
@ -41,7 +48,7 @@ export function UserContent({
|
|||
});
|
||||
}, [i18n.language]);
|
||||
|
||||
const { formatDistance } = useDate();
|
||||
const { formatDistance, formatDiffInMillis } = useDate();
|
||||
|
||||
const formatPlayTime = () => {
|
||||
const seconds = userProfile.libraryGames.reduce(
|
||||
|
@ -102,10 +109,32 @@ export function UserContent({
|
|||
<section
|
||||
className={styles.profileContentBox}
|
||||
style={{
|
||||
background: profileContentBoxBackground,
|
||||
padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`,
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{gameRunning && isMe && (
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${steamUrlBuilder.libraryHero(gameRunning.objectID)})`,
|
||||
backgroundPosition: "top",
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
backgroundSize: "cover",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
background: profileContentBoxBackground,
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
></div>
|
||||
|
||||
<div className={styles.profileAvatarContainer}>
|
||||
{userProfile.profileImageUrl ? (
|
||||
<img
|
||||
|
@ -120,10 +149,45 @@ export function UserContent({
|
|||
|
||||
<div className={styles.profileInformation}>
|
||||
<h2 style={{ fontWeight: "bold" }}>{userProfile.displayName}</h2>
|
||||
{isMe && gameRunning && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT / 2}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<p>{gameRunning.title}</p>
|
||||
</div>
|
||||
<small>
|
||||
{t("playing_for", {
|
||||
amount: formatDiffInMillis(
|
||||
gameRunning.sessionDurationInMillis,
|
||||
new Date()
|
||||
),
|
||||
})}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isMe && (
|
||||
<div style={{ flex: 1, display: "flex", justifyContent: "end" }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
@ -133,7 +197,7 @@ export function UserContent({
|
|||
>
|
||||
<>
|
||||
<Button theme="outline" onClick={handleEditProfile}>
|
||||
Editar perfil
|
||||
{t("edit_profile")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -19,18 +19,21 @@ export const UserSignOutModal = ({
|
|||
<>
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={t("signout_modal_title")}
|
||||
title={t("sign_out_modal_title")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.signOutModalContent}>
|
||||
<p style={{ fontFamily: "Fira Sans" }}>{t("sign_out_modal_text")}</p>
|
||||
<div className={styles.signOutModalButtonsContainer}>
|
||||
<Button onClick={onConfirm} theme="outline">
|
||||
{t("signout")}
|
||||
<Button onClick={onConfirm} theme="danger">
|
||||
{t("sign_out")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={onClose} theme="primary">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,6 @@ import { style } from "@vanilla-extract/css";
|
|||
export const wrapper = style({
|
||||
padding: "24px",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT * 3}px`,
|
||||
|
@ -33,6 +32,7 @@ export const profileAvatarContainer = style({
|
|||
overflow: "hidden",
|
||||
border: `solid 1px ${vars.color.border}`,
|
||||
boxShadow: "0px 0px 5px 0px rgba(0, 0, 0, 0.7)",
|
||||
zIndex: 1,
|
||||
});
|
||||
|
||||
export const profileAvatarEditContainer = style({
|
||||
|
@ -56,7 +56,6 @@ export const profileAvatar = style({
|
|||
borderRadius: "50%",
|
||||
overflow: "hidden",
|
||||
objectFit: "cover",
|
||||
animationPlayState: "paused",
|
||||
});
|
||||
|
||||
export const profileAvatarEditOverlay = style({
|
||||
|
@ -72,8 +71,10 @@ export const profileAvatarEditOverlay = style({
|
|||
export const profileInformation = style({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
alignItems: "flex-start",
|
||||
color: "#c0c1c7",
|
||||
zIndex: 1,
|
||||
});
|
||||
|
||||
export const profileContent = style({
|
||||
|
@ -189,10 +190,18 @@ export const noDownloads = style({
|
|||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const signOutModalContent = style({
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const signOutModalButtonsContainer = style({
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
justifyContent: "end",
|
||||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
paddingTop: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
userPreferencesSlice,
|
||||
toastSlice,
|
||||
userDetailsSlice,
|
||||
gameRunningSlice,
|
||||
} from "@renderer/features";
|
||||
|
||||
export const store = configureStore({
|
||||
|
@ -18,6 +19,7 @@ export const store = configureStore({
|
|||
download: downloadSlice.reducer,
|
||||
toast: toastSlice.reducer,
|
||||
userDetails: userDetailsSlice.reducer,
|
||||
gameRunning: gameRunningSlice.reducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -127,6 +127,15 @@ export interface Game {
|
|||
|
||||
export type LibraryGame = Omit<Game, "repacks">;
|
||||
|
||||
export interface GameRunning {
|
||||
id: number;
|
||||
title: string;
|
||||
iconUrl: string;
|
||||
objectID: string;
|
||||
shop: GameShop;
|
||||
sessionDurationInMillis: number;
|
||||
}
|
||||
|
||||
export interface DownloadProgress {
|
||||
downloadSpeed: number;
|
||||
timeRemaining: number;
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -1251,6 +1251,25 @@
|
|||
"@types/node" "*"
|
||||
"@types/responselike" "^1.0.0"
|
||||
|
||||
"@types/color-convert@*":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.3.tgz#e93f5c991eda87a945058b47044f5f0008b0dce9"
|
||||
integrity sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==
|
||||
dependencies:
|
||||
"@types/color-name" "*"
|
||||
|
||||
"@types/color-name@*":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.4.tgz#e002611ff627347818d440a05e81650e9a4053b8"
|
||||
integrity sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==
|
||||
|
||||
"@types/color@^3.0.6":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.6.tgz#29c27a99d4de2975e1676712679a0bd7f646a3fb"
|
||||
integrity sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==
|
||||
dependencies:
|
||||
"@types/color-convert" "*"
|
||||
|
||||
"@types/conventional-commits-parser@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz#8c9d23e0b415b24b91626d07017303755d542dc8"
|
||||
|
|
Loading…
Reference in a new issue