Spaces:
Runtime error
Runtime error
File size: 8,829 Bytes
be26971 4346adf d6e3c15 95be2ae 4346adf be26971 bf002ae be26971 4346adf be26971 b4efffa 5bdc2c3 d6e3c15 5636b7a b4efffa 4346adf be26971 d6e3c15 be26971 7d4e291 be26971 95be2ae dc37474 7d4e291 bf002ae e847619 95be2ae f7d189a d6e3c15 3205dff d6e3c15 5636b7a d6e3c15 5636b7a d6e3c15 be26971 d6e3c15 5636b7a d6e3c15 bf002ae d6e3c15 5636b7a d6e3c15 a439de3 d6e3c15 5636b7a d6e3c15 a439de3 d6e3c15 5636b7a d6e3c15 bf002ae d6e3c15 f7d189a a439de3 f7d189a 95c652b f7d189a 4b154d2 f7d189a dc37474 be26971 6a839c1 a439de3 f7d189a be26971 2fe0733 4df0c49 2fe0733 d6e3c15 be26971 6a839c1 a439de3 dc37474 f7d189a d6e3c15 b4efffa be26971 d6e3c15 5636b7a d6e3c15 5636b7a d6e3c15 5636b7a 95be2ae be26971 95be2ae 5636b7a 4346adf 95be2ae 5636b7a 95be2ae a439de3 95be2ae a439de3 4346adf 3205dff e847619 a439de3 95be2ae 5bdc2c3 4346adf 3205dff e847619 3205dff d6e3c15 3205dff d6e3c15 95be2ae 4346adf 95be2ae 95c652b 95be2ae d6e3c15 95be2ae be26971 4346adf 95be2ae a439de3 5636b7a 95be2ae 4346adf |
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 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
<script lang="ts">
import PPButton from '$lib/Buttons/PPButton.svelte';
import DragButton from '$lib/Buttons/DragButton.svelte';
import MaskButton from '$lib/Buttons/MaskButton.svelte';
import UndoButton from '$lib/Buttons/UndoButton.svelte';
import LoadingIcon from '$lib/Icons/LoadingIcon.svelte';
import { drag } from 'd3-drag';
import { select } from 'd3-selection';
import { round } from '$lib/utils';
import type { ZoomTransform } from 'd3-zoom';
import { onMount, createEventDispatcher } from 'svelte';
import { useMyPresence } from '$lib/liveblocks';
import { canvasEl, maskEl, loadingState } from '$lib/store';
import { Status } from './types';
const myPresence = useMyPresence();
const dispatch = createEventDispatcher();
export let transform: ZoomTransform;
let maskCtx: CanvasRenderingContext2D;
let position = {
x: 768,
y: 768
};
let frameElement: HTMLDivElement;
let dragEnabled = true;
let isDragging = false;
$: prompt = $myPresence?.currentPrompt;
$: isLoading =
$myPresence?.status === Status.loading || $myPresence?.status === Status.prompting || false;
$: {
if (!dragEnabled && $myPresence.status === Status.loading) {
dragEnabled = true;
}
}
$: coord = {
x: transform.applyX(position.x),
y: transform.applyY(position.y)
};
let offsetX = 0;
let offsetY = 0;
function cropCanvas(cursor: { x: number; y: number }) {
maskCtx.save();
maskCtx.clearRect(0, 0, 512, 512);
maskCtx.globalCompositeOperation = 'source-over';
maskCtx.drawImage($canvasEl, cursor.x, cursor.y, 512, 512, 0, 0, 512, 512);
maskCtx.restore();
}
function drawLine(points: { x: number; y: number; lastx: number; lasty: number }) {
maskCtx.save();
maskCtx.globalCompositeOperation = 'destination-out';
maskCtx.beginPath();
maskCtx.moveTo(points.lastx, points.lasty);
maskCtx.lineTo(points.x, points.y);
maskCtx.lineWidth = 50;
maskCtx.lineCap = 'round';
maskCtx.strokeStyle = 'black';
maskCtx.stroke();
maskCtx.restore();
}
onMount(() => {
maskCtx = $maskEl.getContext('2d') as CanvasRenderingContext2D;
select(frameElement)
.call(dragMoveHandler() as any)
.call(cursorUpdate);
select($maskEl)
.call(maskingHandler() as any)
.call(cursorUpdate);
});
function cursorUpdate(selection: any) {
function handlePointerMove(event: PointerEvent) {
myPresence.update({
cursor: {
x: transform.invertX(event.clientX),
y: transform.invertY(event.clientY)
}
});
}
function handlePointerLeave() {
myPresence.update({
cursor: null
});
}
return selection.on('pointermove', handlePointerMove).on('pointerleave', handlePointerLeave);
}
function maskingHandler() {
let lastx: number;
let lasty: number;
function dragstarted(event: Event) {
if (isLoading) return;
const x = event.x / transform.k;
const y = event.y / transform.k;
lastx = x;
lasty = y;
}
function dragged(event: Event) {
if (isLoading) return;
const x = event.x / transform.k;
const y = event.y / transform.k;
drawLine({ x, y, lastx, lasty });
lastx = x;
lasty = y;
}
// function dragended(event: Event) {}
return drag().on('start', dragstarted).on('drag', dragged);
// on('end', dragended);
}
function dragMoveHandler() {
function dragstarted(event: Event) {
if (isLoading) return;
const rect = (event.sourceEvent.target as HTMLElement).getBoundingClientRect();
if (typeof TouchEvent !== 'undefined' && event.sourceEvent instanceof TouchEvent) {
offsetX = event.sourceEvent.targetTouches[0].pageX - rect.left;
offsetY = event.sourceEvent.targetTouches[0].pageY - rect.top;
} else if (event.sourceEvent instanceof MouseEvent) {
offsetX = event.sourceEvent.pageX - rect.left;
offsetY = event.sourceEvent.pageY - rect.top;
}
isDragging = true;
}
function dragged(event: Event) {
if (isLoading) return;
const x = round(transform.invertX(event.x - offsetX));
const y = round(transform.invertY(event.y - offsetY));
position = {
x,
y
};
myPresence.update({
cursor: {
x: transform.invertX(event.x),
y: transform.invertY(event.y)
}
});
cropCanvas({ x, y });
}
function dragended(event: Event) {
if (isLoading) return;
isDragging = false;
const x = round(transform.invertX(event.x - offsetX));
const y = round(transform.invertY(event.y - offsetY));
cropCanvas({ x, y });
myPresence.update({
frame: {
x,
y
}
});
}
return drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);
}
function toggleDrag() {
dragEnabled = true;
myPresence.update({
status: Status.dragging
});
}
function toggleDrawMask() {
dragEnabled = false;
cropCanvas(position);
myPresence.update({
status: Status.masking
});
}
function cleanMask() {
cropCanvas(position);
}
</script>
<div>
<div
class="absolute top-0 left-0 pen"
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); transform-origin: 0 0;`}
>
<div class="frame">
<canvas class={dragEnabled ? '' : 'bg-white'} bind:this={$maskEl} width="512" height="512" />
<div class="pointer-events-none touch-none">
{#if prompt}
<div class="pointer-events-none touch-none">
<div class="font-bold text-xl text-[#387CFF] text-center px-2 line-clamp-4">
{prompt}
</div>
</div>
{/if}
</div>
{#if isLoading}
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<LoadingIcon />
</div>
{/if}
{#if !isDragging}
<div
class="absolute top-full"
style={`transform: scale(${Math.max(2 - transform.k, 1)}); transform-origin: 0 0;`}
>
<div class="py-3">
<PPButton {isLoading} on:click={() => dispatch('prompt')} />
</div>
{#if $loadingState !== ''}
<div class="p-3 bg-white rounded-lg font-mono">
{#if $loadingState === 'NFSW'}
<h2 class="text-red-500 text-2xl font-bold">NSFW Alert</h2>
<h3 class="text-red-500 text-lg">
Possible NSFW result detected, please try again
</h3>
{/if}
<p>{$loadingState}</p>
</div>
{/if}
</div>
<div
class="absolute left-full"
style={`transform: scale(${Math.max(2 - transform.k, 1)}); transform-origin: 0 0;`}
>
<div class="mx-4">
<DragButton
className={'p-1'}
{isLoading}
isActive={dragEnabled}
on:click={toggleDrag}
/>
<div class="flex bg-white rounded-full mt-3 shadow-lg">
<MaskButton
{isLoading}
className={'p-1'}
isActive={!dragEnabled}
on:click={toggleDrawMask}
/>
{#if !dragEnabled}
<span class="border-gray-800 border-opacity-50 border-r-2 my-2" />
<UndoButton className={'p-1'} {isLoading} on:click={cleanMask} />
{/if}
</div>
</div>
</div>
{/if}
</div>
</div>
<div
bind:this={frameElement}
class="absolute top-0 left-0 w-[512px] h-[512px] ring-8 hand
{dragEnabled ? 'block' : 'hidden'}"
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); transform-origin: 0 0;`}
/>
</div>
<style lang="postcss" scoped>
.frame {
@apply relative grid grid-cols-3 grid-rows-3 ring-8 ring-[#387CFF] w-[512px] h-[512px];
}
.hand {
cursor: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAUCAYAAABvVQZ0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAHSSURBVHgBzVQ9LENRFD4VozZWP4ku/jqJAYNoEQaWNpEwIYJFBINY6UCn6mKTKhYNSTuwVFBiaEgai9bP4CWKUftqv845vU+k7evr2C/5cu6799zvnHvOvQ+gUmEqNimEsKLZQ3YgA0j6TiNXeJPJlIZygEK1yFAmo4rj0Kkg0Jj4DyHyMxKyIt/I+zH5LJrau8V76lPMLa6KjU2vyKhZsbHl1YTX8/dX5X071eyPdX5xDRrr68BiNsNJ+AxsrS1sCf6DIEQub2hoNxJjxO7ivHnMNZqzzlHAIJBIvkBPV6cm7JC11RULWMw1LELRhwf6IPXxxSSRyMU1ztk5mKpmyX9aV0x2KUoitMHW1sxHjd3HWYQyGh7sY1+Z3ZTRMfcpCxLxHwZhZnIc63TEC3TU3iEXj2XdqGGOomKyBhxNq1fi6ZVF3J5tyK+rPGqHXmZX6OAgR61eVCc9UBDE332rzlu3uj0+WRs7GKGxoY5MWi8zZWZygp1KZUSg6yIR1RNzYQeV2/MQLC/MQqmM5HoYb8CDNl/w0GUTlpFLVDPfzi5myZ0DW3szX5Ex5whYLGYFp/pRTAEjyHcaFoX4RvqKPXRTOaJoHJDrmoKMlv0Lqhj8AlEeE/77ZUZMAAAAAElFTkSuQmCC')
8 8,
pointer;
}
.pen {
cursor: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAExSURBVHgBnZJBjkRQEIZLt45YSIwTsLUQbuAqc4LpG3AUO9ueE5idpSFhSyKxsWCBDfKmyozZtcafVLz38r6/Sr0COCDGmEwBZ4TgB0bDfuUchvM8Z7ZtM1VVGa13m9BFuh1FEZNlmdERmfxJvryCi6JwNU2DOI4hCAJAkyVIHMe1mzCVStloS+F53lJJ0yytcA/BFKZprqXfT8FPM/u+r4ZhGJRl2ZyCkyRp0jRlfd8z13X3wyTs7oPg1YDeejeM4ghcN7quAz0ZgoC/AY7jAM/zrSRJb88M+Ov12s7zLN9ut+UAewDjOEJd18u3qqrN2eeHYfgWBMGmy1mWARnRetU0TZ9bBhecpnes4H+iVhj7AaIotoZh3DcNLMsq0MDquu6xHpKhoihf2A8LExRbBj/Uih2c7AwcBQAAAABJRU5ErkJggg==')
8 8,
pointer;
}
</style>
|