refactor: auto format

This commit is contained in:
waltem01 2024-03-14 15:25:46 +01:00
parent 04f905a841
commit 100d37e552
11 changed files with 638 additions and 640 deletions

View File

@ -18,10 +18,10 @@ export function hexToRgb(hex: string): Color | null {
const result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null;
}
@ -36,7 +36,5 @@ export function getLuminance(rgb: Color): number {
// Convert an rgb color object to a hex color string
export function rgbToHex(color: Color): string {
return [color.r, color.g, color.b]
.map((e) => e.toString(16).padStart(2, '0'))
.join('');
return [color.r, color.g, color.b].map((e) => e.toString(16).padStart(2, '0')).join('');
}

View File

@ -1,23 +1,22 @@
import type { APIResponse } from '$lib/interfaces';
export async function redirectAPI({ form, fdata }: { form?: HTMLFormElement; fdata?: FormData }) {
if (!fdata && form) fdata = new FormData(form);
else if (!fdata) throw new Error('No formdata provided!');
// Get endpoint, prefer form, then formdata or empty
const endpoint = (form?.dataset.endpoint ?? fdata.get('endpoint')) ?? '';
// Append endpoint to formdata
if (!fdata?.has('endpoint'))
fdata.append('endpoint', endpoint);
// Send request to be redirected to given endpoint
const response = await fetch('/api/redirect', {
method: 'POST',
body: fdata
});
// Await respose from webserver
const mdata = (await response.json()) as APIResponse;
// Basic error handling
if (!mdata.success) alert(`Error while processing '${endpoint}'!`);
}
import type { APIResponse } from '$lib/interfaces';
export async function redirectAPI({ form, fdata }: { form?: HTMLFormElement; fdata?: FormData }) {
if (!fdata && form) fdata = new FormData(form);
else if (!fdata) throw new Error('No formdata provided!');
// Get endpoint, prefer form, then formdata or empty
const endpoint = form?.dataset.endpoint ?? fdata.get('endpoint') ?? '';
// Append endpoint to formdata
if (!fdata?.has('endpoint')) fdata.append('endpoint', endpoint);
// Send request to be redirected to given endpoint
const response = await fetch('/api/redirect', {
method: 'POST',
body: fdata
});
// Await respose from webserver
const mdata = (await response.json()) as APIResponse;
// Basic error handling
if (!mdata.success) alert(`Error while processing '${endpoint}'!`);
}

View File

@ -46,9 +46,6 @@ export function createGridArray(matrix: Matrix): MatrixCell[][] {
const { factor } = scale;
return Array.from({ length: height / factor }, (_, y) =>
Array.from(
{ length: width / factor },
(_, x) => ({ x, y, color: '000000' }) as MatrixCell
)
Array.from({ length: width / factor }, (_, x) => ({ x, y, color: '000000' }) as MatrixCell)
) as MatrixCell[][];
}

View File

@ -2,4 +2,4 @@ import { API_SERVER_IP, API_SERVER_PORT } from '$env/static/private';
export function buildAPIStr(endpoint: string) {
return `http://${API_SERVER_IP ?? 'localhost'}:${API_SERVER_PORT ?? '8080'}/${endpoint}`;
}
}

View File

@ -1,10 +1,10 @@
<h1 class="font-bold text-2xl">Did you mean:</h1>
<h2 class="font-bold text-xl">
<a class="underline text-blue-600" href="/admin">Admin/Control Panel</a>
</h2>
<h2 class="font-bold text-xl">
<a class="underline text-blue-600" href="/image">Image upload</a>
</h2>
<h2 class="font-bold text-xl">
<a class="underline text-blue-600" href="/clock">Clock</a>
</h2>
<h1 class="font-bold text-2xl">Did you mean:</h1>
<h2 class="font-bold text-xl">
<a class="underline text-blue-600" href="/admin">Admin/Control Panel</a>
</h2>
<h2 class="font-bold text-xl">
<a class="underline text-blue-600" href="/image">Image upload</a>
</h2>
<h2 class="font-bold text-xl">
<a class="underline text-blue-600" href="/clock">Clock</a>
</h2>

View File

@ -227,7 +227,9 @@
matrix = initializeMatrix();
matrix.scale = {
factor: 4,
// 0.1875 (made up padding) * 192 (height when it was made up) * 4 (scaling when it was made up)
// 0.1875 (made up padding) *
// 192 (height when it was made up) *
// 4 (scaling when it was made up)
padding: 144 / matrix.width
};
matrix.grid = createGridArray(matrix);

View File

@ -1,28 +1,28 @@
import type { APIResponse } from "$lib/interfaces";
import { json, type RequestHandler } from "@sveltejs/kit";
export const GET: RequestHandler = async ({ url }) => {
// Get endpoint on main API from searchparams
const params = url.searchParams;
const subreddit = params.get('subreddit');
// Return if param not found
if (subreddit === null) return json({ success: false } as APIResponse);
// Get subreddit page data
const response = await fetch(`https://www.reddit.com/r/${subreddit}/`, {
headers: new Headers({
"User-Agent": "MatrixRedditMemes/0.0.1"
})
});
// Read data as string
const text = await response.text();
// Get all sources of posts
const regex = /<img[^>]+role="presentation"[^>]+src="(.*?)"[^>]*>/g;
let results = [], match;
while ((match = regex.exec(text)) !== null)
results.push(match[1]);
// Return source data
return json({ success: true, results } as APIResponse);
};
import type { APIResponse } from '$lib/interfaces';
import { json, type RequestHandler } from '@sveltejs/kit';
export const GET: RequestHandler = async ({ url }) => {
// Get endpoint on main API from searchparams
const params = url.searchParams;
const subreddit = params.get('subreddit');
// Return if param not found
if (subreddit === null) return json({ success: false } as APIResponse);
// Get subreddit page data
const response = await fetch(`https://www.reddit.com/r/${subreddit}/`, {
headers: new Headers({
'User-Agent': 'MatrixRedditMemes/0.0.1'
})
});
// Read data as string
const text = await response.text();
// Get all sources of posts
const regex = /<img[^>]+role="presentation"[^>]+src="(.*?)"[^>]*>/g;
let results = [],
match;
while ((match = regex.exec(text)) !== null) results.push(match[1]);
// Return source data
return json({ success: true, results } as APIResponse);
};

View File

@ -1,241 +1,241 @@
<script lang="ts">
import { initializeMatrix, type Matrix } from '$lib/client/matrix';
import { redirectAPI } from '$lib/client/httpRequests';
import { onMount } from 'svelte';
// Data structure to keep track of clock
interface Clock {
// Clock circle radius
body: number;
// Clock hand radii
hands: {
second: number;
minute: number;
hour: number;
};
// Clock time percentages
time: {
second: number;
minute: number;
hour: number;
};
// Clock hand positions
positions?: {
second: {
x: number;
y: number;
};
minute: {
x: number;
y: number;
};
hour: {
x: number;
y: number;
};
};
// Clock update speed in seconds
speed: number;
}
// Data structure to keep track of params when displaying clock parts on matrix
interface DisplayParams {
x: string;
y: string;
r?: string;
position?: {
x: string;
y: string;
};
}
//.System variables
let clockEnabled: boolean = false;
let matrix: Matrix;
const clock: Clock = {
body: 48,
hands: {
second: 40,
minute: 30,
hour: 15
},
time: {
second: 0,
minute: 0,
hour: 0
},
speed: 1.25
};
// Constant system values
const TWO_PI = 2 * Math.PI;
const HALF_PI = Math.PI / 2;
// Calculate (x = r * cos(a), y = r * sin(a))
function calcPos(time: 'second' | 'minute' | 'hour', operation: 'sin' | 'cos') {
const radius = clock.hands[time];
const angle = TWO_PI * clock.time[time];
const relative = Math[operation](angle - HALF_PI);
return 50 + radius * relative;
}
async function update() {
// Get current datetime and date
const current = new Date(Date.now());
const day = new Date(current.toDateString());
// Calculate millis diff between now and midnight
const milli = current.getTime() - day.getTime();
// Calculate seconds, minutes and hours
const second = milli / 1000;
const minute = second / 60;
const hour = minute / 60;
// Update time variables
clock.time = {
second: second / 60,
minute: minute / 60,
hour: (hour % 12) / 12
};
// Update hand positions
clock.positions = {
second: {
x: calcPos('second', 'cos'),
y: calcPos('second', 'sin')
},
minute: {
x: calcPos('minute', 'cos'),
y: calcPos('minute', 'sin')
},
hour: {
x: calcPos('hour', 'cos'),
y: calcPos('hour', 'sin')
}
};
// If enabled, update clock display
if (clockEnabled) await displayMatrixClock();
}
// Displays a clock part on the matrix display
async function displayClockPart(
part: 'body' | 'seconds' | 'minutes' | 'hours',
params: DisplayParams
) {
const fdata = new FormData();
if (part === 'body') {
// If part is 'body', append the 'r' parameter to the form data
fdata.append('endpoint', 'circle');
fdata.append('x', params.x);
fdata.append('y', params.y);
fdata.append('r', params.r!);
} else {
// If part is not 'body', convert the position coordinates to matrix coordinates and append to form data
const coords = coordsToMatrix(params.position!.x, params.position!.y);
fdata.append('endpoint', 'line');
fdata.append('x1', params.x);
fdata.append('y1', params.y);
fdata.append('x2', coords.x);
fdata.append('y2', coords.y);
}
return await redirectAPI({ fdata });
}
// Converts the x and y number values in the given positions object to strings
function convertNumbers(positions: { x: number; y: number }): { x: string; y: string } {
return {
x: positions.x.toString(),
y: positions.y.toString()
};
}
// Converts the x and y values from percentage to matrix coordinates
function coordsToMatrix(x: string, y: string): { x: string; y: string } {
// Calculate the matrix coordinates based on the percentage values of x and y
return convertNumbers({
x: Math.round((Number(x) / 100) * matrix.width),
y: Math.round((Number(y) / 100) * matrix.height)
});
}
// Displays the clock on the matrix display, converting number values to strings where necessary
async function displayMatrixClock() {
const middle = coordsToMatrix('50', '50');
// Display the body of the clock
await displayClockPart('body', {
x: middle.x,
y: middle.y,
r: ((clock.body / 100) * matrix.height).toFixed(0)
});
// Display the seconds of the clock, converting position coordinates to strings
await displayClockPart('seconds', {
x: middle.x,
y: middle.y,
position: convertNumbers(clock.positions?.second!)
});
// Display the minutes of the clock, converting position coordinates to strings
await displayClockPart('minutes', {
x: middle.x,
y: middle.y,
position: convertNumbers(clock.positions?.minute!)
});
// Display the hours of the clock, converting position coordinates to strings
await displayClockPart('hours', {
x: middle.x,
y: middle.y,
position: convertNumbers(clock.positions?.hour!)
});
// Update the clock by fetching the update API endpoint
await fetch('/api/redirect?endpoint=update');
}
onMount(() => {
setInterval(update, clock.speed * 1000);
matrix = initializeMatrix();
});
</script>
<!-- Title -->
<h1 class="text-4xl font-bold m-5 text-center">Matrix Clock Display</h1>
<!-- Update toggle form -->
<form class="m-1 grid grid-cols-2" on:submit|preventDefault={() => (clockEnabled = !clockEnabled)}>
<label class="border-2 border-black border-r-0 pl-1" for="toggle">Toggle clock usage:</label>
<input
class="border-2 border-black cursor-pointer hover:bg-gray-200"
id="toggle"
type="submit"
value="Submit"
/>
</form>
<!-- Virtual clock display using SVG -->
<div class="bg-black">
<svg class="text-white" viewBox="0 0 100 100">
<!-- Clock body -->
<circle class="stroke-current" cx="50" cy="50" r={clock.body} fill="none" stroke-width="2" />
<!-- Clock seconds hand -->
<line
class="stroke-current"
x1="50"
y1="50"
x2={clock.positions?.second.x ?? 50}
y2={clock.positions?.second.y ?? 50}
stroke-width=".5"
/>
<!-- Clock minutes hand -->
<line
class="stroke-current"
x1="50"
y1="50"
x2={clock.positions?.minute.x ?? 50}
y2={clock.positions?.minute.y ?? 50}
stroke-width="1"
/>
<!-- Clock hours hand -->
<line
class="stroke-current"
x1="50"
y1="50"
x2={clock.positions?.hour.x ?? 50}
y2={clock.positions?.hour.y ?? 50}
stroke-width="2"
/>
</svg>
</div>
<script lang="ts">
import { initializeMatrix, type Matrix } from '$lib/client/matrix';
import { redirectAPI } from '$lib/client/httpRequests';
import { onMount } from 'svelte';
// Data structure to keep track of clock
interface Clock {
// Clock circle radius
body: number;
// Clock hand radii
hands: {
second: number;
minute: number;
hour: number;
};
// Clock time percentages
time: {
second: number;
minute: number;
hour: number;
};
// Clock hand positions
positions?: {
second: {
x: number;
y: number;
};
minute: {
x: number;
y: number;
};
hour: {
x: number;
y: number;
};
};
// Clock update speed in seconds
speed: number;
}
// Data structure to keep track of params when displaying clock parts on matrix
interface DisplayParams {
x: string;
y: string;
r?: string;
position?: {
x: string;
y: string;
};
}
//.System variables
let clockEnabled: boolean = false;
let matrix: Matrix;
const clock: Clock = {
body: 48,
hands: {
second: 40,
minute: 30,
hour: 15
},
time: {
second: 0,
minute: 0,
hour: 0
},
speed: 1.25
};
// Constant system values
const TWO_PI = 2 * Math.PI;
const HALF_PI = Math.PI / 2;
// Calculate (x = r * cos(a), y = r * sin(a))
function calcPos(time: 'second' | 'minute' | 'hour', operation: 'sin' | 'cos') {
const radius = clock.hands[time];
const angle = TWO_PI * clock.time[time];
const relative = Math[operation](angle - HALF_PI);
return 50 + radius * relative;
}
async function update() {
// Get current datetime and date
const current = new Date(Date.now());
const day = new Date(current.toDateString());
// Calculate millis diff between now and midnight
const milli = current.getTime() - day.getTime();
// Calculate seconds, minutes and hours
const second = milli / 1000;
const minute = second / 60;
const hour = minute / 60;
// Update time variables
clock.time = {
second: second / 60,
minute: minute / 60,
hour: (hour % 12) / 12
};
// Update hand positions
clock.positions = {
second: {
x: calcPos('second', 'cos'),
y: calcPos('second', 'sin')
},
minute: {
x: calcPos('minute', 'cos'),
y: calcPos('minute', 'sin')
},
hour: {
x: calcPos('hour', 'cos'),
y: calcPos('hour', 'sin')
}
};
// If enabled, update clock display
if (clockEnabled) await displayMatrixClock();
}
// Displays a clock part on the matrix display
async function displayClockPart(
part: 'body' | 'seconds' | 'minutes' | 'hours',
params: DisplayParams
) {
const fdata = new FormData();
if (part === 'body') {
// If part is 'body', append the 'r' parameter to the form data
fdata.append('endpoint', 'circle');
fdata.append('x', params.x);
fdata.append('y', params.y);
fdata.append('r', params.r!);
} else {
// If part is not 'body', convert the position coordinates to matrix coordinates and append to form data
const coords = coordsToMatrix(params.position!.x, params.position!.y);
fdata.append('endpoint', 'line');
fdata.append('x1', params.x);
fdata.append('y1', params.y);
fdata.append('x2', coords.x);
fdata.append('y2', coords.y);
}
return await redirectAPI({ fdata });
}
// Converts the x and y number values in the given positions object to strings
function convertNumbers(positions: { x: number; y: number }): { x: string; y: string } {
return {
x: positions.x.toString(),
y: positions.y.toString()
};
}
// Converts the x and y values from percentage to matrix coordinates
function coordsToMatrix(x: string, y: string): { x: string; y: string } {
// Calculate the matrix coordinates based on the percentage values of x and y
return convertNumbers({
x: Math.round((Number(x) / 100) * matrix.width),
y: Math.round((Number(y) / 100) * matrix.height)
});
}
// Displays the clock on the matrix display, converting number values to strings where necessary
async function displayMatrixClock() {
const middle = coordsToMatrix('50', '50');
// Display the body of the clock
await displayClockPart('body', {
x: middle.x,
y: middle.y,
r: ((clock.body / 100) * matrix.height).toFixed(0)
});
// Display the seconds of the clock, converting position coordinates to strings
await displayClockPart('seconds', {
x: middle.x,
y: middle.y,
position: convertNumbers(clock.positions?.second!)
});
// Display the minutes of the clock, converting position coordinates to strings
await displayClockPart('minutes', {
x: middle.x,
y: middle.y,
position: convertNumbers(clock.positions?.minute!)
});
// Display the hours of the clock, converting position coordinates to strings
await displayClockPart('hours', {
x: middle.x,
y: middle.y,
position: convertNumbers(clock.positions?.hour!)
});
// Update the clock by fetching the update API endpoint
await fetch('/api/redirect?endpoint=update');
}
onMount(() => {
setInterval(update, clock.speed * 1000);
matrix = initializeMatrix();
});
</script>
<!-- Title -->
<h1 class="text-4xl font-bold m-5 text-center">Matrix Clock Display</h1>
<!-- Update toggle form -->
<form class="m-1 grid grid-cols-2" on:submit|preventDefault={() => (clockEnabled = !clockEnabled)}>
<label class="border-2 border-black border-r-0 pl-1" for="toggle">Toggle clock usage:</label>
<input
class="border-2 border-black cursor-pointer hover:bg-gray-200"
id="toggle"
type="submit"
value="Submit"
/>
</form>
<!-- Virtual clock display using SVG -->
<div class="bg-black">
<svg class="text-white" viewBox="0 0 100 100">
<!-- Clock body -->
<circle class="stroke-current" cx="50" cy="50" r={clock.body} fill="none" stroke-width="2" />
<!-- Clock seconds hand -->
<line
class="stroke-current"
x1="50"
y1="50"
x2={clock.positions?.second.x ?? 50}
y2={clock.positions?.second.y ?? 50}
stroke-width=".5"
/>
<!-- Clock minutes hand -->
<line
class="stroke-current"
x1="50"
y1="50"
x2={clock.positions?.minute.x ?? 50}
y2={clock.positions?.minute.y ?? 50}
stroke-width="1"
/>
<!-- Clock hours hand -->
<line
class="stroke-current"
x1="50"
y1="50"
x2={clock.positions?.hour.x ?? 50}
y2={clock.positions?.hour.y ?? 50}
stroke-width="2"
/>
</svg>
</div>

View File

@ -1,319 +1,319 @@
<script lang="ts">
import { initializeMatrix, type Matrix } from '$lib/client/matrix';
import { redirectAPI } from '$lib/client/httpRequests';
import type { APIResponse } from '$lib/interfaces';
import { onMount } from 'svelte';
// Data structure to keep track of upload statistics
interface UploadStat {
start: number;
elapsed: string;
interval: NodeJS.Timeout;
}
// Data structure to represent user data
interface SubmitData {
x: number;
y: number;
width: number;
height: number;
update: boolean;
subreddit?: string;
}
// Data structure to represent subreddit posts
interface RedditPosts {
sources: string[];
index: number;
interval: NodeJS.Timeout;
}
// System variables
let imageURL: string | null;
let uploadData: UploadStat, matrix: Matrix;
let uploadStarted = false,
submitData: SubmitData,
posts: RedditPosts;
// Update matrix over endpoint
async function updateMatrix(data: SubmitData) {
if (data.update) await fetch('/api/redirect?endpoint=update');
clearInterval(uploadData.interval);
}
// Placing image over endpoint (by specifying position)
// Image is always max resolution! See resizing in `fileAsDataURL`.
async function placeImage(data: SubmitData) {
const fdata = new FormData();
fdata.append('x', data.x.toString());
fdata.append('y', data.y.toString());
await post(fdata, 'image');
}
// Send image data to endpoint
async function sendImage(url: string) {
const fdata = new FormData();
fdata.append('url', url);
await post(fdata, 'upload');
}
// Handle sending HTTP POST request with specified data
async function post(fdata: FormData, endpoint: string) {
// Append endpoint to formdata for redirection
fdata.append('endpoint', endpoint);
redirectAPI({ fdata });
}
// Load and read file, encode as base64 data url
async function fileAsDataURL(data: SubmitData): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// Set the canvas dimensions
canvas.width = data.width;
canvas.height = data.height;
// Draw the image onto the canvas, resizing it to fit the canvas
context?.drawImage(img, 0, 0, canvas.width, canvas.height);
// Convert the canvas image to a Data URL
resolve(canvas.toDataURL());
};
img.onerror = reject;
// Load image by assigning url
img.src = imageURL!;
});
}
// Getting an image from input
function getImage(event: Event) {
uploadStarted = false;
// Revoke previous image url, if any
if (imageURL) {
URL.revokeObjectURL(imageURL);
imageURL = null;
}
// Get user input
const input = event.target as HTMLInputElement;
const file = input?.files?.[0];
if (!file) return;
// Load image data from file
imageURL = URL.createObjectURL(file);
}
// Handling form submit event
function handleSubmit(event: SubmitEvent) {
// Get image data from submit
const form = event.target as HTMLFormElement;
const fdata = new FormData(form);
submitData = {
x: parseInt(fdata.get('x')?.toString() ?? '0'),
y: parseInt(fdata.get('y')?.toString() ?? '0'),
width: parseInt(fdata.get('w')?.toString() ?? matrix.width.toString()),
height: parseInt(fdata.get('h')?.toString() ?? matrix.height.toString()),
update: fdata.get('update') ? true : false,
subreddit: fdata.get('subreddit')?.toString()
};
// Time upload by saving date
uploadData = {
interval: setInterval(() => {
const now = performance.now();
const diff = now - uploadData.start;
const secs = diff / 1000;
uploadData.elapsed = secs.toFixed(1);
}, 75),
start: performance.now(),
elapsed: '0'
};
// Start upload process
uploadStarted = true;
}
async function fetchReddit(data: SubmitData) {
if (!data.subreddit) return;
// Request subreddit data
const response = await fetch(`/api/reddit?subreddit=${data.subreddit}`);
const mdata = (await response.json()) as APIResponse;
// Ignore failed
if (!mdata.success) return;
const sources = mdata.results! as string[];
if (sources.length === 0) return;
// Get images and automatically update
imageURL = sources[0];
posts = {
interval: setInterval(() => {
uploadStarted = false;
if (posts.index >= posts.sources.length) {
clearInterval(posts.interval);
return;
}
imageURL = posts.sources[++posts.index];
setTimeout(() => (uploadStarted = true), 15);
}, 30000),
index: 0,
sources
};
// Remove subreddit tag from submit data
delete data.subreddit;
}
// On client loaded
onMount(() => {
matrix = initializeMatrix();
});
</script>
<h1 class="text-4xl font-bold m-5 text-center">Matrix Image Upload</h1>
<form class="grid grid-rows-5 m-1" on:submit|preventDefault={handleSubmit}>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="x">Set X coordinate:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="x"
id="x"
min="0"
max={(matrix?.width ?? 192) - 1}
step="1"
value="0"
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="y">Set Y coordinate:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="y"
id="y"
min="0"
max={(matrix?.height ?? 192) - 1}
step="1"
value="0"
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="w">Set image width:</label>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="w"
id="w"
min="1"
max={matrix?.width ?? 192}
step="1"
value={matrix?.width ?? 192}
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="h">Set image height:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="h"
id="h"
min="1"
max={matrix?.height ?? 192}
step="1"
value={matrix?.height ?? 192}
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="image">Set an image:</label
>
<input
class="border-2 border-black border-b-0 bg-gray-100"
type="file"
name="image"
id="image"
accept="image/*"
on:change={getImage}
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="update">Auto update:</label
>
<div class="border-2 border-black border-b-0 pl-1 bg-gray-100 w-full">
<input class="w-full" type="checkbox" name="update" id="update" checked />
</div>
</div>
<input
class="border-2 border-black border-b-0 hover:bg-gray-200 cursor-pointer"
type="submit"
value="Upload!"
/>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="subreddit"
>Subreddit name:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="text"
name="subreddit"
id="subreddit"
/>
</div>
<input
class="border-2 border-black hover:bg-gray-200 cursor-pointer"
type="submit"
value="Fetch!"
/>
{#if imageURL}
<img class="mt-5 block ml-auto mr-auto" src={imageURL} alt="User uploaded or reddit" />
{/if}
</form>
{#if uploadStarted}
{#if submitData.subreddit}
{#await fetchReddit(submitData)}
<p>Fetching subreddit data . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{/await}
{:else}
{#await fileAsDataURL(submitData)}
<p>Loading image data . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then dataUrl}
{#await sendImage(dataUrl)}
<p>Sending image . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then}
{#await placeImage(submitData)}
<p>Placing image . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then}
{#await updateMatrix(submitData)}
<p>Updating matrix . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then}
<p>Done!</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{/await}
{/await}
{/await}
{/await}
{/if}
{/if}
<script lang="ts">
import { initializeMatrix, type Matrix } from '$lib/client/matrix';
import { redirectAPI } from '$lib/client/httpRequests';
import type { APIResponse } from '$lib/interfaces';
import { onMount } from 'svelte';
// Data structure to keep track of upload statistics
interface UploadStat {
start: number;
elapsed: string;
interval: NodeJS.Timeout;
}
// Data structure to represent user data
interface SubmitData {
x: number;
y: number;
width: number;
height: number;
update: boolean;
subreddit?: string;
}
// Data structure to represent subreddit posts
interface RedditPosts {
sources: string[];
index: number;
interval: NodeJS.Timeout;
}
// System variables
let imageURL: string | null;
let uploadData: UploadStat, matrix: Matrix;
let uploadStarted = false,
submitData: SubmitData,
posts: RedditPosts;
// Update matrix over endpoint
async function updateMatrix(data: SubmitData) {
if (data.update) await fetch('/api/redirect?endpoint=update');
clearInterval(uploadData.interval);
}
// Placing image over endpoint (by specifying position)
// Image is always max resolution! See resizing in `fileAsDataURL`.
async function placeImage(data: SubmitData) {
const fdata = new FormData();
fdata.append('x', data.x.toString());
fdata.append('y', data.y.toString());
await post(fdata, 'image');
}
// Send image data to endpoint
async function sendImage(url: string) {
const fdata = new FormData();
fdata.append('url', url);
await post(fdata, 'upload');
}
// Handle sending HTTP POST request with specified data
async function post(fdata: FormData, endpoint: string) {
// Append endpoint to formdata for redirection
fdata.append('endpoint', endpoint);
redirectAPI({ fdata });
}
// Load and read file, encode as base64 data url
async function fileAsDataURL(data: SubmitData): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// Set the canvas dimensions
canvas.width = data.width;
canvas.height = data.height;
// Draw the image onto the canvas, resizing it to fit the canvas
context?.drawImage(img, 0, 0, canvas.width, canvas.height);
// Convert the canvas image to a Data URL
resolve(canvas.toDataURL());
};
img.onerror = reject;
// Load image by assigning url
img.src = imageURL!;
});
}
// Getting an image from input
function getImage(event: Event) {
uploadStarted = false;
// Revoke previous image url, if any
if (imageURL) {
URL.revokeObjectURL(imageURL);
imageURL = null;
}
// Get user input
const input = event.target as HTMLInputElement;
const file = input?.files?.[0];
if (!file) return;
// Load image data from file
imageURL = URL.createObjectURL(file);
}
// Handling form submit event
function handleSubmit(event: SubmitEvent) {
// Get image data from submit
const form = event.target as HTMLFormElement;
const fdata = new FormData(form);
submitData = {
x: parseInt(fdata.get('x')?.toString() ?? '0'),
y: parseInt(fdata.get('y')?.toString() ?? '0'),
width: parseInt(fdata.get('w')?.toString() ?? matrix.width.toString()),
height: parseInt(fdata.get('h')?.toString() ?? matrix.height.toString()),
update: fdata.get('update') ? true : false,
subreddit: fdata.get('subreddit')?.toString()
};
// Time upload by saving date
uploadData = {
interval: setInterval(() => {
const now = performance.now();
const diff = now - uploadData.start;
const secs = diff / 1000;
uploadData.elapsed = secs.toFixed(1);
}, 75),
start: performance.now(),
elapsed: '0'
};
// Start upload process
uploadStarted = true;
}
async function fetchReddit(data: SubmitData) {
if (!data.subreddit) return;
// Request subreddit data
const response = await fetch(`/api/reddit?subreddit=${data.subreddit}`);
const mdata = (await response.json()) as APIResponse;
// Ignore failed
if (!mdata.success) return;
const sources = mdata.results! as string[];
if (sources.length === 0) return;
// Get images and automatically update
imageURL = sources[0];
posts = {
interval: setInterval(() => {
uploadStarted = false;
if (posts.index >= posts.sources.length) {
clearInterval(posts.interval);
return;
}
imageURL = posts.sources[++posts.index];
setTimeout(() => (uploadStarted = true), 15);
}, 30000),
index: 0,
sources
};
// Remove subreddit tag from submit data
delete data.subreddit;
}
// On client loaded
onMount(() => {
matrix = initializeMatrix();
});
</script>
<h1 class="text-4xl font-bold m-5 text-center">Matrix Image Upload</h1>
<form class="grid grid-rows-5 m-1" on:submit|preventDefault={handleSubmit}>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="x">Set X coordinate:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="x"
id="x"
min="0"
max={(matrix?.width ?? 192) - 1}
step="1"
value="0"
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="y">Set Y coordinate:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="y"
id="y"
min="0"
max={(matrix?.height ?? 192) - 1}
step="1"
value="0"
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="w">Set image width:</label>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="w"
id="w"
min="1"
max={matrix?.width ?? 192}
step="1"
value={matrix?.width ?? 192}
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="h">Set image height:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="number"
name="h"
id="h"
min="1"
max={matrix?.height ?? 192}
step="1"
value={matrix?.height ?? 192}
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="image">Set an image:</label
>
<input
class="border-2 border-black border-b-0 bg-gray-100"
type="file"
name="image"
id="image"
accept="image/*"
on:change={getImage}
/>
</div>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="update">Auto update:</label
>
<div class="border-2 border-black border-b-0 pl-1 bg-gray-100 w-full">
<input class="w-full" type="checkbox" name="update" id="update" checked />
</div>
</div>
<input
class="border-2 border-black border-b-0 hover:bg-gray-200 cursor-pointer"
type="submit"
value="Upload!"
/>
<div class="grid grid-cols-2">
<label class="border-2 border-black border-r-0 border-b-0 pl-1" for="subreddit"
>Subreddit name:</label
>
<input
class="border-2 border-black border-b-0 cursor-text pl-1 bg-gray-100"
type="text"
name="subreddit"
id="subreddit"
/>
</div>
<input
class="border-2 border-black hover:bg-gray-200 cursor-pointer"
type="submit"
value="Fetch!"
/>
{#if imageURL}
<img class="mt-5 block ml-auto mr-auto" src={imageURL} alt="User uploaded or reddit" />
{/if}
</form>
{#if uploadStarted}
{#if submitData.subreddit}
{#await fetchReddit(submitData)}
<p>Fetching subreddit data . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{/await}
{:else}
{#await fileAsDataURL(submitData)}
<p>Loading image data . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then dataUrl}
{#await sendImage(dataUrl)}
<p>Sending image . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then}
{#await placeImage(submitData)}
<p>Placing image . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then}
{#await updateMatrix(submitData)}
<p>Updating matrix . . .</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{:then}
<p>Done!</p>
<p>{uploadData.elapsed} seconds elapsed.</p>
{/await}
{/await}
{/await}
{/await}
{/if}
{/if}

View File

@ -10,11 +10,13 @@
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"plugins": [{
"name": "typescript-svelte-plugin",
"assumeIsSvelteProject": false,
"enabled": true
}]
"plugins": [
{
"name": "typescript-svelte-plugin",
"assumeIsSvelteProject": false,
"enabled": true
}
]
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//

View File

@ -6,5 +6,5 @@ export default defineConfig({
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
exclude: ['src/playwright']
},
}
});