Spaces:
Runtime error
Runtime error
File size: 6,430 Bytes
70b8e47 2fe0733 86b7d9d 66ed450 03bb246 6cb70cf 423b87b 6cb70cf 423b87b a7fe81f 423b87b 3d2cb9e 423b87b 32561d8 a7fe81f 560b99e 66ed450 560b99e 423b87b 6cb70cf 423b87b 8c259b0 6cb70cf 8c259b0 423b87b aa3e783 86b7d9d f32cee9 86b7d9d 560b99e c81b6c8 93b70aa 66ed450 2fe0733 86b7d9d a392773 66ed450 d6e3c15 560b99e 86b7d9d a392773 66ed450 d6e3c15 c2163fa 93b70aa f32cee9 86b7d9d c2163fa 70b8e47 6a839c1 ab1e5bd 08cb669 b2fc3c0 08cb669 ab1e5bd 423b87b 418e5b6 b2fc3c0 5fc8a26 6a839c1 be26971 423b87b 5fc8a26 b2fc3c0 418e5b6 5fc8a26 b2fc3c0 423b87b be26971 423b87b 03bb246 5fc8a26 03bb246 ab1e5bd aa3e783 560b99e 66ed450 d6e3c15 66ed450 32561d8 4df0c49 be26971 423b87b 32561d8 423b87b 66ed450 32561d8 7d4e291 70b8e47 6a839c1 f32cee9 6a839c1 27cf1bb 9ed69b7 27cf1bb be26971 560b99e 70b8e47 560b99e 70b8e47 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
<script lang="ts">
import { zoom, zoomIdentity } from 'd3-zoom';
import { min } from 'd3-array';
import { select } from 'd3-selection';
import { onMount } from 'svelte';
import { PUBLIC_UPLOADS } from '$env/static/public';
import { currZoomTransform, canvasEl, isRenderingCanvas, canvasSize } from '$lib/store';
import { useMyPresence, useObject } from '$lib/liveblocks';
import { LiveObject } from '@liveblocks/client';
import type { PromptImgObject } from '$lib/types';
import { FRAME_SIZE, GRID_SIZE } from '$lib/constants';
const myPresence = useMyPresence({ addToHistory: true });
const promptImgStorage = useObject('promptImgStorage');
const height = $canvasSize.height;
const width = $canvasSize.width;
let containerEl: HTMLDivElement;
let canvasCtx: CanvasRenderingContext2D;
const imagesOnCanvas = new Set();
function getpromptImgList(
promptImgList: Record<string, LiveObject<PromptImgObject> | PromptImgObject>
): PromptImgObject[] {
if (promptImgList) {
//sorted by last updated
const canvasPixels = new Map();
for (const x of Array.from(Array(width / GRID_SIZE).keys())) {
for (const y of Array.from(Array(height / GRID_SIZE).keys())) {
canvasPixels.set(`${x * GRID_SIZE}_${y * GRID_SIZE}`, null);
}
}
const list: PromptImgObject[] = Object.values(promptImgList)
.map((e) => {
if (e instanceof LiveObject) {
return e.toObject();
} else {
return e;
}
})
.sort((a, b) => b.date - a.date);
// init
for (const promptImg of list) {
const x = promptImg.position.x;
const y = promptImg.position.y;
for (const i of [...Array(FRAME_SIZE / GRID_SIZE).keys()]) {
for (const j of [...Array(FRAME_SIZE / GRID_SIZE).keys()]) {
const key = `${x + i * GRID_SIZE}_${y + j * GRID_SIZE}`;
if (!canvasPixels.get(key)) {
canvasPixels.set(key, promptImg.id);
}
}
}
}
const ids = new Set([...canvasPixels.values()]);
const filteredImages = list.filter((promptImg) => ids.has(promptImg.id));
return filteredImages.reverse().filter((promptImg) => !imagesOnCanvas.has(promptImg.id));
}
return [];
}
let promptImgList: PromptImgObject[] = [];
$: promptImgList = getpromptImgList($promptImgStorage?.toObject());
$: if (promptImgList) {
renderImages(promptImgList);
}
function to_bbox(
W: number,
H: number,
center: { x: number; y: number },
w: number,
h: number,
margin: number
) {
//https://bl.ocks.org/fabiovalse/b9224bfd64ca96c47f8cdcb57b35b8e2
const kw = (W - margin) / w;
const kh = (H - margin) / h;
const k = min([kw, kh]) || 1;
const x = W / 2 - center.x * k;
const y = H / 2 - center.y * k;
return zoomIdentity.translate(x, y).scale(k);
}
onMount(() => {
const padding = 50;
const scale =
(width + padding * 2) /
(containerEl.clientHeight > containerEl.clientWidth
? containerEl.clientWidth
: containerEl.clientHeight);
const zoomHandler = zoom()
.scaleExtent([1 / scale / 2, 3])
// .translateExtent([
// [-padding, -padding],
// [width + padding, height + padding]
// ])
.tapDistance(10)
.on('zoom', zoomed);
const selection = select($canvasEl.parentElement)
.call(zoomHandler as any)
.call(
zoomHandler.transform as any,
to_bbox(
containerEl.clientWidth,
containerEl.clientHeight,
{ x: width / 2, y: height / 2 },
width,
height,
padding
)
)
// .call(zoomHandler.scaleTo as any, 1 / scale)
.on('pointermove', handlePointerMove)
.on('pointerleave', handlePointerLeave);
canvasCtx = $canvasEl.getContext('2d') as CanvasRenderingContext2D;
function zoomReset() {
const scale =
(width + padding * 2) /
(containerEl.clientHeight > containerEl.clientWidth
? containerEl.clientWidth
: containerEl.clientHeight);
zoomHandler.scaleExtent([1 / scale / 2, 3]);
selection.call(
zoomHandler.transform as any,
to_bbox(
containerEl.clientWidth,
containerEl.clientHeight,
{ x: width / 2, y: height / 2 },
width,
height,
padding
)
);
}
window.addEventListener('resize', zoomReset);
return () => {
window.removeEventListener('resize', zoomReset);
};
});
type ImageRendered = {
img: HTMLImageElement;
position: { x: number; y: number };
id: string;
};
async function renderImages(promptImgList: PromptImgObject[]) {
if (promptImgList.length === 0) return;
$isRenderingCanvas = true;
await Promise.allSettled(
promptImgList.map(
({ imgURL, position, id, room }) =>
new Promise<ImageRendered>((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
const res: ImageRendered = { img, position, id };
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
resolve(res);
};
img.onerror = (err) => {
reject(err);
};
img.src = `${PUBLIC_UPLOADS}/${room}/${imgURL}`;
})
)
).then((values) => {
const images = values
.filter((v) => v.status === 'fulfilled')
.map((v) => (v as PromiseFulfilledResult<ImageRendered>).value);
images.forEach(({ img, position, id }) => {
// keep track of images already rendered
//re draw in order
imagesOnCanvas.add(id);
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
});
});
$isRenderingCanvas = false;
}
function zoomed(e: Event) {
const transform = ($currZoomTransform = e.transform);
$canvasEl.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
}
// Update cursor presence to current pointer location
function handlePointerMove(event: PointerEvent) {
event.preventDefault();
const x = $currZoomTransform.invertX(event.clientX);
const y = $currZoomTransform.invertY(event.clientY);
myPresence.update({
cursor: {
x,
y
}
});
}
// When the pointer leaves the page, set cursor presence to null
function handlePointerLeave() {
myPresence.update({
cursor: null
});
}
</script>
<div
bind:this={containerEl}
class="absolute top-0 left-0 right-0 bottom-0 overflow-hidden z-0 bg-blue-200"
>
<canvas
bind:this={$canvasEl}
{width}
{height}
class="absolute top-0 left-0 bg-white shadow-2xl shadow-blue-500/20"
/>
<slot />
</div>
<style lang="postcss" scoped>
canvas {
transform-origin: 0 0;
}
</style>
|