From 868faafb4d9ab310336e04ba7cb2d3e57b023a4c Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 29 Jun 2026 00:33:20 +0800 Subject: [PATCH] fix(plugin): screenshot limit, version/build fields, delete button, workspace scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - list_screenshots: bump limit 12 → 50 so users can scroll further back - Add versionString + buildId to DeckyVaultImportV1, SessionData, and buildImportPayload. Import endpoint now saves them on new game versions. - Screenshot delete button in preset detail modal: owner/admin sees a trash icon on hover that calls DELETE /api/performance/:id/screenshots/:sid - Root workspace scripts for plugin workflow: bun run plugin:build | plugin:zip | plugin:release | plugin:deploy - Add deploy.sh (SSH scripts to push plugin to Deck) --- .../web/app/game/[id]/preset-detail-modal.tsx | 81 +++++++++++++------ apps/web/lib/api/performance-import.ts | 4 + package.json | 6 +- packages/shared/src/index.ts | 2 + plugins/decky-vault/main.py | 2 +- plugins/decky-vault/scripts/deploy.sh | 30 +++++++ plugins/decky-vault/src/lib/store.ts | 6 ++ 7 files changed, 104 insertions(+), 27 deletions(-) create mode 100755 plugins/decky-vault/scripts/deploy.sh diff --git a/apps/web/app/game/[id]/preset-detail-modal.tsx b/apps/web/app/game/[id]/preset-detail-modal.tsx index 84e4be7..b6e4200 100644 --- a/apps/web/app/game/[id]/preset-detail-modal.tsx +++ b/apps/web/app/game/[id]/preset-detail-modal.tsx @@ -169,6 +169,9 @@ export function PresetDetailModal({ const [activeTab, setActiveTab] = useState("media") const [lightboxOpen, setLightboxOpen] = useState(false) const [lightboxIndex, setLightboxIndex] = useState(0) + const [deletingScreenshotId, setDeletingScreenshotId] = useState< + string | null + >(null) const isOwner = session?.user?.id === preset.userId const isAdmin = session?.user?.role === "admin" @@ -259,6 +262,23 @@ export function PresetDetailModal({ } } + async function handleDeleteScreenshot(screenshotId: string) { + setDeletingScreenshotId(screenshotId) + try { + const res = await fetch( + `/api/performance/${preset.id}/screenshots/${screenshotId}`, + { method: "DELETE" }, + ) + if (res.ok) { + router.refresh() + } + } catch (err) { + console.error("Failed to delete screenshot:", err) + } finally { + setDeletingScreenshotId(null) + } + } + const visibleTabs: TabKey[] = ["details", "media", "settings"] if (preset.userNotes) visibleTabs.push("notes") @@ -854,31 +874,42 @@ export function PresetDetailModal({
{preset.screenshots.map( (ss, i) => ( - +
+ + {(isOwner || isAdmin) && ( + + )} +
), )}
diff --git a/apps/web/lib/api/performance-import.ts b/apps/web/lib/api/performance-import.ts index a59aad5..10a71c9 100644 --- a/apps/web/lib/api/performance-import.ts +++ b/apps/web/lib/api/performance-import.ts @@ -88,6 +88,8 @@ export const performanceImportRoutes = new Elysia({ .values({ gameId: game.id, isLatest: true, + versionString: body.versionString ?? null, + buildId: body.buildId ?? null, }) .returning({ id: gameVersions.id }) version = newVersion @@ -260,6 +262,8 @@ export const performanceImportRoutes = new Elysia({ fpsHigh: t.Optional(t.Nullable(t.Number())), protonVersion: t.Optional(t.Nullable(t.String())), osVersion: t.Optional(t.Nullable(t.String())), + versionString: t.Optional(t.Nullable(t.String())), + buildId: t.Optional(t.Nullable(t.String())), upscalerType: t.Optional(t.String()), upscalerVersion: t.Optional(t.Nullable(t.String())), frameGenMethod: t.Optional(t.String()), diff --git a/package.json b/package.json index 93b4814..10d5f88 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,11 @@ "db:migrate": "bun run --filter @deckyvault/web db:migrate", "db:push": "bun run --filter @deckyvault/web db:push", "db:studio": "bun run --filter @deckyvault/web db:studio", - "db:seed": "bun run --filter @deckyvault/web db:seed" + "db:seed": "bun run --filter @deckyvault/web db:seed", + "plugin:build": "bun run --filter @deckyvault/plugin build", + "plugin:zip": "bun run --filter @deckyvault/plugin zip", + "plugin:release": "bun run --filter @deckyvault/plugin release", + "plugin:deploy": "bun run --filter @deckyvault/plugin build && bash plugins/decky-vault/scripts/deploy.sh" }, "trustedDependencies": ["sharp", "unrs-resolver"], "overrides": { diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index d9c23f6..20234ff 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -38,6 +38,8 @@ export interface DeckyVaultImportV1 { settingsJson?: unknown[] | null userNotes?: string | null customSystem?: boolean + versionString?: string | null + buildId?: string | null antiCheatRelevant?: boolean antiCheatName?: string | null antiCheatStatus?: string diff --git a/plugins/decky-vault/main.py b/plugins/decky-vault/main.py index 2e92f7d..00ea939 100644 --- a/plugins/decky-vault/main.py +++ b/plugins/decky-vault/main.py @@ -605,7 +605,7 @@ exec mangohud "$@" except Exception as e: return {"success": False, "error": str(e), "status": 0} - async def list_screenshots(self, limit: int = 12) -> dict: + async def list_screenshots(self, limit: int = 50) -> dict: """RPC: List recent Steam Deck screenshots from ~/Pictures/Screenshots/. Returns {screenshots: [{path, name, mtime, size}], error?}. Steam saves timestamped JPGs in a 'Steam Client' subfolder and keeps diff --git a/plugins/decky-vault/scripts/deploy.sh b/plugins/decky-vault/scripts/deploy.sh new file mode 100755 index 0000000..a6f7794 --- /dev/null +++ b/plugins/decky-vault/scripts/deploy.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Deploy the built plugin dist/ + main.py to the Steam Deck and restart the +# plugin loader. The build step is handled separately (bun run plugin:build +# calls rollup first, then this script runs). +set -euo pipefail + +DECK_IP="${DECK_IP:-192.168.254.112}" +DECK_USER="${DECK_USER:-deck}" +DECK_PASS="${DECK_PASS:-22122012003}" +PLUGIN_DIR="/home/deck/homebrew/plugins/decky-vault" + +cd "$(dirname "$0")/.." + +echo "› Deploying to ${DECK_USER}@${DECK_IP}…" + +sshpass -p "$DECK_PASS" ssh -o StrictHostKeyChecking=no "${DECK_USER}@${DECK_IP}" \ + "mkdir -p /tmp/dv-deploy && rm -rf /tmp/dv-deploy/*" + +sshpass -p "$DECK_PASS" scp -o StrictHostKeyChecking=no -r dist main.py \ + "${DECK_USER}@${DECK_IP}:/tmp/dv-deploy/" + +sshpass -p "$DECK_PASS" ssh -o StrictHostKeyChecking=no "${DECK_USER}@${DECK_IP}" \ + "echo '${DECK_PASS}' | sudo -S cp /tmp/dv-deploy/dist/index.js ${PLUGIN_DIR}/dist/index.js && \ + echo '${DECK_PASS}' | sudo -S cp /tmp/dv-deploy/main.py ${PLUGIN_DIR}/main.py && \ + echo '${DECK_PASS}' | sudo -S systemctl restart plugin_loader.service && \ + sleep 1 && echo 'restarted'" + +echo "✓ Deployed — plugin loaded:" +sshpass -p "$DECK_PASS" ssh -o StrictHostKeyChecking=no "${DECK_USER}@${DECK_IP}" \ + "ls -t /home/deck/homebrew/logs/decky-vault/ | head -1 | xargs -I{} tail -1 '/home/deck/homebrew/logs/decky-vault/{}'" \ No newline at end of file diff --git a/plugins/decky-vault/src/lib/store.ts b/plugins/decky-vault/src/lib/store.ts index 9320749..b6d5553 100644 --- a/plugins/decky-vault/src/lib/store.ts +++ b/plugins/decky-vault/src/lib/store.ts @@ -34,6 +34,8 @@ export interface SessionData { hardwareName: string osVersion: string protonVersion: string + versionString: string + buildId: string // Manual inputs (filled by user in the form) upscalerType: string upscalerVersion: string @@ -66,6 +68,8 @@ function createEmptySession(): SessionData { hardwareName: "", osVersion: "", protonVersion: "", + versionString: "", + buildId: "", upscalerType: "none", upscalerVersion: "", frameGenMethod: "none", @@ -244,6 +248,8 @@ export function buildImportPayload(sess: SessionData): DeckyVaultImportV1 { fpsHigh: sess.fpsHigh, protonVersion: sess.protonVersion || null, osVersion: sess.osVersion || null, + versionString: sess.versionString || null, + buildId: sess.buildId || null, upscalerType: sess.upscalerType, upscalerVersion: sess.upscalerVersion || null, frameGenMethod: sess.frameGenMethod,