Skip to content

Commit

Permalink
chore: Fix Library and improve MicroSDeck overall (#29)
Browse files Browse the repository at this point in the history
Improved library and Refactored code
  • Loading branch information
CEbbinghaus authored Sep 2, 2024
1 parent e0936bc commit e5ed312
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 63 deletions.
4 changes: 2 additions & 2 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tools]
# specify single or multiple versions
pnpm = 'latest'
node = 'lts'
pnpm = '9.5.0'
node = '22.4.0'
rust = '1.79.0'


Expand Down
2 changes: 1 addition & 1 deletion backend/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.10.4
0.10.5
29 changes: 24 additions & 5 deletions lib/src/MicoSDeck.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Event as BackendEvent, EventType, fetchCardsAndGames, fetchCardsForGame, fetchCurrentCardAndGames, fetchDeleteCard, fetchEventTarget, fetchHealth, fetchUpdateCard, fetchVersion } from "./backend.js";
import { Event as BackendEvent, EventType, fetchCardsAndGames, fetchCardsForGame, fetchCreateGame, fetchCurrentCardAndGames, fetchDeleteCard, fetchEventTarget, fetchHealth, fetchLinkCardAndGame, fetchLinkCardAndManyGames, fetchUnlinkCardAndGame, fetchUnlinkCardAndManyGames, fetchUpdateCard, fetchVersion } from "./backend.js";
import Logger from "lipe";
import { CardAndGames, CardsAndGames, MicroSDCard } from "./types.js"
import { CardAndGames, CardsAndGames, Game, MicroSDCard } from "./types.js"

import semverParse from "semver/functions/parse"
import semverEq from "semver/functions/eq.js"
Expand Down Expand Up @@ -127,10 +127,10 @@ export class MicroSDeck {
}

async fetchCurrent() {
this.currentCardAndGames = await fetchCurrentCardAndGames(this.fetchProps);
this.currentCardAndGames = await fetchCurrentCardAndGames(this.fetchProps) || this.currentCardAndGames;
}
async fetchCardsAndGames() {
this.cardsAndGames = await fetchCardsAndGames(this.fetchProps) || [];
this.cardsAndGames = await fetchCardsAndGames(this.fetchProps) || this.cardsAndGames || [];
}

getProps() {
Expand Down Expand Up @@ -200,11 +200,30 @@ export class MicroSDeck {

async hideCard(card: MicroSDCard) {
card.hidden = true;
//TODO: Implement
this.updateCard(card);
this.logger?.Log("Card {uid} was supposed to be hidden", card);
}

async fetchCardsForGame(gameId: string) {
return await fetchCardsForGame({ ...this.fetchProps, gameId })
}

async createGame(game: Game) {
return await fetchCreateGame({...this.fetchProps, game});
}

async link(card: MicroSDCard, gameId: string) {
return await fetchLinkCardAndGame({...this.fetchProps, card_id: card.uid, game_id: gameId});
}

async linkMany(card: MicroSDCard, gameIds: string[]) {
return await fetchLinkCardAndManyGames({...this.fetchProps, card_id: card.uid, game_ids: gameIds});
}

async unlink(card: MicroSDCard, gameId: string) {
return await fetchUnlinkCardAndGame({...this.fetchProps, card_id: card.uid, game_id: gameId});
}
async unlinkMany(card: MicroSDCard, gameIds: string[]) {
return await fetchUnlinkCardAndManyGames({...this.fetchProps, card_id: card.uid, game_ids: gameIds});
}
}
57 changes: 50 additions & 7 deletions lib/src/backend.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Logger from 'lipe';
import { CardAndGames, CardsAndGames, MicroSDCard } from "./types.js";
import { CardAndGames, CardsAndGames, Game, MicroSDCard } from "./types.js";

export type FetchProps = {
url: string,
Expand Down Expand Up @@ -34,21 +34,21 @@ async function wrapFetch({ url, logger }: FetchProps, init?: RequestInit): Promi

export type EventType = "start" | "close" | "abort" | "message" | "insert" | "remove" | "update" | "change";
export type Event = {
[key: string]: string | undefined,
event: EventType,
data?: string,
id?: string
}

function decodeEvent(event: string, logger?: Logger): Event {
logger?.Debug(`Recieved event to process: [{event}]`, {event});
function decodeEvent(message: string, logger?: Logger): Event {
logger?.Debug(`Received event to process: [{message}]`, { message });

var result = { event: "message" as EventType };
var lines = event.split('\n');
var result: Event = { event: "message" };

for (let line of lines) {
for (let line of message.split('\n')) {
let [key, value] = line.split(":").map(v => v.trim());
if (!key) {
throw new Error("No key was present for event " + event);
throw new Error("No key was present for event " + message);
}

result[key] = value;
Expand All @@ -69,7 +69,10 @@ function decodeStreamEvents(logger?: Logger) {
++pos
continue;
}
// extract the full message out of the buffer
const message = buffer.substring(0, pos).trim();

// Remove the message from the buffer and reset the index
buffer = buffer.substring(pos + 2);
pos = 0;

Expand Down Expand Up @@ -150,4 +153,44 @@ export async function fetchCardsAndGames({ url, logger }: FetchProps): Promise<C

export async function fetchCardsForGame({ url, logger, gameId }: FetchProps & { gameId: string }): Promise<MicroSDCard[] | undefined> {
return await wrapFetch({ url: `${url}/list/cards/${gameId}`, logger });
}

export async function fetchCreateGame({ url, logger, game}: FetchProps & { game: Game }) {
await wrapFetch({ url: `${url}/game`, logger }, {
method: "POST",
...ApplicationJsonHeaders,
body: JSON.stringify({game}),
});
}

export async function fetchLinkCardAndGame({ url, logger, card_id, game_id}: FetchProps & { card_id: string, game_id: string }) {
await wrapFetch({ url: `${url}/link`, logger }, {
method: "POST",
...ApplicationJsonHeaders,
body: JSON.stringify({card_id, game_id}),
});
}

export async function fetchLinkCardAndManyGames({ url, logger, card_id, game_ids}: FetchProps & { card_id: string, game_ids: string[] }) {
await wrapFetch({ url: `${url}/linkmany`, logger }, {
method: "POST",
...ApplicationJsonHeaders,
body: JSON.stringify({card_id, game_ids}),
});
}

export async function fetchUnlinkCardAndGame({ url, logger, card_id, game_id}: FetchProps & { card_id: string, game_id: string }) {
await wrapFetch({ url: `${url}/unlink`, logger }, {
method: "POST",
...ApplicationJsonHeaders,
body: JSON.stringify({card_id, game_id}),
});
}

export async function fetchUnlinkCardAndManyGames({ url, logger, card_id, game_ids}: FetchProps & { card_id: string, game_ids: string[] }) {
await wrapFetch({ url: `${url}/unlinkmany`, logger }, {
method: "POST",
...ApplicationJsonHeaders,
body: JSON.stringify({card_id, game_ids}),
});
}
1 change: 1 addition & 0 deletions lib/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"jsxFragmentFactory": "React.Fragment",
"allowJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"moduleResolution": "Node",
"noUnusedLocals": true,
"noUnusedParameters": true,
Expand Down
43 changes: 10 additions & 33 deletions src/components/CardActions.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MenuItem, showModal, Menu, ConfirmModal } from "@decky/ui"
import { CardAndGames, MicroSDCard, MicroSDeck } from "../../lib/src"
import { EditCardModal } from "../modals/EditCardModal";
import { API_URL, UNAMED_CARD_NAME } from "../const";
import { UNNAMED_CARD_NAME } from "../const";
import { GamesOnCardModal } from '../modals/GamesOnCardModal';
import { Logger } from '../Logging';

Expand Down Expand Up @@ -29,42 +29,19 @@ export function CardActionsContextMenu({ cardAndGames, currentCard, microSDeck }
</MenuItem>
<MenuItem onSelected={() => {
showModal(<EditCardModal
onConfirm={(card: MicroSDCard, nonSteamAdditions: string[], nonSteamDeletions: string[]) => {
onConfirm={async (card: MicroSDCard, nonSteamAdditions: string[], nonSteamDeletions: string[]) => {
microSDeck.updateCard(card);

//* probably want to move this into another method of microSDeckManager or combine it all into updateCard
nonSteamAdditions.forEach(async appId => {
await Promise.all(nonSteamAdditions.map(appId => {
const appName = collectionStore.deckDesktopApps?.apps.get(parseInt(appId))?.display_name ?? "Unknown Game";

//* might wanna tweak this to check responses are good before continuing
const res1 = await fetch(`${API_URL}/game/${appId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
//* i think the collection is only null if user has no shortcuts (non-steam games)
//* so if we just got the shorcut ids then i think we're good to assert that the collection and the specific game exist here
body: JSON.stringify({ uid: appId, name: collectionStore.deckDesktopApps!.apps.get(parseInt(appId))!.display_name, is_steam: false, size: 0 }),
}).catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error }));
return microSDeck.createGame({ uid: appId, name: appName, is_steam: false, size: 0 })
.catch(Error => Logger.Error("There was a critical error creating game: \"{Error}\"", { Error }));
}));

const res2 = await fetch(`${API_URL}/link`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ card_id: card.uid, game_id: appId }),
}).catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error }));
});
microSDeck.linkMany(card, nonSteamAdditions);

nonSteamDeletions.forEach(async appId => {
//* api call to remove game from card
await fetch(`${API_URL}/unlink`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ card_id: card.uid, game_id: appId }),
}).catch(Error => Logger.Error("There was a critical error: \"{Error}\"", { Error }));
});
await microSDeck.unlinkMany(card, nonSteamDeletions);
}}
card={{ ...card }}
games={games}
Expand All @@ -78,7 +55,7 @@ export function CardActionsContextMenu({ cardAndGames, currentCard, microSDeck }
<MenuItem tone="destructive" disabled={card.uid == currentCard?.uid} onSelected={() => {
showModal(<ConfirmModal
bAllowFullSize
strTitle={`Are you sure you want to delete ${card.name || UNAMED_CARD_NAME}`}
strTitle={`Are you sure you want to delete ${card.name || UNNAMED_CARD_NAME}`}
onOK={() => microSDeck.deleteCard(card)}
strOKButtonText="Confirm">
This cannot be undone. If you insert the card it will be registered again but any changes you have made will be lost.
Expand Down
4 changes: 2 additions & 2 deletions src/components/LibraryModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ReactElement, useEffect, useRef, useState } from 'react';
import { FaSdCard } from 'react-icons/fa';
import { Logger } from '../Logging';
import { API_URL, UNAMED_CARD_NAME } from '../const';
import { API_URL, UNNAMED_CARD_NAME } from '../const';
import { useCardsForGame } from "../../lib/src"
import { findModule } from "@decky/ui"

Expand Down Expand Up @@ -82,7 +82,7 @@ export default function LibraryModal({ appId: gameId }: { appId: string }): Reac
<FaSdCard size={18} />
</div>
<div style={{ marginLeft: "1.4rem", lineHeight: "18px", fontSize: 18, fontWeight: "bold" }} className="tab-label">
{cards.map(v => v.name || UNAMED_CARD_NAME).join(", ")}
{cards.map(v => v.name || UNNAMED_CARD_NAME).join(", ")}
</div>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export const API_URL: string = `${PROTOCOL}://${HOST}${PORT ? (":" + PORT) : ""}
export const CONFIGURATION_PATH = "/microsdeck/config";
export const DOCUMENTATION_PATH = "/microsdeck/docs";

export const UNAMED_CARD_NAME = "Unamed Card";
export const UNNAMED_CARD_NAME = "Unnamed Card";
5 changes: 2 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import {
import { routerHook } from '@decky/api';
import { FaEllipsisH, FaSdCard, FaStar } from "react-icons/fa";
import PatchAppScreen from "./patch/PatchAppScreen";
import { API_URL, DOCUMENTATION_PATH, UNAMED_CARD_NAME } from "./const";
import { API_URL, DOCUMENTATION_PATH, UNNAMED_CARD_NAME } from "./const";
import { Logger } from "./Logging";
import React from "react";
import Docs from "./pages/Docs";
import { DeckyAPI } from "./lib/DeckyApi";
import { MicroSDeck, MicroSDeckContextProvider, useMicroSDeckContext, CardAndGames, MicroSDCard, IsMatchingSemver } from "../lib/src";
import { CardActionsContextMenu } from "./components/CardActions";
import { backend } from "../lib/src";
Expand Down Expand Up @@ -68,7 +67,7 @@ function Content() {
<div style={{ float: "left" }}>
<FaSdCard size={14} />
</div>
<div style={{ marginLeft: "1.2rem", fontSize: 18, fontWeight: "bold" }} className="tab-label">{card.name || UNAMED_CARD_NAME}{currentCardMark}</div>
<div style={{ marginLeft: "1.2rem", fontSize: 18, fontWeight: "bold" }} className="tab-label">{card.name || UNNAMED_CARD_NAME}{currentCardMark}</div>
<div style={{ position: "absolute", bottom: 0, left: 0, fontSize: 8, color: "#aaa", whiteSpace: "nowrap" }}>{card.uid}</div>
</div>
,
Expand Down
4 changes: 2 additions & 2 deletions src/modals/EditCardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { BiSolidDownArrow } from "react-icons/bi";
import { DeckyAPI } from "../lib/DeckyApi";
import { Game, MicroSDCard } from "../../lib/src";
import { Logger } from "../Logging";
import { UNAMED_CARD_NAME } from "../const";
import { UNNAMED_CARD_NAME } from "../const";
import { GamesOnCardModal } from './GamesOnCardModal';
import { GamepadUIAudio } from '../lib/GamepadUIAudio';

Expand Down Expand Up @@ -75,7 +75,7 @@ export const EditCardModal: VFC<EditCardProps> = ({ card, games, onConfirm, clos
bAllowFullSize
onCancel={closeModal}
onEscKeypress={closeModal}
strTitle={`Editing Card: "${(card.name ?? UNAMED_CARD_NAME)}"`}
strTitle={`Editing Card: "${(card.name ?? UNNAMED_CARD_NAME)}"`}
onOK={onSave}
strOKButtonText="Save">
<Field description={
Expand Down
11 changes: 4 additions & 7 deletions util/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Version, UpdateVersion, ResetVersion } from './versioning.mjs';
import { Logger } from './log.mjs';
import { exit } from 'process';

import { name as PluginName } from "../plugin.json" with { type: "json" };
import deploy from "../deploy.json" with { type: "json" };

if (process.argv.includes('-h') || process.argv.includes('--help')) {
console.log(
` __ __ _ ___ ___ _ ___ _ _ _
Expand Down Expand Up @@ -86,12 +89,6 @@ function runCommand(command, directory = "") {
return output;
}

async function importJson(file) {
return (await import(file, { with: { type: "json" } })).default;
}

const { name: PluginName } = await importJson(join(basePath, "plugin.json"));

Logger.Log(`Building plugin ${PluginName}@${Version}`);

if (!existsSync('plugin.json')) {
Expand Down Expand Up @@ -154,7 +151,7 @@ if (tasks.includes('upload')) {
exit(1);
}

const { host, user, keyfile } = await importJson(join(basePath, "deploy.json"));
const { host, user, keyfile } = deploy;

const deployPath = `/home/${user}/homebrew/plugins/${PluginName}`;

Expand Down

0 comments on commit e5ed312

Please sign in to comment.