fix(plugin): screenshot limit, version/build fields, delete button, workspace scripts
- 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)
This commit is contained in:
@@ -169,6 +169,9 @@ export function PresetDetailModal({
|
||||
const [activeTab, setActiveTab] = useState<TabKey>("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({
|
||||
<div className='flex flex-col gap-3'>
|
||||
{preset.screenshots.map(
|
||||
(ss, i) => (
|
||||
<button
|
||||
key={
|
||||
ss.id
|
||||
}
|
||||
onClick={() => {
|
||||
setLightboxIndex(
|
||||
i,
|
||||
)
|
||||
setLightboxOpen(
|
||||
true,
|
||||
)
|
||||
}}
|
||||
className='block cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary/50 rounded-lg overflow-hidden w-full'
|
||||
aria-label={`View screenshot ${i + 1}`}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={
|
||||
ss.url
|
||||
}
|
||||
alt={`Screenshot ${i + 1}`}
|
||||
className='w-full h-auto object-cover border border-border hover:border-primary/50 transition-colors rounded-lg'
|
||||
loading='lazy'
|
||||
/>
|
||||
</button>
|
||||
<div key={ss.id} className='relative group'>
|
||||
<button
|
||||
onClick={() => {
|
||||
setLightboxIndex(
|
||||
i,
|
||||
)
|
||||
setLightboxOpen(
|
||||
true,
|
||||
)
|
||||
}}
|
||||
className='block w-full cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary/50 rounded-lg overflow-hidden'
|
||||
aria-label={`View screenshot ${i + 1}`}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={ss.url}
|
||||
alt={`Screenshot ${i + 1}`}
|
||||
className='w-full h-auto object-cover border border-border hover:border-primary/50 transition-colors rounded-lg'
|
||||
loading='lazy'
|
||||
/>
|
||||
</button>
|
||||
{(isOwner || isAdmin) && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleDeleteScreenshot(ss.id)
|
||||
}}
|
||||
disabled={deletingScreenshotId === ss.id}
|
||||
className='absolute top-2 right-2 p-1.5 rounded-md bg-black/50 text-white/60 hover:text-red-400 hover:bg-red-500/20 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer disabled:cursor-not-allowed'
|
||||
aria-label={`Delete screenshot ${i + 1}`}
|
||||
title='Delete screenshot'
|
||||
>
|
||||
<TrashIcon className='h-3.5 w-3.5' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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()),
|
||||
|
||||
+5
-1
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+30
@@ -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/{}'"
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user