pc setup v2: one-screen layout with a time slider, photo grid and lightbox
All checks were successful
Deploy production / deploy (push) Successful in 1m7s

- hardware: slots, sections, usage, install/removal dates, images and a hidden flag on
  components and devices; setup_photos table; setup_overview view gains the new columns
- data: hide the 2021 friends-PC build and the wrong 140mm / unknown fans (kept, not deleted);
  7x SL-INF + 3 Kraken stock fans; mice, audio, desk extras; MacBook Pro 16 M2 Pro 1 TB;
  dates for every swap so the slider can rebuild the setup on any day
- self-hosted product images in static/hardware (webp + thumbs)
- prices and order details are owner-only (in a popover)
This commit is contained in:
2026-10-25 16:15:51 +00:00
parent d414e7190e
commit eff5a24985
204 changed files with 32374 additions and 776 deletions

View File

@@ -1,5 +1,14 @@
import { isOwner, requireOwner } from '#lib/server/owner.js';
import { deleteRow, loadSetup, saveDevice, saveGame, savePart } from '#lib/server/hardware.js';
import {
addPhoto,
deletePhoto,
deleteRow,
loadSetup,
saveDevice,
saveGame,
savePart
} from '#lib/server/hardware.js';
import { deleteObject, setupImageKey, uploadImage } from '#lib/server/r2.js';
import { fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
@@ -7,15 +16,16 @@ import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async (event) => {
const owner = isOwner(event);
if (owner) event.setHeaders({ 'cache-control': 'private, no-store' });
const { devices, games } = await loadSetup(owner);
const { devices, games, photos } = await loadSetup(owner);
return {
owner,
devices,
games,
photos,
seo: {
title: 'PC setup',
description:
'Every part in my PC with specs and prices, my other devices and servers, the planned next build and my mouse sensitivities.'
'My PC, desk and devices on one screen, with a time slider that shows the setup on any day since 2021, plus the planned next build and my mouse sensitivities.'
}
};
};
@@ -49,5 +59,33 @@ export const actions: Actions = {
deleteGame: async (event) => {
requireOwner(event);
return run(deleteRow('game', await event.request.formData()));
},
addPhoto: async (event) => {
requireOwner(event);
const form = await event.request.formData();
const photo = form.get('photo');
if (
!(photo instanceof File) ||
!['image/jpeg', 'image/png', 'image/webp'].includes(photo.type) ||
!photo.size ||
photo.size > 15 * 1024 * 1024
)
return fail(400, { message: 'Choose a JPEG, PNG or WebP image up to 15 MB.' });
let url: string;
try {
url = (await uploadImage(photo, 'setup')).publicUrl;
} catch (cause) {
console.error('setup photo upload failed', cause);
return fail(503, { message: 'Image storage is unavailable.' });
}
return run(addPhoto(url, form));
},
deletePhoto: async (event) => {
requireOwner(event);
const r = await deletePhoto(await event.request.formData());
if (!r.ok) return fail(400, { message: r.message });
const key = r.url ? setupImageKey(r.url) : null;
if (key) await deleteObject(key).catch(() => {});
return { saved: true };
}
};

File diff suppressed because it is too large Load Diff