route: The image component

This commit is contained in:
wilson 2026-03-16 07:06:38 +00:00
parent 3ebeb8ed6a
commit c3e6f481bb
6 changed files with 170 additions and 15 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ node_modules
dev.db
local.db
uploads

View file

@ -0,0 +1,71 @@
<script lang="ts">
let canvasElement: HTMLCanvasElement;
let errorMessage = $state("");
let photoObjectUrl = $state("");
function handleFileChange(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) {
errorMessage = `No file selected, try again`;
return;
}
const ctx = canvasElement.getContext("2d");
if (!ctx) {
errorMessage = `Could not get 2d context from canvas`;
return;
}
ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
photoObjectUrl = URL.createObjectURL(file);
const image = new Image();
image.src = photoObjectUrl;
image.onload = () => {
const orientation = image.width > image.height ? "landscape" : "portrait";
if (orientation === "landscape") {
ctx.drawImage(image, 0, 0, canvasElement.width, canvasElement.height);
} else {
ctx.drawImage(image, 0, 0, canvasElement.height, canvasElement.width);
}
};
}
</script>
{#if errorMessage}
<div class="error-message">
{errorMessage}
</div>
{/if}
<form
method="POST"
action="/admin/photos"
enctype="multipart/form-data"
class="admin-form"
>
<canvas bind:this={canvasElement}></canvas>
<div class="field">
<label for="title">Title</label>
<input type="text" name="title" id="title" />
</div>
<div class="field">
<input
onchange={handleFileChange}
type="file"
name="file"
accept="image/*"
/>
</div>
<div class="field">
<label for="description">Description</label>
<textarea name="description" id="description"></textarea>
</div>
<button type="submit">Upload</button>
</form>

View file

@ -1,38 +1,45 @@
import type { Cookies } from "@sveltejs/kit";
export class CookieAuthentication {
private readonly cookieValue: string;
private readonly cookieValueArray: string[];
public static cookieName = 'auth'
public static adminAuthRole = 'admin'
public static cookieName = "auth";
public static adminAuthRole = "admin";
constructor(
private readonly cookies: Cookies,
){
this.cookieValue = this.cookies.get(CookieAuthentication.cookieName) ?? ''
this.cookieValueArray = this.cookieValue.split(',')
constructor(private readonly cookies: Cookies) {
this.cookieValue = this.cookies.get(CookieAuthentication.cookieName) ?? "";
this.cookieValueArray = this.cookieValue.split(",");
}
public get isAuthdAsAdmin(): boolean {
let isAuthdAsAdmin = false;
if (this.cookieValueArray.includes(CookieAuthentication.adminAuthRole)) {
isAuthdAsAdmin = true
isAuthdAsAdmin = true;
}
return isAuthdAsAdmin
return isAuthdAsAdmin;
}
public logout() {
if (!this.isAuthdAsAdmin) return;
this.cookies.delete(CookieAuthentication.cookieName, { path: "/" });
}
public setAdminAuthentication(isAuthd: boolean) {
let value = this.cookieValue
let value = this.cookieValue;
if (isAuthd) {
value = Array.from(new Set([...this.cookieValueArray, CookieAuthentication.adminAuthRole])).join(',')
value = Array.from(
new Set([...this.cookieValueArray, CookieAuthentication.adminAuthRole]),
).join(",");
} else {
value = this.cookieValueArray.filter((i) => i !== CookieAuthentication.adminAuthRole).join(',')
value = this.cookieValueArray
.filter((i) => i !== CookieAuthentication.adminAuthRole)
.join(",");
}
this.cookies.set(CookieAuthentication.cookieName, value, { path: '/'})
this.cookies.set(CookieAuthentication.cookieName, value, { path: "/" });
}
}

View file

@ -0,0 +1,25 @@
import { access, readFile } from "node:fs/promises";
import { constants } from "node:fs";
import { PRIVATE_PHOTO_UPLOAD_DIR } from "$env/static/private";
import * as path from "node:path";
import { error, type ServerLoad } from "@sveltejs/kit";
export const GET: ServerLoad = async ({ params, locals }) => {
const { filename } = params;
const proposedFilePath = path.join(PRIVATE_PHOTO_UPLOAD_DIR, filename);
const fileExists = await access(proposedFilePath, constants.F_OK)
.then(() => true)
.catch(() => false);
if (!fileExists) {
return error(404, "File not found");
}
const file = await readFile(proposedFilePath);
const fileExt = path.extname(filename);
const contentType = `image/${fileExt.replace(".", "")}`;
return new Response(file, { headers: { "Content-Type": contentType } });
};

View file

@ -213,3 +213,50 @@ a.no-icon::after {
content: "";
display: none;
}
/* An Alert-like component*/
.alert {
--colour-scheme-border: var(--gray-800);
--font-size: var(--font-size-base);
--colour-scheme-text: var(--gray-800);
--colour-scheme-bg: var(--gray-100);
padding: 8px;
border: 1px solid var(--colour-scheme-border);
border-radius: 4px;
font-size: var(--font-size);
letter-spacing: 0px;
color: var(--colour-scheme-text);
background-color: var(--colour-scheme-bg);
}
.alert.error {
--colour-scheme-border: var(--red-800);
--colour-scheme-text: var(--red-800);
--colour-scheme-bg: var(--red-100);
}
.admin-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.admin-form .field {
display: flex;
flex-direction: column;
gap: 4px;
}
.admin-form .field label {
font-size: var(--font-size);
letter-spacing: 0px;
color: var(--colour-scheme-text);
}
.admin-form .field input {
padding: 8px;
border: 1px solid var(--colour-scheme-border);
border-radius: 4px;
font-size: var(--font-size);
letter-spacing: 0px;
color: var(--colour-scheme-text);
background-color: var(--colour-scheme-bg);
}

View file

@ -10,7 +10,11 @@ const config = {
kit: {
adapter: adapter({ split: false }),
alias: {
$lib: "/src/lib",
$srcPrisma: "/src/prisma",
$generatedPrisma: "/generated/prisma/*",
},
env: {
publicPrefix: "PUBLIC_",
privatePrefix: "PRIVATE_",