Skip to content

Commit

Permalink
feat(ui): impl game pinning
Browse files Browse the repository at this point in the history
- Add entry to pin and unpin games.
- Make game card slightly more compact.
  • Loading branch information
skjsjhb committed Mar 1, 2025
1 parent 5bf0753 commit ef4b9bc
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/main/api/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ ipcMain.handle("addGame", async (_, init) => {
versions: {
game: p.id
},
user: {},
type
};

Expand Down
21 changes: 20 additions & 1 deletion src/main/game/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ export interface GameProfile {
*/
launchHint: LaunchHint;

/**
* User preference object.
*/
user: {
/**
* The priority of the pinned game.
*/
pinTime?: number;
};

/**
* Type of the game core.
*/
Expand All @@ -60,7 +70,7 @@ export type GameCoreType =
"neoforged" |
"unknown"

export const GAME_REG_VERSION = 2;
export const GAME_REG_VERSION = 3;
export const GAME_REG_TRANS: RegistryTransformer[] = [
// v1: patch the `installerProps` key
(s) => {
Expand All @@ -77,6 +87,15 @@ export const GAME_REG_TRANS: RegistryTransformer[] = [
s.installProps.gameVersion = s.launchHint.profileId;
}

return s;
},

// v3: append `user` key
(s) => {
if (!s.user) {
s.user = {};
}

return s;
}
];
12 changes: 7 additions & 5 deletions src/renderer/pages/games/GameCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useInstallProgress } from "@/renderer/services/install";
import { GameTypeImage } from "@components/GameTypeImage";
import { Card, CardBody, Chip } from "@heroui/react";
import { GameCardActions } from "@pages/games/GameCardActions";
import { clsx } from "clsx";
import { DotIcon } from "lucide-react";
import { useTranslation } from "react-i18next";

Expand All @@ -14,21 +15,22 @@ export function GameCard({ game }: GameCardProps) {
const { id, name, versions: { game: gameVersion }, installed, type } = game;
const { t: tc } = useTranslation("common", { keyPrefix: "progress" });
const installProgress = useInstallProgress(id);
const pinned = game.user.pinTime && game.user.pinTime > 0;

const isInstalling = installProgress !== null;
const installStatus = isInstalling ? "installing" : installed ? "installed" : "not-installed";

const progressText = installProgress && tc(installProgress.state, { ...installProgress.value });

return <Card shadow="sm">
return <Card shadow="sm" className={clsx(pinned && "outline-2 outline-default-500")}>
<CardBody>
<div className="flex gap-4 items-center h-16 px-3">
<div className="h-full p-3 bg-content2 rounded-full">
<div className="flex gap-6 items-center px-3">
<div className="h-12 p-2 bg-content2 rounded-full">
<GameTypeImage type={type}/>
</div>

<div className="flex flex-col gap-1">
<div className="font-bold text-xl">{name}</div>
<div className="flex flex-col">
<div className="font-bold text-lg">{name}</div>
<div className="flex items-center text-foreground-400">
{id}
{
Expand Down
30 changes: 28 additions & 2 deletions src/renderer/pages/games/GameCardActions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { alter } from "@/main/util/misc";
import { useGameProfile } from "@/renderer/services/game";
import { remoteInstaller } from "@/renderer/services/install";
import { procService } from "@/renderer/services/proc";
import { useNav } from "@/renderer/util/nav";
import { Button } from "@heroui/react";
import { CirclePlayIcon, DownloadIcon, EllipsisIcon, XIcon } from "lucide-react";
import { clsx } from "clsx";
import { CirclePlayIcon, DownloadIcon, EllipsisIcon, PinIcon, PinOffIcon, XIcon } from "lucide-react";
import { useState } from "react";

type InstallStatus = "installed" | "installing" | "not-installed";
Expand All @@ -14,8 +17,13 @@ interface GameActionsProps {

export function GameCardActions({ installStatus, gameId }: GameActionsProps) {
const [launching, setLaunching] = useState(false);
const game = useGameProfile(gameId);
const nav = useNav();

if (!game) throw "Game actions cannot be used without corresponding game profile";

const pinned = game.user.pinTime && game.user.pinTime > 0;

function handleShowDetails() {
nav(`/game-detail/${gameId}`);
}
Expand All @@ -28,6 +36,16 @@ export function GameCardActions({ installStatus, gameId }: GameActionsProps) {
void native.install.cancel(gameId);
}

function togglePin() {
void native.game.update(alter(game!, g => {
if (g.user.pinTime && g.user.pinTime > 0) {
g.user.pinTime = undefined;
} else {
g.user.pinTime = Date.now();
}
}));
}

async function launch() {
try {
setLaunching(true);
Expand Down Expand Up @@ -65,7 +83,15 @@ export function GameCardActions({ installStatus, gameId }: GameActionsProps) {
</Button>
}

<Button isIconOnly onPress={handleShowDetails}>
<Button variant="light" isIconOnly onPress={togglePin}>
<span className={clsx("duration-200", !pinned && "rotate-45")}>
{
pinned ? <PinOffIcon/> : <PinIcon/>
}
</span>
</Button>

<Button variant="light" isIconOnly onPress={handleShowDetails}>
<EllipsisIcon/>
</Button>
</div>;
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/pages/games/GamesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ export function GamesView() {

function toSortedGames(games: GameProfile[], sortMethod: SortMethod): GameProfile[] {
return games.toSorted((a, b) => {
const pa = a.user.pinTime;
const pb = b.user.pinTime;

if (pa && pb) {
return pb - pa;
}

if (pa) return -1;
if (pb) return 1;

switch (sortMethod) {
case "az" :
return a.name.localeCompare(b.name);
Expand Down

0 comments on commit ef4b9bc

Please sign in to comment.