feat(plugin): thumbnail previews for screenshot picker

The screenshot picker now shows a live thumbnail next to each recent
Steam screenshot so users can identify the right one before selecting.

- read_screenshot() Python RPC: returns a base64 data URL for inline
  display. Tries Pillow downscale (320px), falls back to raw bytes
  (Steam Deck screenshots are ~78KB so this is fine without Pillow).
- Thumbnail component: 56x56 rounded image with loading placeholder.
- Thumbnails load progressively in both the picker list and the
  selected-shots rows via a useEffect.
This commit is contained in:
2026-06-29 00:17:04 +08:00
parent 59ecb4e4a6
commit 8cff20c13e
3 changed files with 101 additions and 4 deletions
+33
View File
@@ -646,6 +646,39 @@ exec mangohud "$@"
except Exception as e:
return {"screenshots": [], "error": str(e)}
async def read_screenshot(self, path: str, max_width: int = 320) -> dict:
"""RPC: Read a screenshot file and return it as a base64 data URL,
downscaled to a thumbnail for preview display in the plugin UI.
Returns {dataUrl: str, error?: str}."""
import base64
try:
if not os.path.exists(path):
return {"dataUrl": "", "error": "File not found"}
with open(path, "rb") as f:
raw = f.read()
# Downscale via Pillow if available, else return raw bytes as-is
try:
from PIL import Image
import io
img = Image.open(io.BytesIO(raw))
if img.mode not in ("RGB", "RGBA"):
img = img.convert("RGB")
if img.width > max_width:
ratio = max_width / float(img.width)
img = img.resize((max_width, int(img.height * ratio)))
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=70)
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
return {"dataUrl": f"data:image/jpeg;base64,{b64}"}
except ImportError:
# No Pillow — return the raw file as a data URL
ext = os.path.splitext(path)[1].lower()
mime = "image/png" if ext == ".png" else ("image/webp" if ext == ".webp" else "image/jpeg")
b64 = base64.b64encode(raw).decode("ascii")
return {"dataUrl": f"data:{mime};base64,{b64}"}
except Exception as e:
return {"dataUrl": "", "error": str(e)}
async def upload_screenshots(self, entry_id: str, screenshot_paths: list, api_key: str, base_url: str = "https://deckyvault.xyz") -> dict:
"""RPC: Upload up to 2 screenshots to a performance entry as multipart/form-data.
Returns {success: bool, uploaded: int, error?: str, status?: int}.