feat: support for image interactions

This commit is contained in:
Guz
2024-09-25 16:01:30 -03:00
parent 01976ebf6c
commit daf8282177
6 changed files with 222 additions and 10 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -7,6 +7,7 @@
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^9.6.0",
"@types/node": "^22.6.0",
"blob-util": "^2.0.2",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",

View File

@@ -17,12 +17,21 @@ CREATE TABLE IF NOT EXISTS projects (
type Project = {
id: string;
title: string;
pages: {
title: string;
src: string;
background: string;
}[];
pages: Page[];
};
export type { Project };
type Page = {
title: string;
src: string;
background: string;
iteraction: Iteraction[];
};
type Iteraction = {
x: number;
y: number;
link: string;
};
export type { Project, Iteraction, Page };
export default db;

View File

@@ -49,6 +49,7 @@ export const actions = {
const file = form?.get('file') as File;
const title = form?.get('title') as string;
const color = form?.get('color') as string;
const iteractions = form?.get('iteractions') as string;
console.log(file);
@@ -68,7 +69,8 @@ export const actions = {
proj.pages.push({
title: title,
src: filename,
background: color
background: color,
iteraction: JSON.parse(iteractions)
});
const buf = Buffer.from(await file.arrayBuffer());

View File

@@ -1,6 +1,8 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { arrayBufferToBlob } from 'blob-util';
import IImage from './IteractiveImage.svelte';
export let data: PageData;
const pages = data.project.pages;
@@ -55,6 +57,31 @@
c1[2] * (1 - ratio) + c2[2] * ratio
];
}
let fileInput: Element;
let blobUrl: string | undefined = undefined;
let currentIteraction: { x: number; y: number; link: string };
let iteractionUrl = '';
let iteractions: { x: number; y: number; link: string }[] = [];
let imageElement: Element;
let imageX = 0;
let imageY = 0;
let imageWidth = 0;
let imageHeight = 0;
function readFile(file: Blob) {
let reader = new FileReader();
reader.onloadend = function (e) {
let buf = e.target?.result;
let blob = arrayBufferToBlob(buf as ArrayBuffer, file.type);
blobUrl = window.URL.createObjectURL(blob);
};
reader.readAsArrayBuffer(file);
}
let temp: any;
let images: Map<string, { width: number; height: number }> = new Map();
</script>
{#if browser}
@@ -82,6 +109,12 @@
)}
</code>
</pre>
<details style="position: fixed; bottom: 0; font-size: 0.6rem;">
<pre>
<code>{JSON.stringify(pages)}</code>
</pre>
</details>
{/if}
<dialog open={modal}>
@@ -101,7 +134,96 @@
<form method="POST" action="?/addpage" enctype="multipart/form-data">
<input type="text" required placeholder="Page title" name="title" />
<input type="color" required placeholder="Background color" name="color" />
<input type="file" required name="file" />
{#if blobUrl}
<div class="blob-image">
<div class="blob-image-image">
<div
class="iteraction-box"
style={`${[
`margin-left:${Math.round((imageX / 100) * imageWidth)}px;`,
`margin-top:${Math.round((imageY / 100) * imageHeight)}px;`
].join('')} pointer-events: none;`}
></div>
{#each iteractions as i}
<a
class="iteraction-box"
href={i.link}
style={[
`margin-left:${Math.round((i.x / 100) * imageWidth)}px;`,
`margin-top:${Math.round((i.y / 100) * imageHeight)}px;`
].join('')}
></a>
{/each}
<img
style="margin: auto 0;"
src={blobUrl}
bind:this={imageElement}
on:mousemove={(e) => {
let rect = imageElement.getBoundingClientRect();
imageX = Math.round(((e.clientX - rect.left) / rect.width) * 100);
imageY = Math.round(((e.clientY - rect.top) / rect.height) * 100);
imageWidth = rect.width;
imageHeight = rect.height;
}}
on:click={() => {
currentIteraction ||= { x: 0, y: 0, link: '' };
currentIteraction.x = imageX;
currentIteraction.y = imageY;
}}
alt=""
/>
</div>
<fieldset role="group">
<input
type="url"
placeholder="Iteraction url"
bind:value={iteractionUrl}
on:change={() => {
currentIteraction ||= { x: 0, y: 0, link: '' };
currentIteraction.link = iteractionUrl;
}}
on:mouseout={() => {
currentIteraction ||= { x: 0, y: 0, link: '' };
currentIteraction.link = iteractionUrl;
}}
/>
<input
type="button"
value="Add"
on:click|preventDefault={() => {
iteractions.push({
x: currentIteraction.x,
y: currentIteraction.y,
link: currentIteraction.link
});
iteractions = iteractions;
}}
/>
</fieldset>
<div>
<code>{imageX} {imageY}</code>
<code>
Iteraction: {JSON.stringify(currentIteraction)}
</code>
<input
style="display:hidden;"
type="text"
value={JSON.stringify(iteractions.filter(Boolean))}
name="iteractions"
/>
</div>
</div>
{/if}
<input
type="file"
required
name="file"
bind:this={fileInput}
on:change={() => {
// @ts-ignore
readFile(fileInput.files[0]);
}}
/>
<input type="submit" value="Add page" />
</form>
</article>
@@ -156,10 +278,10 @@
{#each pages as page, key}
{@const coord = key * chunk}
<div class="page" style={`background-color:${page.background}`}>
<img width="1080" height="1920" src={`/files/${data.project.id}/${page.src}`} />
<IImage {page} projectId={data.project.id} />
<form method="POST" action="?/delete-file" class="delete-file">
<fieldset role="group">
<input type="text" disabled value={`${page.src}`} name="file" />
<input type="text" value={`${page.src}`} name="file" />
<input type="submit" value="Delete page" class="pico-background-red-500" />
</fieldset>
</form>
@@ -182,6 +304,20 @@
opacity: 0.3;
}
.iteraction-box {
width: 30px;
height: 30px;
display: block;
background-color: #ff0000;
opacity: 0.3;
position: absolute;
z-index: 100;
}
.blob-image-image {
position: relative;
}
.reader {
display: flex;

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import { onMount } from 'svelte';
type Page = {
title: string;
src: string;
background: string;
iteraction: Iteraction[];
};
type Iteraction = {
x: number;
y: number;
link: string;
};
export let page: Page;
export let projectId: string;
let image: Element;
let width: number;
let height: number;
let browser = false;
function setCoords() {
let rect = image.getBoundingClientRect();
width = rect.width;
height = rect.height;
}
onMount(() => {
setCoords();
browser = true;
});
</script>
<div style="position: relative;" on:resize={() => setCoords()}>
{#if page.iteraction !== undefined && browser}
{#each page.iteraction as i}
<a
class="iteraction-box"
href={i.link}
target="_blank"
style={[
`margin-left:${(i.x / 100) * width}px;`,
`margin-top:${(i.y / 100) * height}px;`
].join('')}
></a>
{/each}
{/if}
<img bind:this={image} width="1080" height="1920" src={`/files/${projectId}/${page.src}`} />
</div>
<style>
.iteraction-box {
width: 30px;
height: 30px;
display: block;
background-color: #ff0000;
opacity: 0.3;
position: absolute;
z-index: 100;
}
</style>