Spaces:
Runtime error
Runtime error
Duplicate from huggingface-projects/stable-diffusion-multiplayer
Browse filesCo-authored-by: Radamés Ajna <radames@users.noreply.huggingface.co>
This view is limited to 50 files because it contains too many changes.
See raw diff
- .devcontainer/Dockerfile +11 -0
- .devcontainer/devcontainer.json +35 -0
- .devcontainer/packages.txt +32 -0
- .devcontainer/setup-python-tools.sh +64 -0
- .gitattributes +31 -0
- .github/workflows/size.yml +16 -0
- .github/workflows/sync.yml +20 -0
- .gitignore +21 -0
- .nvmrc +1 -0
- .vscode/extensions.json +6 -0
- .vscode/settings.json +3 -0
- Makefile +12 -0
- README.md +14 -0
- frontend/.env.development.example +3 -0
- frontend/.env.example +3 -0
- frontend/.eslintignore +13 -0
- frontend/.eslintrc.cjs +20 -0
- frontend/.gitignore +12 -0
- frontend/.npmrc +1 -0
- frontend/.prettierignore +13 -0
- frontend/.prettierrc +8 -0
- frontend/README.md +38 -0
- frontend/package.json +52 -0
- frontend/postcss.config.cjs +6 -0
- frontend/src/app.css +25 -0
- frontend/src/app.d.ts +29 -0
- frontend/src/app.html +15 -0
- frontend/src/lib/About.svelte +67 -0
- frontend/src/lib/App.svelte +240 -0
- frontend/src/lib/Buttons/AboutButton.svelte +43 -0
- frontend/src/lib/Buttons/DragButton.svelte +20 -0
- frontend/src/lib/Buttons/MaskButton.svelte +18 -0
- frontend/src/lib/Buttons/PPButton.svelte +29 -0
- frontend/src/lib/Buttons/RoomsSelector.svelte +136 -0
- frontend/src/lib/Buttons/ShareWithCommunity.svelte +80 -0
- frontend/src/lib/Buttons/UndoButton.svelte +20 -0
- frontend/src/lib/Cursor.svelte +35 -0
- frontend/src/lib/Frame.svelte +39 -0
- frontend/src/lib/Icons/Cursor.svelte +24 -0
- frontend/src/lib/Icons/IconCommunity.svelte +25 -0
- frontend/src/lib/Icons/LiveBlocks.svelte +117 -0
- frontend/src/lib/Icons/LoadingIcon.svelte +23 -0
- frontend/src/lib/Icons/Mask.svelte +17 -0
- frontend/src/lib/Icons/Move.svelte +20 -0
- frontend/src/lib/Icons/People.svelte +29 -0
- frontend/src/lib/Icons/Pin.svelte +16 -0
- frontend/src/lib/Icons/Room.svelte +16 -0
- frontend/src/lib/Icons/Undo.svelte +21 -0
- frontend/src/lib/Menu.svelte +57 -0
- frontend/src/lib/PaintCanvas.svelte +284 -0
.devcontainer/Dockerfile
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/codespaces-linux/.devcontainer/base.Dockerfile
|
2 |
+
|
3 |
+
FROM mcr.microsoft.com/vscode/devcontainers/universal:2-focal
|
4 |
+
|
5 |
+
USER root
|
6 |
+
|
7 |
+
COPY packages.txt /root/packages.txt
|
8 |
+
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
9 |
+
&& xargs -r -a /root/packages.txt apt-get install -y
|
10 |
+
|
11 |
+
USER codespace
|
.devcontainer/devcontainer.json
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"postCreateCommand": "oryx build -p virtualenv_name=.venv --log-file /tmp/oryx-build.log --manifest-dir /tmp || echo 'Could not auto-build. Skipping.' && make set-env",
|
3 |
+
"hostRequirements": {
|
4 |
+
"gpus": 1
|
5 |
+
},
|
6 |
+
"features": {
|
7 |
+
"ghcr.io/devcontainers/features/nvidia-cuda:1": {
|
8 |
+
"installCudnn": true
|
9 |
+
}
|
10 |
+
},
|
11 |
+
"build": {
|
12 |
+
"dockerfile": "Dockerfile"
|
13 |
+
},
|
14 |
+
"customizations": {
|
15 |
+
"vscode": {
|
16 |
+
"extensions": [
|
17 |
+
"GitHub.vscode-pull-request-github",
|
18 |
+
"bradlc.vscode-tailwindcss",
|
19 |
+
"svelte.svelte-vscode",
|
20 |
+
"csstools.postcss"
|
21 |
+
]
|
22 |
+
}
|
23 |
+
},
|
24 |
+
"mounts": [
|
25 |
+
"source=codespaces-linux-var-lib-docker,target=/var/lib/docker,type=volume"
|
26 |
+
],
|
27 |
+
"runArgs": [
|
28 |
+
"--cap-add=SYS_PTRACE",
|
29 |
+
"--security-opt",
|
30 |
+
"seccomp=unconfined",
|
31 |
+
"--privileged",
|
32 |
+
"--init"
|
33 |
+
],
|
34 |
+
"remoteUser": "codespace"
|
35 |
+
}
|
.devcontainer/packages.txt
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
python3-opencv
|
2 |
+
libopencv-dev
|
3 |
+
libopencv-core-dev
|
4 |
+
pkg-config
|
5 |
+
libopencv-imgcodecs-dev
|
6 |
+
libopencv-dev
|
7 |
+
libopencv-contrib-dev
|
8 |
+
build-essential
|
9 |
+
cmake
|
10 |
+
git
|
11 |
+
pkg-config
|
12 |
+
libgtk-3-dev
|
13 |
+
libavcodec-dev
|
14 |
+
libavformat-dev
|
15 |
+
libswscale-dev
|
16 |
+
libv4l-dev
|
17 |
+
libxvidcore-dev
|
18 |
+
libx264-dev
|
19 |
+
libjpeg-dev
|
20 |
+
libpng-dev
|
21 |
+
libtiff-dev
|
22 |
+
gfortran
|
23 |
+
openexr
|
24 |
+
libatlas-base-dev
|
25 |
+
python3-dev
|
26 |
+
python3-numpy
|
27 |
+
libtbb2
|
28 |
+
libtbb-dev
|
29 |
+
libdc1394-22-dev
|
30 |
+
libopenexr-dev
|
31 |
+
libgstreamer-plugins-base1.0-dev
|
32 |
+
libgstreamer1.0-dev
|
.devcontainer/setup-python-tools.sh
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env bash
|
2 |
+
|
3 |
+
set -e
|
4 |
+
|
5 |
+
PYTHON=${1:-"python"}
|
6 |
+
USERNAME=${2-"automatic"}
|
7 |
+
|
8 |
+
# Make sure we run the command as non-root user
|
9 |
+
sudoUserIf() {
|
10 |
+
if [ "$(id -u)" -eq 0 ] && [ "${USERNAME}" != "root" ]; then
|
11 |
+
sudo -u ${USERNAME} "$@"
|
12 |
+
else
|
13 |
+
"$@"
|
14 |
+
fi
|
15 |
+
}
|
16 |
+
|
17 |
+
installPythonPackage() {
|
18 |
+
PACKAGE=${1:-""}
|
19 |
+
VERSION=${2:-"latest"}
|
20 |
+
|
21 |
+
# pip skips installation if the package is already installed
|
22 |
+
echo "Installing $PACKAGE..."
|
23 |
+
if [ "${VERSION}" = "latest" ]; then
|
24 |
+
sudoUserIf ${PYTHON} -m pip install ${PACKAGE} --no-cache-dir
|
25 |
+
else
|
26 |
+
sudoUserIf ${PYTHON} -m pip install ${PACKAGE}=="${VERSION}" --no-cache-dir
|
27 |
+
fi
|
28 |
+
}
|
29 |
+
|
30 |
+
# If in automatic mode, determine if a user already exists, if not use vscode
|
31 |
+
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
32 |
+
USERNAME=""
|
33 |
+
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
34 |
+
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
35 |
+
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
36 |
+
USERNAME=${CURRENT_USER}
|
37 |
+
break
|
38 |
+
fi
|
39 |
+
done
|
40 |
+
if [ "${USERNAME}" = "" ]; then
|
41 |
+
USERNAME=vscode
|
42 |
+
fi
|
43 |
+
elif [ "${USERNAME}" = "none" ]; then
|
44 |
+
USERNAME=root
|
45 |
+
USER_UID=0
|
46 |
+
USER_GID=0
|
47 |
+
fi
|
48 |
+
|
49 |
+
# Make sure that Python is available
|
50 |
+
if ! ${PYTHON} --version > /dev/null ; then
|
51 |
+
echo "You need to install Python before installing packages"
|
52 |
+
exit 1
|
53 |
+
fi
|
54 |
+
|
55 |
+
installPythonPackage "numpy" "latest"
|
56 |
+
installPythonPackage "pandas" "latest"
|
57 |
+
installPythonPackage "scipy" "latest"
|
58 |
+
installPythonPackage "matplotlib" "latest"
|
59 |
+
installPythonPackage "seaborn" "latest"
|
60 |
+
installPythonPackage "scikit-learn" "latest"
|
61 |
+
installPythonPackage "tensorflow" "latest"
|
62 |
+
installPythonPackage "keras" "latest"
|
63 |
+
installPythonPackage "torch" "latest"
|
64 |
+
installPythonPackage "requests" "latest"
|
.gitattributes
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
23 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
26 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/size.yml
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Check file size
|
2 |
+
on: # or directly `on: [push]` to run the action on every push on any branch
|
3 |
+
pull_request:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- name: Check large files
|
14 |
+
uses: ActionsDesk/lfs-warning@v2.0
|
15 |
+
with:
|
16 |
+
filesizelimit: 10485760 # this is 10MB so we can sync to HF Spaces
|
.github/workflows/sync.yml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Sync to Hugging Face hub
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
tags:
|
5 |
+
- '*'
|
6 |
+
|
7 |
+
# to run this workflow manually from the Actions tab
|
8 |
+
workflow_dispatch:
|
9 |
+
|
10 |
+
jobs:
|
11 |
+
sync-to-hub:
|
12 |
+
runs-on: ubuntu-latest
|
13 |
+
steps:
|
14 |
+
- uses: actions/checkout@v2
|
15 |
+
with:
|
16 |
+
fetch-depth: 0
|
17 |
+
- name: Push to hub
|
18 |
+
env:
|
19 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
20 |
+
run: git push https://radames:$HF_TOKEN@huggingface.co/spaces/huggingface-projects/stable-diffusion-multiplayer main --force
|
.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
+
node_modules
|
3 |
+
/build
|
4 |
+
/.svelte-kit
|
5 |
+
/package
|
6 |
+
.env
|
7 |
+
.env.*
|
8 |
+
!.env.example
|
9 |
+
|
10 |
+
# Ignore files for PNPM, NPM and YARN
|
11 |
+
pnpm-lock.yaml
|
12 |
+
package-lock.json
|
13 |
+
yarn.lock
|
14 |
+
venv/
|
15 |
+
__pycache__/
|
16 |
+
flagged/
|
17 |
+
data
|
18 |
+
data.db
|
19 |
+
data.json
|
20 |
+
rooms.db
|
21 |
+
sd-multiplayer-data/
|
.nvmrc
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
18
|
.vscode/extensions.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"recommendations": [
|
3 |
+
"bradlc.vscode-tailwindcss",
|
4 |
+
"svelte.svelte-vscode"
|
5 |
+
],
|
6 |
+
}
|
.vscode/settings.json
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"terminal.integrated.defaultProfile.linux": "zsh"
|
3 |
+
}
|
Makefile
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
set-env:
|
2 |
+
pip install -r requirements.txt
|
3 |
+
cd frontend && npm install && cp .env.development.example .env.development && cp .env.example .env
|
4 |
+
build-client:
|
5 |
+
cd frontend && npm install && npm run build && rm -rf ../static && cp -r build/ ../static/
|
6 |
+
build-dev:
|
7 |
+
cd frontend && npm install && npm run build-dev && rm -rf ../static && cp -r build/ ../static/
|
8 |
+
run-front-dev:
|
9 |
+
cd frontend && npm install && npm run dev
|
10 |
+
run-prod:
|
11 |
+
python app.py
|
12 |
+
build-all: run-prod
|
README.md
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Stable Diffusion Multiplayer
|
3 |
+
emoji: 👥⚙️🎨
|
4 |
+
colorFrom: yellow
|
5 |
+
colorTo: gray
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 3.4
|
8 |
+
app_file: run.py
|
9 |
+
fullWidth: true
|
10 |
+
pinned: false
|
11 |
+
duplicated_from: huggingface-projects/stable-diffusion-multiplayer
|
12 |
+
---
|
13 |
+
|
14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
frontend/.env.development.example
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
PUBLIC_WS_INPAINTING="ws://0.0.0.0:7860/gradio/queue/join"
|
2 |
+
PUBLIC_UPLOADS="https://d26smi9133w0oo.cloudfront.net"
|
3 |
+
PUBLIC_API_BASE="/server/api"
|
frontend/.env.example
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
PUBLIC_WS_INPAINTING="wss://spaces.huggingface.tech/huggingface-projects/stable-diffusion-multiplayer/gradio/queue/join"
|
2 |
+
PUBLIC_UPLOADS="https://d26smi9133w0oo.cloudfront.net"
|
3 |
+
PUBLIC_API_BASE="/api"
|
frontend/.eslintignore
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
+
node_modules
|
3 |
+
/build
|
4 |
+
/.svelte-kit
|
5 |
+
/package
|
6 |
+
.env
|
7 |
+
.env.*
|
8 |
+
!.env.example
|
9 |
+
|
10 |
+
# Ignore files for PNPM, NPM and YARN
|
11 |
+
pnpm-lock.yaml
|
12 |
+
package-lock.json
|
13 |
+
yarn.lock
|
frontend/.eslintrc.cjs
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
root: true,
|
3 |
+
parser: '@typescript-eslint/parser',
|
4 |
+
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
5 |
+
plugins: ['svelte3', '@typescript-eslint'],
|
6 |
+
ignorePatterns: ['*.cjs'],
|
7 |
+
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
8 |
+
settings: {
|
9 |
+
'svelte3/typescript': () => require('typescript')
|
10 |
+
},
|
11 |
+
parserOptions: {
|
12 |
+
sourceType: 'module',
|
13 |
+
ecmaVersion: 2020
|
14 |
+
},
|
15 |
+
env: {
|
16 |
+
browser: true,
|
17 |
+
es2017: true,
|
18 |
+
node: true
|
19 |
+
}
|
20 |
+
};
|
frontend/.gitignore
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
+
node_modules
|
3 |
+
/build
|
4 |
+
/.svelte-kit
|
5 |
+
/package
|
6 |
+
.env
|
7 |
+
.env.*
|
8 |
+
!.env.example
|
9 |
+
.vercel
|
10 |
+
.output
|
11 |
+
venv/
|
12 |
+
__pycache__/
|
frontend/.npmrc
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
engine-strict=true
|
frontend/.prettierignore
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
+
node_modules
|
3 |
+
/build
|
4 |
+
/.svelte-kit
|
5 |
+
/package
|
6 |
+
.env
|
7 |
+
.env.*
|
8 |
+
!.env.example
|
9 |
+
|
10 |
+
# Ignore files for PNPM, NPM and YARN
|
11 |
+
pnpm-lock.yaml
|
12 |
+
package-lock.json
|
13 |
+
yarn.lock
|
frontend/.prettierrc
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"useTabs": true,
|
3 |
+
"singleQuote": true,
|
4 |
+
"trailingComma": "none",
|
5 |
+
"printWidth": 100,
|
6 |
+
"pluginSearchDirs": ["."],
|
7 |
+
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
8 |
+
}
|
frontend/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# create-svelte
|
2 |
+
|
3 |
+
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
4 |
+
|
5 |
+
## Creating a project
|
6 |
+
|
7 |
+
If you're seeing this, you've probably already done this step. Congrats!
|
8 |
+
|
9 |
+
```bash
|
10 |
+
# create a new project in the current directory
|
11 |
+
npm create svelte@latest
|
12 |
+
|
13 |
+
# create a new project in my-app
|
14 |
+
npm create svelte@latest my-app
|
15 |
+
```
|
16 |
+
|
17 |
+
## Developing
|
18 |
+
|
19 |
+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
20 |
+
|
21 |
+
```bash
|
22 |
+
npm run dev
|
23 |
+
|
24 |
+
# or start the server and open the app in a new browser tab
|
25 |
+
npm run dev -- --open
|
26 |
+
```
|
27 |
+
|
28 |
+
## Building
|
29 |
+
|
30 |
+
To create a production version of your app:
|
31 |
+
|
32 |
+
```bash
|
33 |
+
npm run build
|
34 |
+
```
|
35 |
+
|
36 |
+
You can preview the production build with `npm run preview`.
|
37 |
+
|
38 |
+
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
frontend/package.json
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "frontend",
|
3 |
+
"version": "0.0.1",
|
4 |
+
"scripts": {
|
5 |
+
"dev": "NODE_ENV=development vite --config vite.config.dev.ts dev",
|
6 |
+
"build-dev": "vite build --mode development",
|
7 |
+
"build": "vite build",
|
8 |
+
"preview": "vite preview",
|
9 |
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
10 |
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
11 |
+
"lint": "prettier --check . && eslint .",
|
12 |
+
"format": "prettier --write ."
|
13 |
+
},
|
14 |
+
"devDependencies": {
|
15 |
+
"@sveltejs/adapter-static": "^1.0.0-next.43",
|
16 |
+
"@sveltejs/kit": "^1.0.0-next.504",
|
17 |
+
"@tailwindcss/forms": "^0.5.3",
|
18 |
+
"@tailwindcss/line-clamp": "^0.4.2",
|
19 |
+
"@types/cookie": "^0.5.1",
|
20 |
+
"@types/d3-array": "^3.0.3",
|
21 |
+
"@types/d3-drag": "^3.0.1",
|
22 |
+
"@types/d3-selection": "^3.0.3",
|
23 |
+
"@types/d3-zoom": "^3.0.1",
|
24 |
+
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
25 |
+
"@typescript-eslint/parser": "^5.38.0",
|
26 |
+
"autoprefixer": "^10.4.12",
|
27 |
+
"d3-scale": "^4.0.2",
|
28 |
+
"eslint": "^8.24.0",
|
29 |
+
"eslint-config-prettier": "^8.3.0",
|
30 |
+
"eslint-plugin-svelte3": "^4.0.0",
|
31 |
+
"postcss": "^8.4.16",
|
32 |
+
"prettier": "^2.6.2",
|
33 |
+
"prettier-plugin-svelte": "^2.7.1",
|
34 |
+
"svelte": "^3.46.0",
|
35 |
+
"svelte-check": "^2.9.1",
|
36 |
+
"svelte-preprocess": "^4.10.7",
|
37 |
+
"tailwindcss": "^3.1.8",
|
38 |
+
"tslib": "^2.3.1",
|
39 |
+
"typescript": "^4.7.4",
|
40 |
+
"vite": "^3.1.3"
|
41 |
+
},
|
42 |
+
"type": "module",
|
43 |
+
"dependencies": {
|
44 |
+
"@fontsource/fira-mono": "^4.5.0",
|
45 |
+
"@liveblocks/client": "^0.18.2",
|
46 |
+
"d3-array": "^3.2.0",
|
47 |
+
"d3-drag": "^3.0.0",
|
48 |
+
"d3-selection": "^3.0.0",
|
49 |
+
"d3-zoom": "^3.0.0",
|
50 |
+
"nanoid": "^4.0.0"
|
51 |
+
}
|
52 |
+
}
|
frontend/postcss.config.cjs
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
}
|
frontend/src/app.css
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
/* Firefox */
|
6 |
+
.x-scroll {
|
7 |
+
scrollbar-width: thin;
|
8 |
+
scrollbar-color: white #2F6DCB;
|
9 |
+
}
|
10 |
+
|
11 |
+
/* Chrome, Edge, and Safari */
|
12 |
+
.x-scroll::-webkit-scrollbar {
|
13 |
+
width: 4px;
|
14 |
+
}
|
15 |
+
|
16 |
+
.x-scroll::-webkit-scrollbar-track {
|
17 |
+
background: white;
|
18 |
+
border-radius: 100px;
|
19 |
+
}
|
20 |
+
|
21 |
+
.x-scroll::-webkit-scrollbar-thumb {
|
22 |
+
background-color: #2F6DCB;
|
23 |
+
border-radius: 100px;
|
24 |
+
border: 2px solid #2F6DCB;
|
25 |
+
}
|
frontend/src/app.d.ts
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// See https://kit.svelte.dev/docs/types#app
|
2 |
+
// for information about these interfaces
|
3 |
+
// and what to do when importing types
|
4 |
+
import type { ZoomTransform } from 'd3-zoom';
|
5 |
+
|
6 |
+
declare global {
|
7 |
+
namespace App {
|
8 |
+
// interface Locals {}
|
9 |
+
// interface PageData {}
|
10 |
+
// interface Platform {}
|
11 |
+
interface Window {
|
12 |
+
parentIFrame: unknown;
|
13 |
+
}
|
14 |
+
}
|
15 |
+
interface Error {
|
16 |
+
code: number;
|
17 |
+
}
|
18 |
+
interface Event {
|
19 |
+
relatedTarget: EventTarget | null;
|
20 |
+
transform: ZoomTransform;
|
21 |
+
x: number;
|
22 |
+
y: number;
|
23 |
+
subject: {
|
24 |
+
x: number;
|
25 |
+
y: number;
|
26 |
+
}
|
27 |
+
sourceEvent: PointerEvent | MouseEvent | TouchEvent
|
28 |
+
}
|
29 |
+
}
|
frontend/src/app.html
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
<meta charset="utf-8" />
|
6 |
+
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
8 |
+
<script src=""></script>
|
9 |
+
%sveltekit.head%
|
10 |
+
</head>
|
11 |
+
<body class="bg-blue-200">
|
12 |
+
<div>%sveltekit.body%</div>
|
13 |
+
</body>
|
14 |
+
|
15 |
+
</html>
|
frontend/src/lib/About.svelte
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import LiveBlocks from '$lib/Icons/LiveBlocks.svelte';
|
3 |
+
export let classList = '';
|
4 |
+
</script>
|
5 |
+
|
6 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
7 |
+
<div
|
8 |
+
on:click
|
9 |
+
class="fixed {classList} w-screen h-screen top-0 left-0 bottom-0 right-0 z-50 items-center justify-center bg-black text-white bg-opacity-90 px-3"
|
10 |
+
>
|
11 |
+
<div class="max-w-md">
|
12 |
+
<h2 class="font-bold text-xl font-mono mb-8">Stable Difussion Multiplayer</h2>
|
13 |
+
<ul>
|
14 |
+
<li class="mb-2">
|
15 |
+
Powered by <a
|
16 |
+
href="https://huggingface.co/docs/hub/spaces-gpus"
|
17 |
+
class="text-blue-400 hover:text-blue-600 underline"
|
18 |
+
>Hugging Face GPU Spaces
|
19 |
+
</a>
|
20 |
+
</li>
|
21 |
+
<li class="mb-2">
|
22 |
+
<a
|
23 |
+
href="https://huggingface.co/docs/diffusers/index"
|
24 |
+
class="text-blue-400 hover:text-blue-600 underline"
|
25 |
+
>
|
26 |
+
Diffusers
|
27 |
+
</a>
|
28 |
+
and
|
29 |
+
<a
|
30 |
+
href="https://github.com/runwayml/stable-diffusion"
|
31 |
+
target="_blank"
|
32 |
+
class="text-blue-400 hover:text-blue-600 underline"
|
33 |
+
rel="noopener noreferrer"
|
34 |
+
>
|
35 |
+
Runwayml Inpainting Stable Diffusion</a
|
36 |
+
>
|
37 |
+
</li>
|
38 |
+
<li class="mb-2">
|
39 |
+
Thanks to <a
|
40 |
+
href="https://twitter.com/lkwq007"
|
41 |
+
target="_blank"
|
42 |
+
rel="noopener noreferrer"
|
43 |
+
class="text-blue-400 hover:text-blue-600 underline"
|
44 |
+
>
|
45 |
+
Lnyan</a
|
46 |
+
>
|
47 |
+
for the original outpainting technique implemented on
|
48 |
+
<a
|
49 |
+
href="https://github.com/lkwq007/stablediffusion-infinity"
|
50 |
+
target="_blank"
|
51 |
+
rel="noopener noreferrer"
|
52 |
+
class="text-blue-400 hover:text-blue-600 underline"
|
53 |
+
>Stable Diffusion Infinity
|
54 |
+
</a>
|
55 |
+
</li>
|
56 |
+
|
57 |
+
<li class="mb-2">
|
58 |
+
<p class="text-base flex items-center">
|
59 |
+
Multiplayer API by
|
60 |
+
<a href="https://liveblocks.io" target="_blank" rel="noopener noreferrer">
|
61 |
+
<LiveBlocks classList={'ml-2'} />
|
62 |
+
</a>
|
63 |
+
</p>
|
64 |
+
</li>
|
65 |
+
</ul>
|
66 |
+
</div>
|
67 |
+
</div>
|
frontend/src/lib/App.svelte
ADDED
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import Cursor from '$lib/Cursor.svelte';
|
3 |
+
import Frame from '$lib/Frame.svelte';
|
4 |
+
import PaintFrame from '$lib/PaintFrame.svelte';
|
5 |
+
import PaintCanvas from '$lib/PaintCanvas.svelte';
|
6 |
+
import Menu from '$lib/Menu.svelte';
|
7 |
+
import PromptModal from '$lib/PromptModal.svelte';
|
8 |
+
import { COLORS, FRAME_SIZE } from '$lib/constants';
|
9 |
+
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
10 |
+
import type { PromptImgKey } from '$lib/types';
|
11 |
+
import { Status } from '$lib/types';
|
12 |
+
import { LiveObject } from '@liveblocks/client';
|
13 |
+
|
14 |
+
import {
|
15 |
+
loadingState,
|
16 |
+
currZoomTransform,
|
17 |
+
maskEl,
|
18 |
+
selectedRoomID,
|
19 |
+
isRenderingCanvas
|
20 |
+
} from '$lib/store';
|
21 |
+
import { useMyPresence, useObject, useOthers } from '$lib/liveblocks';
|
22 |
+
|
23 |
+
const myPresence = useMyPresence();
|
24 |
+
const others = useOthers();
|
25 |
+
let showModal = false;
|
26 |
+
function getKey(position: { x: number; y: number }): PromptImgKey {
|
27 |
+
return `${position.x}_${position.y}`;
|
28 |
+
}
|
29 |
+
|
30 |
+
const promptImgStorage = useObject('promptImgStorage');
|
31 |
+
|
32 |
+
$: isLoading = $myPresence?.status === Status.loading || $isRenderingCanvas || false;
|
33 |
+
function onShowModal(e: CustomEvent) {
|
34 |
+
if (isLoading) return;
|
35 |
+
showModal = e.detail.showModal;
|
36 |
+
if (showModal) {
|
37 |
+
myPresence.update({
|
38 |
+
status: Status.prompting
|
39 |
+
});
|
40 |
+
} else {
|
41 |
+
myPresence.update({
|
42 |
+
status: Status.ready
|
43 |
+
});
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
function onPaint() {
|
48 |
+
showModal = false;
|
49 |
+
generateImage();
|
50 |
+
}
|
51 |
+
function canPaint(position: { x: number; y: number }): boolean {
|
52 |
+
if (!$others) return true;
|
53 |
+
let canPaint = true;
|
54 |
+
for (const { presence } of $others) {
|
55 |
+
if (
|
56 |
+
position.x < presence.frame.x + FRAME_SIZE &&
|
57 |
+
position.x + FRAME_SIZE > presence.frame.x &&
|
58 |
+
position.y < presence.frame.y + FRAME_SIZE &&
|
59 |
+
position.y + FRAME_SIZE > presence.frame.y
|
60 |
+
) {
|
61 |
+
// can paint if presence is only dragging
|
62 |
+
if (presence.status === Status.ready || presence.status === Status.dragging) {
|
63 |
+
canPaint = true;
|
64 |
+
continue;
|
65 |
+
}
|
66 |
+
canPaint = false;
|
67 |
+
break;
|
68 |
+
}
|
69 |
+
}
|
70 |
+
return canPaint;
|
71 |
+
}
|
72 |
+
|
73 |
+
function clearStateMsg(t = 5000) {
|
74 |
+
setTimeout(() => {
|
75 |
+
$loadingState = '';
|
76 |
+
}, t);
|
77 |
+
}
|
78 |
+
async function generateImage() {
|
79 |
+
if (isLoading) return;
|
80 |
+
const prompt = $myPresence.currentPrompt;
|
81 |
+
const position = $myPresence.frame;
|
82 |
+
$loadingState = 'Pending';
|
83 |
+
if (!canPaint(position)) {
|
84 |
+
$loadingState = 'Someone is already painting here';
|
85 |
+
myPresence.update({
|
86 |
+
status: Status.ready
|
87 |
+
});
|
88 |
+
clearStateMsg();
|
89 |
+
return;
|
90 |
+
}
|
91 |
+
const imageKey = getKey(position);
|
92 |
+
const room = $selectedRoomID || 'default';
|
93 |
+
console.log('Generating...', prompt, position);
|
94 |
+
myPresence.update({
|
95 |
+
status: Status.loading
|
96 |
+
});
|
97 |
+
const sessionHash = crypto.randomUUID();
|
98 |
+
const base64Crop = $maskEl.toDataURL('image/webp');
|
99 |
+
|
100 |
+
const hashpayload = {
|
101 |
+
fn_index: 0,
|
102 |
+
session_hash: sessionHash
|
103 |
+
};
|
104 |
+
|
105 |
+
const datapayload = {
|
106 |
+
data: [base64Crop, prompt, 0.75, 7.5, 40, 'patchmatch', room, imageKey]
|
107 |
+
};
|
108 |
+
|
109 |
+
const websocket = new WebSocket(PUBLIC_WS_INPAINTING);
|
110 |
+
// websocket.onopen = async function (event) {
|
111 |
+
// websocket.send(JSON.stringify({ hash: sessionHash }));
|
112 |
+
// };
|
113 |
+
websocket.onclose = (evt) => {
|
114 |
+
if (!evt.wasClean) {
|
115 |
+
$loadingState = 'Error';
|
116 |
+
myPresence.update({
|
117 |
+
status: Status.ready
|
118 |
+
});
|
119 |
+
}
|
120 |
+
};
|
121 |
+
websocket.onmessage = async function (event) {
|
122 |
+
try {
|
123 |
+
const data = JSON.parse(event.data);
|
124 |
+
$loadingState = '';
|
125 |
+
switch (data.msg) {
|
126 |
+
case 'send_hash':
|
127 |
+
websocket.send(JSON.stringify(hashpayload));
|
128 |
+
break;
|
129 |
+
case 'send_data':
|
130 |
+
$loadingState = 'Sending Data';
|
131 |
+
websocket.send(JSON.stringify({ ...hashpayload, ...datapayload }));
|
132 |
+
break;
|
133 |
+
case 'queue_full':
|
134 |
+
$loadingState = 'Queue full';
|
135 |
+
websocket.close();
|
136 |
+
myPresence.update({
|
137 |
+
status: Status.ready
|
138 |
+
});
|
139 |
+
return;
|
140 |
+
case 'estimation':
|
141 |
+
const { rank, queue_size } = data;
|
142 |
+
$loadingState = `On queue ${rank}/${queue_size}`;
|
143 |
+
break;
|
144 |
+
case 'process_generating':
|
145 |
+
$loadingState = data.success ? 'Generating' : 'Error';
|
146 |
+
break;
|
147 |
+
case 'process_completed':
|
148 |
+
try {
|
149 |
+
const params = data.output.data[0] as {
|
150 |
+
is_nsfw: boolean;
|
151 |
+
image: {
|
152 |
+
url: string;
|
153 |
+
filename: string;
|
154 |
+
};
|
155 |
+
};
|
156 |
+
const isNSWF = params.is_nsfw;
|
157 |
+
if (isNSWF) {
|
158 |
+
throw new Error('NFSW');
|
159 |
+
}
|
160 |
+
// const imgBlob = await base64ToBlob(imgBase64);
|
161 |
+
const promptImgParams = {
|
162 |
+
imgURL: params.image.filename
|
163 |
+
};
|
164 |
+
// const imgURL = await uploadImage(imgBlob, promptImgParams);
|
165 |
+
$promptImgStorage.set(imageKey, new LiveObject(promptImgParams));
|
166 |
+
// $promptImgStorage.set(imageKey, promptImgParams);
|
167 |
+
|
168 |
+
console.log(params.image.url);
|
169 |
+
$loadingState = data.success ? 'Complete' : 'Error';
|
170 |
+
clearStateMsg();
|
171 |
+
myPresence.update({
|
172 |
+
status: Status.ready,
|
173 |
+
currentPrompt: ''
|
174 |
+
});
|
175 |
+
} catch (err) {
|
176 |
+
const tError = err as Error;
|
177 |
+
$loadingState = tError?.message;
|
178 |
+
myPresence.update({
|
179 |
+
status: Status.ready
|
180 |
+
});
|
181 |
+
clearStateMsg(10000);
|
182 |
+
}
|
183 |
+
websocket.close();
|
184 |
+
return;
|
185 |
+
case 'process_starts':
|
186 |
+
$loadingState = 'Processing';
|
187 |
+
break;
|
188 |
+
}
|
189 |
+
} catch (e) {
|
190 |
+
console.error(e);
|
191 |
+
$loadingState = 'Error';
|
192 |
+
}
|
193 |
+
};
|
194 |
+
}
|
195 |
+
</script>
|
196 |
+
|
197 |
+
<!-- Show the current user's cursor location -->
|
198 |
+
<div class="text touch-none pointer-events-none">
|
199 |
+
{$loadingState}
|
200 |
+
</div>
|
201 |
+
{#if showModal}
|
202 |
+
<PromptModal
|
203 |
+
on:paint={onPaint}
|
204 |
+
initPrompt={$myPresence?.currentPrompt}
|
205 |
+
on:showModal={onShowModal}
|
206 |
+
/>
|
207 |
+
{/if}
|
208 |
+
<div class="fixed top-0 left-0 z-0 w-screen h-screen min-h-[600px]">
|
209 |
+
<PaintCanvas />
|
210 |
+
|
211 |
+
<main class="z-10 relative">
|
212 |
+
<!-- When others connected, iterate through others and show their cursors -->
|
213 |
+
{#if $others}
|
214 |
+
{#each [...$others] as { connectionId, presence } (connectionId)}
|
215 |
+
{#if (presence?.status === Status.loading || presence?.status === Status.prompting || presence?.status === Status.masking) && presence?.frame}
|
216 |
+
<Frame
|
217 |
+
status={presence.status}
|
218 |
+
position={presence?.frame}
|
219 |
+
prompt={presence?.currentPrompt}
|
220 |
+
transform={$currZoomTransform}
|
221 |
+
/>
|
222 |
+
{/if}
|
223 |
+
{#if presence?.cursor}
|
224 |
+
<Cursor
|
225 |
+
color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
|
226 |
+
position={presence?.cursor}
|
227 |
+
transform={$currZoomTransform}
|
228 |
+
/>
|
229 |
+
{/if}
|
230 |
+
{/each}
|
231 |
+
{/if}
|
232 |
+
<PaintFrame transform={$currZoomTransform} on:showModal={onShowModal} />
|
233 |
+
</main>
|
234 |
+
</div>
|
235 |
+
<div class="fixed bottom-0 md:bottom-16 left-0 right-0 z-10 my-2">
|
236 |
+
<Menu {isLoading} on:showModal={onShowModal} />
|
237 |
+
</div>
|
238 |
+
|
239 |
+
<style lang="postcss" scoped>
|
240 |
+
</style>
|
frontend/src/lib/Buttons/AboutButton.svelte
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { onMount } from 'svelte';
|
3 |
+
import ShareWithCommunity from './ShareWithCommunity.svelte';
|
4 |
+
|
5 |
+
export let isLoading = false;
|
6 |
+
|
7 |
+
let open = true;
|
8 |
+
|
9 |
+
onMount(() => {
|
10 |
+
autoOpen();
|
11 |
+
window.addEventListener('resize', autoOpen);
|
12 |
+
return () => {
|
13 |
+
window.removeEventListener('resize', autoOpen);
|
14 |
+
};
|
15 |
+
});
|
16 |
+
function autoOpen() {
|
17 |
+
open = window.innerWidth > 768;
|
18 |
+
}
|
19 |
+
</script>
|
20 |
+
|
21 |
+
<div class="md:w-[210px] text-sm md:flex flex-col order-last md:order-none">
|
22 |
+
<details {open}>
|
23 |
+
<summary class="md:hidden">More</summary>
|
24 |
+
<p class="inline">
|
25 |
+
Instructions: move the <span class="text-blue-700 bg-blue-300/60 px-0.5">blue square</span>,
|
26 |
+
click "🖍 Paint".
|
27 |
+
</p>
|
28 |
+
<div class="flex justify-between py-2 gap-3">
|
29 |
+
<button on:click class="items-center inline-flex">
|
30 |
+
<svg width=".9em" height=".9em" viewBox="0 0 24 24" class="mr-1"
|
31 |
+
><path
|
32 |
+
fill="currentColor"
|
33 |
+
fill-rule="evenodd"
|
34 |
+
d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10s10-4.477 10-10S17.523 2 12 2zm0 7a1 1 0 0 0-1 1a1 1 0 1 1-2 0a3 3 0 1 1 4.44 2.633a1.404 1.404 0 0 0-.383.288a.303.303 0 0 0-.057.085v.494a1 1 0 1 1-2 0V13c0-.58.253-1.047.539-1.38c.281-.33.63-.572.94-.742A1 1 0 0 0 12 9zm.999 4.011v-.004v.005zM12 15a1 1 0 1 0 0 2h.01a1 1 0 1 0 0-2H12z"
|
35 |
+
clip-rule="evenodd"
|
36 |
+
/></svg
|
37 |
+
>
|
38 |
+
About
|
39 |
+
</button>
|
40 |
+
<ShareWithCommunity {isLoading} />
|
41 |
+
</div>
|
42 |
+
</details>
|
43 |
+
</div>
|
frontend/src/lib/Buttons/DragButton.svelte
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import Move from '$lib/Icons/Move.svelte';
|
3 |
+
export let isActive = false;
|
4 |
+
export let isLoading = false;
|
5 |
+
export let className = '';
|
6 |
+
</script>
|
7 |
+
|
8 |
+
<button
|
9 |
+
on:click
|
10 |
+
disabled={isLoading}
|
11 |
+
class="{className} shadow-lg bg-white rounded-full disabled:opacity-50 {isActive
|
12 |
+
? 'text-blue-700'
|
13 |
+
: 'text-gray-800'}"
|
14 |
+
title="Drag to Move"
|
15 |
+
>
|
16 |
+
<Move />
|
17 |
+
</button>
|
18 |
+
|
19 |
+
<style lang="postcss" scoped>
|
20 |
+
</style>
|
frontend/src/lib/Buttons/MaskButton.svelte
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import Mask from '$lib/Icons/Mask.svelte';
|
3 |
+
export let isActive = false;
|
4 |
+
export let isLoading = false;
|
5 |
+
export let className = '';
|
6 |
+
</script>
|
7 |
+
|
8 |
+
<button
|
9 |
+
on:click
|
10 |
+
disabled={isLoading}
|
11 |
+
class="{className} rounded-full disabled:opacity-50 {isActive ? 'text-white' : 'text-blue-200'}"
|
12 |
+
title="Draw to Mask"
|
13 |
+
>
|
14 |
+
<Mask classList="text-3xl" />
|
15 |
+
</button>
|
16 |
+
|
17 |
+
<style lang="postcss" scoped>
|
18 |
+
</style>
|
frontend/src/lib/Buttons/PPButton.svelte
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let isLoading = false;
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<button
|
6 |
+
on:click
|
7 |
+
disabled={isLoading}
|
8 |
+
class="button-paint {isLoading ? 'cursor-wait' : 'cursor-pointer'}"
|
9 |
+
title="Prompt and Paint"
|
10 |
+
>
|
11 |
+
{#if isLoading}
|
12 |
+
<span>painting... </span>{:else}
|
13 |
+
<span
|
14 |
+
class="rounded-sm h-4 w-4 flex justify-center items-center border-2 border-dashed border-violet-700 mr-2"
|
15 |
+
>
|
16 |
+
+
|
17 |
+
</span>
|
18 |
+
<span>Prompt + Paint</span>
|
19 |
+
{/if}
|
20 |
+
</button>
|
21 |
+
|
22 |
+
<style lang="postcss" scoped>
|
23 |
+
/* .button {
|
24 |
+
@apply disabled:opacity-50 dark:bg-white dark:text-black bg-black text-white rounded-2xl text-xs shadow-sm focus:outline-none focus:border-gray-400;
|
25 |
+
} */
|
26 |
+
.button-paint {
|
27 |
+
@apply text-xs md:text-sm font-mono bg-blue-100 text-blue-900 min-w-[25ch] flex justify-center items-center disabled:opacity-50 rounded-xl px-3 py-1 shadow-sm focus:outline-none focus:border-gray-400;
|
28 |
+
}
|
29 |
+
</style>
|
frontend/src/lib/Buttons/RoomsSelector.svelte
ADDED
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { page } from '$app/stores';
|
3 |
+
import Room from '$lib/Icons/Room.svelte';
|
4 |
+
import Pin from '$lib/Icons/Pin.svelte';
|
5 |
+
import People from '$lib/Icons/People.svelte';
|
6 |
+
import LoadingIcon from '$lib/Icons/LoadingIcon.svelte';
|
7 |
+
import { onMount } from 'svelte';
|
8 |
+
import { selectedRoomID } from '$lib/store';
|
9 |
+
import { MAX_CAPACITY } from '$lib/constants';
|
10 |
+
import { useRooms } from '$lib/liveblocks';
|
11 |
+
import type { RoomResponse } from '$lib/types';
|
12 |
+
|
13 |
+
export let isLoading = false;
|
14 |
+
let boxEl: HTMLElement;
|
15 |
+
|
16 |
+
const rooms = useRooms();
|
17 |
+
|
18 |
+
let collapsed = true;
|
19 |
+
$: selectedRoom = $rooms.find((room) => room.room_id === $selectedRoomID);
|
20 |
+
$: loadingRooms = $rooms.length > 0;
|
21 |
+
|
22 |
+
function clickHandler(event: Event) {
|
23 |
+
if (boxEl && !boxEl.contains(event.target as Node)) {
|
24 |
+
collapsed = true;
|
25 |
+
}
|
26 |
+
}
|
27 |
+
onMount(() => {
|
28 |
+
window.addEventListener('pointerdown', clickHandler, true);
|
29 |
+
return () => {
|
30 |
+
window.removeEventListener('pointerdown', clickHandler, true);
|
31 |
+
};
|
32 |
+
});
|
33 |
+
|
34 |
+
function changeRoom(room: RoomResponse) {
|
35 |
+
$selectedRoomID = room.room_id;
|
36 |
+
collapsed = true;
|
37 |
+
$page.url.searchParams.set('roomid', room.room_id);
|
38 |
+
window.location.search = `?${$page.url.searchParams.toString()}`;
|
39 |
+
window.parent.postMessage({ queryString: `?${$page.url.searchParams.toString()}` }, '*');
|
40 |
+
}
|
41 |
+
</script>
|
42 |
+
|
43 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
44 |
+
<div class="min-w-[20ch]">
|
45 |
+
{#if loadingRooms && selectedRoom}
|
46 |
+
<div
|
47 |
+
class="text-sm rounded-2xl md:text-smtext-gray-700 py-1 font-medium tracking-tight relative ring-1 ring-blue-500 px-2"
|
48 |
+
title="Choose a different room"
|
49 |
+
bind:this={boxEl}
|
50 |
+
>
|
51 |
+
{#if !collapsed}
|
52 |
+
<div
|
53 |
+
class="absolute left-0 right-0 bottom-full rounded-xl bg-blue-600 px-1 overflow-hidden z-0"
|
54 |
+
>
|
55 |
+
<ul class="relative overflow-hidden overflow-y-scroll max-h-72 w-full x-scroll">
|
56 |
+
<li class="grid-row gap-2 pb-2 sticky top-0 py-2 bg-blue-600 font-semibold">
|
57 |
+
<Room />
|
58 |
+
<span> room </span>
|
59 |
+
<span />
|
60 |
+
<People />
|
61 |
+
<span> players </span>
|
62 |
+
</li>
|
63 |
+
{#each $rooms as room}
|
64 |
+
<li>
|
65 |
+
<!-- svelte-ignore a11y-invalid-attribute -->
|
66 |
+
<a
|
67 |
+
href="#"
|
68 |
+
on:click|preventDefault={() => changeRoom(room)}
|
69 |
+
class="grid-row gap-2 hover:bg-gray-300
|
70 |
+
{room.room_id === $selectedRoomID ? 'text-black' : ''}"
|
71 |
+
>
|
72 |
+
<span>
|
73 |
+
{#if room.room_id === $selectedRoomID}
|
74 |
+
<Pin />
|
75 |
+
{/if}
|
76 |
+
</span>
|
77 |
+
<span>{room.room_id} </span>
|
78 |
+
<span />
|
79 |
+
<span />
|
80 |
+
<span>{room.users_count} / {MAX_CAPACITY}</span>
|
81 |
+
</a>
|
82 |
+
</li>
|
83 |
+
{/each}
|
84 |
+
</ul>
|
85 |
+
<div class="border-t-2 border-t-gray-400 border-opacity-50" />
|
86 |
+
</div>
|
87 |
+
{/if}
|
88 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
89 |
+
<div
|
90 |
+
class={isLoading ? 'cursor-wait' : 'cursor-pointer'}
|
91 |
+
on:click={() => (isLoading ? null : (collapsed = !collapsed))}
|
92 |
+
>
|
93 |
+
{#if selectedRoom}
|
94 |
+
<div class="grid-row gap-2">
|
95 |
+
<Room />
|
96 |
+
<span>
|
97 |
+
{selectedRoom?.room_id}
|
98 |
+
</span>
|
99 |
+
<span />
|
100 |
+
<People />
|
101 |
+
<span>
|
102 |
+
{selectedRoom?.users_count} / {MAX_CAPACITY}
|
103 |
+
</span>
|
104 |
+
</div>
|
105 |
+
{:else}
|
106 |
+
<div class="grid-row gap-2">
|
107 |
+
<Room />
|
108 |
+
<span>
|
109 |
+
Loading...
|
110 |
+
<People />
|
111 |
+
<span> ... / ... </span>
|
112 |
+
</span>
|
113 |
+
</div>
|
114 |
+
{/if}
|
115 |
+
</div>
|
116 |
+
</div>
|
117 |
+
{:else}
|
118 |
+
<div
|
119 |
+
class="bg-gradient-to-r from-transparent via-blue-900/20 to-transparent py-1.5 text-blue-700 rounded-full flex justify-center items-center"
|
120 |
+
>
|
121 |
+
<LoadingIcon classList="animate-spin mr-2 text-sm" /> loading rooms
|
122 |
+
</div>
|
123 |
+
{/if}
|
124 |
+
</div>
|
125 |
+
|
126 |
+
<style lang="postcss" scoped>
|
127 |
+
.grid-row {
|
128 |
+
display: grid;
|
129 |
+
grid-template-columns: 0.5fr 2fr 1fr 0.5fr 2fr;
|
130 |
+
align-items: center;
|
131 |
+
justify-items: flex-start;
|
132 |
+
}
|
133 |
+
.grid-row span {
|
134 |
+
white-space: nowrap;
|
135 |
+
}
|
136 |
+
</style>
|
frontend/src/lib/Buttons/ShareWithCommunity.svelte
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import IconCommunity from '$lib/Icons/IconCommunity.svelte';
|
3 |
+
import LoadingIcon from '$lib/Icons/LoadingIcon.svelte';
|
4 |
+
import { uploadImage } from '$lib/utils';
|
5 |
+
import { canvasEl, selectedRoomID } from '$lib/store';
|
6 |
+
import { nanoid } from 'nanoid';
|
7 |
+
|
8 |
+
export let isLoading = false;
|
9 |
+
|
10 |
+
let isUploading: boolean = false;
|
11 |
+
async function handleClick() {
|
12 |
+
if (isUploading) {
|
13 |
+
return;
|
14 |
+
}
|
15 |
+
const blob: Blob = await new Promise((resolve) => {
|
16 |
+
$canvasEl.toBlob(resolve as BlobCallback, 'image/jpeg', 0.95);
|
17 |
+
});
|
18 |
+
isUploading = true;
|
19 |
+
|
20 |
+
await createCommunityPost(blob);
|
21 |
+
|
22 |
+
isUploading = false;
|
23 |
+
}
|
24 |
+
|
25 |
+
async function createCommunityPost(canvasBlob: Blob) {
|
26 |
+
let canvasURL: {
|
27 |
+
url: string;
|
28 |
+
filename: string;
|
29 |
+
} | null = null;
|
30 |
+
try {
|
31 |
+
canvasURL = await uploadImage(canvasBlob, {
|
32 |
+
prompt: 'canvas',
|
33 |
+
position: { x: 0, y: 0 },
|
34 |
+
date: new Date().getTime(),
|
35 |
+
id: nanoid(),
|
36 |
+
room: $selectedRoomID || 'default'
|
37 |
+
});
|
38 |
+
} catch (err) {
|
39 |
+
console.error(err);
|
40 |
+
}
|
41 |
+
if (!canvasURL) {
|
42 |
+
console.error('Failed to upload image');
|
43 |
+
return;
|
44 |
+
}
|
45 |
+
const canvasImage = `<img src="${canvasURL.url}" style="width:100%" width="1000" height="1000">`;
|
46 |
+
const descriptionMd = `#### Stable Diffusion Multiplayer:
|
47 |
+
### [${$selectedRoomID}](https://huggingface.co/spaces/huggingface-projects/stable-diffusion-multiplayer?roomid=${$selectedRoomID})
|
48 |
+
|
49 |
+
<div style="display: flex; overflow: scroll; column-gap: 0.75rem;">
|
50 |
+
${canvasImage}
|
51 |
+
</div>`;
|
52 |
+
|
53 |
+
const params = new URLSearchParams({
|
54 |
+
title: `Room ${$selectedRoomID}`,
|
55 |
+
description: descriptionMd
|
56 |
+
});
|
57 |
+
const paramsStr = params.toString();
|
58 |
+
window.open(
|
59 |
+
`https://huggingface.co/spaces/huggingface-projects/stable-diffusion-multiplayer/discussions/new?${paramsStr}`,
|
60 |
+
'_blank'
|
61 |
+
);
|
62 |
+
}
|
63 |
+
</script>
|
64 |
+
|
65 |
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
66 |
+
<button
|
67 |
+
class="{isLoading || isUploading
|
68 |
+
? 'cursor-wait'
|
69 |
+
: 'cursor-pointer'} text-xs font-mono flex items-center justify-center bg-black gap-x-1 rounded-xl px-2 py-1 whitespace-nowrap"
|
70 |
+
on:click={handleClick}
|
71 |
+
disabled={isUploading || isLoading}
|
72 |
+
title="Share with community"
|
73 |
+
>
|
74 |
+
{#if isUploading}
|
75 |
+
<LoadingIcon classList={'animate-spin max-w-[25px] text-white'} />
|
76 |
+
{:else}
|
77 |
+
<IconCommunity />
|
78 |
+
{/if}
|
79 |
+
<p class="text-white font-semibold">Share to community</p>
|
80 |
+
</button>
|
frontend/src/lib/Buttons/UndoButton.svelte
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import Undo from '$lib/Icons/Undo.svelte';
|
3 |
+
export let isActive = false;
|
4 |
+
export let isLoading = false;
|
5 |
+
export let className = '';
|
6 |
+
</script>
|
7 |
+
|
8 |
+
<button
|
9 |
+
on:click
|
10 |
+
disabled={isLoading}
|
11 |
+
class="{className} bg-white rounded-full disabled:opacity-50 bg-white rounded-full {isActive
|
12 |
+
? 'text-blue-700'
|
13 |
+
: 'text-gray-800'}"
|
14 |
+
title="Clear Masking"
|
15 |
+
>
|
16 |
+
<Undo />
|
17 |
+
</button>
|
18 |
+
|
19 |
+
<style lang="postcss" scoped>
|
20 |
+
</style>
|
frontend/src/lib/Cursor.svelte
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { ZoomTransform } from 'd3-zoom';
|
3 |
+
import Cursor from '$lib/Icons/Cursor.svelte';
|
4 |
+
export let transform: ZoomTransform;
|
5 |
+
export let color = '';
|
6 |
+
export let position = { x: 0, y: 0 };
|
7 |
+
|
8 |
+
$: coord = {
|
9 |
+
x: transform.applyX(position.x),
|
10 |
+
y: transform.applyY(position.y)
|
11 |
+
};
|
12 |
+
</script>
|
13 |
+
|
14 |
+
<div
|
15 |
+
class="cursor text-4xl"
|
16 |
+
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k});`}
|
17 |
+
>
|
18 |
+
<Cursor classList={'block z-0 col-span-2 row-span-2 text-8xl'} fill={color} />
|
19 |
+
<!--
|
20 |
+
{#if emoji}
|
21 |
+
<div
|
22 |
+
class="absolute right-0 col-start-2 row-start-2 text-8xl"
|
23 |
+
style={`text-shadow: 0px 5px 5px ${color}`}
|
24 |
+
>
|
25 |
+
{emoji}
|
26 |
+
</div>
|
27 |
+
{/if} -->
|
28 |
+
</div>
|
29 |
+
|
30 |
+
<style lang="postcss" scoped>
|
31 |
+
.cursor {
|
32 |
+
@apply absolute top-0 left-0 grid grid-cols-3 touch-none pointer-events-none;
|
33 |
+
transform-origin: 0 0;
|
34 |
+
}
|
35 |
+
</style>
|
frontend/src/lib/Frame.svelte
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import LoadingIcon from '$lib/Icons/LoadingIcon.svelte';
|
3 |
+
import { FRAME_SIZE } from '$lib/constants';
|
4 |
+
import type { ZoomTransform } from 'd3-zoom';
|
5 |
+
import { Status } from '$lib/types';
|
6 |
+
|
7 |
+
export let transform: ZoomTransform;
|
8 |
+
export let position = { x: 0, y: 0 };
|
9 |
+
export let prompt = '';
|
10 |
+
export let status: Status;
|
11 |
+
|
12 |
+
$: coord = {
|
13 |
+
x: transform.applyX(position.x),
|
14 |
+
y: transform.applyY(position.y)
|
15 |
+
};
|
16 |
+
</script>
|
17 |
+
|
18 |
+
<div
|
19 |
+
class="absolute top-0 left-0 border-8 border-dashed border-black flex items-center justify-center bg-black/60"
|
20 |
+
style={`width: ${FRAME_SIZE}px;
|
21 |
+
height: ${FRAME_SIZE}px;
|
22 |
+
transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); transform-origin: 0 0;`}
|
23 |
+
>
|
24 |
+
<div class="pointer-events-none touch-none">
|
25 |
+
<div class="font-bold !text-4xl text-white rounded-2xl text-center p-10">
|
26 |
+
{#if status === Status.loading || status === Status.processing}
|
27 |
+
<LoadingIcon classList={'animate-spin text-4xl inline mr-2 mx-auto text-white mb-4'} />
|
28 |
+
<p class="text-4xl">Someone is painting:</p>
|
29 |
+
{:else if status === Status.masking}
|
30 |
+
<p class="text-4xl">Someone is masking</p>
|
31 |
+
{:else if status === Status.prompting}
|
32 |
+
<p class="text-4xl">Someone is typing:</p>
|
33 |
+
{/if}
|
34 |
+
{#if prompt}
|
35 |
+
<span class="italic font-normal line-clamp-4">"{prompt}"</span>
|
36 |
+
{/if}
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
</div>
|
frontend/src/lib/Icons/Cursor.svelte
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
export let fill = '';
|
4 |
+
</script>
|
5 |
+
|
6 |
+
<svg
|
7 |
+
class={classList}
|
8 |
+
width="1em"
|
9 |
+
height="1em"
|
10 |
+
viewBox="0 0 12 12"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
>
|
13 |
+
<path
|
14 |
+
d="M3.09022 1.77278C2.25499 1.40456 1.40358 2.25597 1.7718 3.0912L4.57875 9.45818C4.95365 10.3086 6.18455 10.2323 6.4516 9.34213L6.9637 7.63515C7.06029 7.31317 7.31219 7.06126 7.63417 6.96467L9.34117 6.45258C10.2313 6.18552 10.3076 4.95463 9.45722 4.57972L3.09022 1.77278Z"
|
15 |
+
{fill}
|
16 |
+
/>
|
17 |
+
<path
|
18 |
+
fill-rule="evenodd"
|
19 |
+
clip-rule="evenodd"
|
20 |
+
d="M3.66305 9.86091L0.856101 3.49392C0.119663 1.82347 1.82248 0.120639 3.49294 0.857077L9.85994 3.66402C11.5607 4.41383 11.4082 6.87562 9.62784 7.40973L7.92085 7.92182L7.40875 9.62881C6.87465 11.4092 4.41286 11.5617 3.66305 9.86091ZM1.77113 3.09053C1.40291 2.2553 2.25432 1.40388 3.08955 1.7721L9.45654 4.57905C10.3069 4.95395 10.2307 6.18485 9.34049 6.4519L7.6335 6.964C7.31152 7.06059 7.05961 7.3125 6.96302 7.63448L6.45093 9.34146C6.18387 10.2316 4.95298 10.3079 4.57807 9.45751L1.77113 3.09053Z"
|
21 |
+
fill="white"
|
22 |
+
fill-opacity="0.8"
|
23 |
+
/>
|
24 |
+
</svg>
|
frontend/src/lib/Icons/IconCommunity.svelte
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classNames = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classNames}
|
7 |
+
xmlns="http://www.w3.org/2000/svg"
|
8 |
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
9 |
+
aria-hidden="true"
|
10 |
+
focusable="false"
|
11 |
+
role="img"
|
12 |
+
width="1em"
|
13 |
+
height="1em"
|
14 |
+
preserveAspectRatio="xMidYMid meet"
|
15 |
+
viewBox="0 0 32 32"
|
16 |
+
>
|
17 |
+
<path
|
18 |
+
d="M20.6081 3C21.7684 3 22.8053 3.49196 23.5284 4.38415C23.9756 4.93678 24.4428 5.82749 24.4808 7.16133C24.9674 7.01707 25.4353 6.93643 25.8725 6.93643C26.9833 6.93643 27.9865 7.37587 28.696 8.17411C29.6075 9.19872 30.0124 10.4579 29.8361 11.7177C29.7523 12.3177 29.5581 12.8555 29.2678 13.3534C29.8798 13.8646 30.3306 14.5763 30.5485 15.4322C30.719 16.1032 30.8939 17.5006 29.9808 18.9403C30.0389 19.0342 30.0934 19.1319 30.1442 19.2318C30.6932 20.3074 30.7283 21.5229 30.2439 22.6548C29.5093 24.3704 27.6841 25.7219 24.1397 27.1727C21.9347 28.0753 19.9174 28.6523 19.8994 28.6575C16.9842 29.4379 14.3477 29.8345 12.0653 29.8345C7.87017 29.8345 4.8668 28.508 3.13831 25.8921C0.356375 21.6797 0.754104 17.8269 4.35369 14.1131C6.34591 12.058 7.67023 9.02782 7.94613 8.36275C8.50224 6.39343 9.97271 4.20438 12.4172 4.20438H12.4179C12.6236 4.20438 12.8314 4.2214 13.0364 4.25468C14.107 4.42854 15.0428 5.06476 15.7115 6.02205C16.4331 5.09583 17.134 4.359 17.7682 3.94323C18.7242 3.31737 19.6794 3 20.6081 3ZM20.6081 5.95917C20.2427 5.95917 19.7963 6.1197 19.3039 6.44225C17.7754 7.44319 14.8258 12.6772 13.7458 14.7131C13.3839 15.3952 12.7655 15.6837 12.2086 15.6837C11.1036 15.6837 10.2408 14.5497 12.1076 13.1085C14.9146 10.9402 13.9299 7.39584 12.5898 7.1776C12.5311 7.16799 12.4731 7.16355 12.4172 7.16355C11.1989 7.16355 10.6615 9.33114 10.6615 9.33114C10.6615 9.33114 9.0863 13.4148 6.38031 16.206C3.67434 18.998 3.5346 21.2388 5.50675 24.2246C6.85185 26.2606 9.42666 26.8753 12.0653 26.8753C14.8021 26.8753 17.6077 26.2139 19.1799 25.793C19.2574 25.7723 28.8193 22.984 27.6081 20.6107C27.4046 20.212 27.0693 20.0522 26.6471 20.0522C24.9416 20.0522 21.8393 22.6726 20.5057 22.6726C20.2076 22.6726 19.9976 22.5416 19.9116 22.222C19.3433 20.1173 28.552 19.2325 27.7758 16.1839C27.639 15.6445 27.2677 15.4256 26.746 15.4263C24.4923 15.4263 19.4358 19.5181 18.3759 19.5181C18.2949 19.5181 18.2368 19.4937 18.2053 19.4419C17.6743 18.557 17.9653 17.9394 21.7082 15.6009C25.4511 13.2617 28.0783 11.8545 26.5841 10.1752C26.4121 9.98141 26.1684 9.8956 25.8725 9.8956C23.6001 9.89634 18.2311 14.9403 18.2311 14.9403C18.2311 14.9403 16.7821 16.496 15.9057 16.496C15.7043 16.496 15.533 16.4139 15.4169 16.2112C14.7956 15.1296 21.1879 10.1286 21.5484 8.06535C21.7928 6.66715 21.3771 5.95917 20.6081 5.95917Z"
|
19 |
+
fill="#FF9D00"
|
20 |
+
/>
|
21 |
+
<path
|
22 |
+
d="M5.50686 24.2246C3.53472 21.2387 3.67446 18.9979 6.38043 16.206C9.08641 13.4147 10.6615 9.33111 10.6615 9.33111C10.6615 9.33111 11.2499 6.95933 12.59 7.17757C13.93 7.39581 14.9139 10.9401 12.1069 13.1084C9.29997 15.276 12.6659 16.7489 13.7459 14.713C14.8258 12.6772 17.7747 7.44316 19.304 6.44221C20.8326 5.44128 21.9089 6.00204 21.5484 8.06532C21.188 10.1286 14.795 15.1295 15.4171 16.2118C16.0391 17.2934 18.2312 14.9402 18.2312 14.9402C18.2312 14.9402 25.0907 8.49588 26.5842 10.1752C28.0776 11.8545 25.4512 13.2616 21.7082 15.6008C17.9646 17.9393 17.6744 18.557 18.2054 19.4418C18.7372 20.3266 26.9998 13.1351 27.7759 16.1838C28.5513 19.2324 19.3434 20.1173 19.9117 22.2219C20.48 24.3274 26.3979 18.2382 27.6082 20.6107C28.8193 22.9839 19.2574 25.7722 19.18 25.7929C16.0914 26.62 8.24723 28.3726 5.50686 24.2246Z"
|
23 |
+
fill="#FFD21E"
|
24 |
+
/>
|
25 |
+
</svg>
|
frontend/src/lib/Icons/LiveBlocks.svelte
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
width="123"
|
8 |
+
height="44"
|
9 |
+
viewBox="0 0 123 44"
|
10 |
+
fill="none"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
>
|
13 |
+
<rect
|
14 |
+
opacity="0.5"
|
15 |
+
x="0.25"
|
16 |
+
y="0.25"
|
17 |
+
width="122.5"
|
18 |
+
height="43.5"
|
19 |
+
rx="6.75"
|
20 |
+
fill="#1F2937"
|
21 |
+
stroke="#3A4C64"
|
22 |
+
stroke-width="0.5"
|
23 |
+
/>
|
24 |
+
<path
|
25 |
+
d="M44.9102 16.8569H45.8514V14.778H47.1691C48.364 14.778 49.1497 14.0577 49.1497 12.9528C49.1497 11.8397 48.364 11.1277 47.1691 11.1277H44.9102V16.8569ZM47.0954 11.9461C47.7666 11.9461 48.1921 12.3308 48.1921 12.9528C48.1921 13.5667 47.7584 13.9595 47.0872 13.9595H45.8514V11.9461H47.0954Z"
|
26 |
+
fill="#A3BAD9"
|
27 |
+
/>
|
28 |
+
<path
|
29 |
+
d="M51.5649 16.9059C52.8417 16.9059 53.701 16.0465 53.701 14.7615C53.701 13.4847 52.8417 12.6172 51.5649 12.6172C50.2881 12.6172 49.4287 13.4847 49.4287 14.7615C49.4287 16.0465 50.2881 16.9059 51.5649 16.9059ZM51.5649 16.1529C50.8201 16.1529 50.3372 15.5718 50.3372 14.7615C50.3372 13.9513 50.8201 13.3702 51.5649 13.3702C52.3097 13.3702 52.8007 13.9513 52.8007 14.7615C52.8007 15.5718 52.3097 16.1529 51.5649 16.1529Z"
|
30 |
+
fill="#A3BAD9"
|
31 |
+
/>
|
32 |
+
<path
|
33 |
+
d="M55.233 16.857H56.1906L57.1073 14.0088L57.9912 16.857H58.9406L60.332 12.6665H59.4317L58.4414 15.8748L57.5247 12.6665H56.7144L55.7568 15.8748L54.7992 12.6665H53.8662L55.233 16.857Z"
|
34 |
+
fill="#A3BAD9"
|
35 |
+
/>
|
36 |
+
<path
|
37 |
+
d="M62.6424 16.9059C63.6818 16.9059 64.3857 16.2757 64.533 15.449H63.6491C63.5263 15.9156 63.1335 16.1856 62.5933 16.1856C61.8976 16.1856 61.4475 15.7191 61.4229 15.0071V14.9498H64.5821C64.5985 14.8352 64.6067 14.7206 64.6067 14.6142C64.5821 13.3865 63.7719 12.6172 62.5605 12.6172C61.3247 12.6172 60.498 13.4766 60.498 14.7697C60.498 16.0547 61.3247 16.9059 62.6424 16.9059ZM61.4556 14.3032C61.5211 13.7057 62.004 13.3292 62.5687 13.3292C63.1826 13.3292 63.6164 13.6812 63.69 14.3032H61.4556Z"
|
38 |
+
fill="#A3BAD9"
|
39 |
+
/>
|
40 |
+
<path
|
41 |
+
d="M67.4411 12.6665C66.7536 12.6665 66.418 12.9448 66.2052 13.2967L66.107 12.6665H65.3213V16.857H66.2052V14.7536C66.2052 14.107 66.4589 13.4931 67.2447 13.4931H67.6375V12.6665H67.4411Z"
|
42 |
+
fill="#A3BAD9"
|
43 |
+
/>
|
44 |
+
<path
|
45 |
+
d="M69.9725 16.9059C71.0119 16.9059 71.7158 16.2757 71.8631 15.449H70.9792C70.8564 15.9156 70.4635 16.1856 69.9234 16.1856C69.2277 16.1856 68.7775 15.7191 68.753 15.0071V14.9498H71.9122C71.9286 14.8352 71.9368 14.7206 71.9368 14.6142C71.9122 13.3865 71.1019 12.6172 69.8906 12.6172C68.6548 12.6172 67.8281 13.4766 67.8281 14.7697C67.8281 16.0547 68.6548 16.9059 69.9725 16.9059ZM68.7857 14.3032C68.8512 13.7057 69.3341 13.3292 69.8988 13.3292C70.5127 13.3292 70.9464 13.6812 71.0201 14.3032H68.7857Z"
|
46 |
+
fill="#A3BAD9"
|
47 |
+
/>
|
48 |
+
<path
|
49 |
+
d="M75.8352 13.2475C75.5651 12.8792 75.1149 12.6173 74.4765 12.6173C73.3391 12.6173 72.4961 13.4685 72.4961 14.7616C72.4961 16.1039 73.3391 16.906 74.4765 16.906C75.1395 16.906 75.5733 16.5949 75.8433 16.2266L75.9497 16.8569H76.7191V11.1277H75.8352V13.2475ZM74.6238 16.1612C73.8875 16.1612 73.4046 15.5883 73.4046 14.7616C73.4046 13.9432 73.8875 13.3702 74.6238 13.3702C75.3605 13.3702 75.8433 13.9432 75.8433 14.778C75.8433 15.5883 75.3605 16.1612 74.6238 16.1612Z"
|
50 |
+
fill="#A3BAD9"
|
51 |
+
/>
|
52 |
+
<path
|
53 |
+
d="M81.9193 12.6173C81.2727 12.6173 80.839 12.9119 80.5607 13.2884V11.1277H79.6768V16.8569H80.4461L80.5525 16.2512C80.8144 16.6277 81.2646 16.906 81.9193 16.906C83.0652 16.906 83.9 16.0466 83.9 14.7616C83.9 13.4112 83.0652 12.6173 81.9193 12.6173ZM81.772 16.1612C81.0354 16.1612 80.5525 15.5801 80.5525 14.7534C80.5525 13.9432 81.0354 13.3702 81.772 13.3702C82.5086 13.3702 82.9997 13.9432 82.9997 14.7616C82.9997 15.5883 82.5086 16.1612 81.772 16.1612Z"
|
54 |
+
fill="#A3BAD9"
|
55 |
+
/>
|
56 |
+
<path
|
57 |
+
d="M85.6277 16.8815L85.4641 17.2908C85.3331 17.6345 85.2513 17.7164 84.9075 17.7164H84.2855V18.4939H85.2349C85.8487 18.4939 86.0288 18.1911 86.2907 17.5117L88.1895 12.6665H87.2483L86.1106 15.8503L84.9402 12.6665H83.999L85.6277 16.8815Z"
|
58 |
+
fill="#A3BAD9"
|
59 |
+
/>
|
60 |
+
<path d="M45.1162 32.4821H47.2144V22.0654H45.1162V32.4821Z" fill="#A3BAD9" />
|
61 |
+
<path
|
62 |
+
d="M49.7329 24.0148C50.4175 24.0148 50.9532 23.5088 50.9532 22.8392C50.9532 22.1546 50.4175 21.6487 49.7329 21.6487C49.0484 21.6487 48.5127 22.1546 48.5127 22.8392C48.5127 23.5088 49.0484 24.0148 49.7329 24.0148ZM48.6764 32.482H50.7895V24.8332H48.6764V32.482Z"
|
63 |
+
fill="#A3BAD9"
|
64 |
+
/>
|
65 |
+
<path
|
66 |
+
d="M54.2461 32.4823H56.8503L59.6033 24.8335H57.4009L55.5854 30.3394L53.7402 24.8335H51.4932L54.2461 32.4823Z"
|
67 |
+
fill="#A3BAD9"
|
68 |
+
/>
|
69 |
+
<path
|
70 |
+
d="M63.718 32.5713C65.7567 32.5713 67.0811 31.3808 67.3192 29.8927H65.2507C65.0722 30.607 64.4769 30.979 63.6287 30.979C62.5722 30.979 61.8728 30.3243 61.8281 29.2379V29.1338H67.3787C67.4234 28.8957 67.4382 28.6427 67.4382 28.4195C67.4085 26.1725 65.8609 24.7439 63.5692 24.7439C61.2031 24.7439 59.6406 26.2915 59.6406 28.6725C59.6406 31.0385 61.1734 32.5713 63.718 32.5713ZM61.8728 27.7647C61.9918 26.857 62.6763 26.3064 63.5841 26.3064C64.5365 26.3064 65.1763 26.8272 65.3251 27.7647H61.8728Z"
|
71 |
+
fill="#A3BAD9"
|
72 |
+
/>
|
73 |
+
<path
|
74 |
+
d="M72.9898 24.744C71.9332 24.744 71.1891 25.1904 70.6832 25.8005V22.0654H68.585V32.4821H70.4153L70.6237 31.4404C71.1148 32.0952 71.8737 32.5714 72.9749 32.5714C75.0434 32.5714 76.5315 31.0089 76.5315 28.6428C76.5315 26.2172 75.0434 24.744 72.9898 24.744ZM72.5136 30.8601C71.3678 30.8601 70.6534 29.9523 70.6534 28.6428C70.6534 27.3482 71.3678 26.4553 72.5136 26.4553C73.6594 26.4553 74.4035 27.3482 74.4035 28.6577C74.4035 29.9672 73.6594 30.8601 72.5136 30.8601Z"
|
75 |
+
fill="#A3BAD9"
|
76 |
+
/>
|
77 |
+
<path d="M77.6973 32.4821H79.7955V22.0654H77.6973V32.4821Z" fill="#A3BAD9" />
|
78 |
+
<path
|
79 |
+
d="M84.9927 32.5713C87.4332 32.5713 89.0254 31.0237 89.0254 28.6576C89.0254 26.3064 87.4332 24.7439 84.9927 24.7439C82.5522 24.7439 80.96 26.3064 80.96 28.6576C80.96 31.0088 82.5522 32.5713 84.9927 32.5713ZM84.9927 30.86C83.8171 30.86 83.1028 29.9522 83.1028 28.6576C83.1028 27.3629 83.8171 26.4552 84.9927 26.4552C86.1683 26.4552 86.8975 27.3629 86.8975 28.6576C86.8975 29.9522 86.1683 30.86 84.9927 30.86Z"
|
80 |
+
fill="#A3BAD9"
|
81 |
+
/>
|
82 |
+
<path
|
83 |
+
d="M93.7816 32.5713C95.865 32.5713 97.2489 31.4254 97.5465 29.5951H95.3441C95.1953 30.3689 94.6447 30.86 93.8263 30.86C92.7251 30.86 92.0257 29.9671 92.0257 28.6576C92.0257 27.3629 92.7251 26.4552 93.8263 26.4552C94.6447 26.4552 95.1655 26.9314 95.3441 27.6903H97.5316C97.234 25.8748 95.9394 24.7439 93.856 24.7439C91.4304 24.7439 89.8828 26.2915 89.8828 28.6576C89.8828 31.0683 91.356 32.5713 93.7816 32.5713Z"
|
84 |
+
fill="#A3BAD9"
|
85 |
+
/>
|
86 |
+
<path
|
87 |
+
d="M98.6514 32.4821H100.75V30.622L101.895 29.3869L103.83 32.4821H106.256L103.354 27.8392L106.107 24.8333H103.741L100.75 28.0773V22.0654H98.6514V32.4821Z"
|
88 |
+
fill="#A3BAD9"
|
89 |
+
/>
|
90 |
+
<path
|
91 |
+
d="M106.188 29.9375C106.277 31.5744 107.72 32.5714 109.848 32.5714C111.902 32.5714 113.33 31.6042 113.33 30.0863C113.33 28.3601 111.872 27.9583 110.116 27.7798C109.015 27.6458 108.36 27.5863 108.36 27.006C108.36 26.5149 108.896 26.2173 109.714 26.2173C110.562 26.2173 111.143 26.5893 111.202 27.1994H113.211C113.107 25.6369 111.693 24.7292 109.64 24.7292C107.676 24.7143 106.351 25.7113 106.351 27.2292C106.351 28.8065 107.735 29.2083 109.521 29.4167C110.756 29.5804 111.277 29.625 111.277 30.25C111.277 30.7857 110.741 31.0685 109.863 31.0685C108.836 31.0685 108.256 30.6071 108.182 29.9375H106.188Z"
|
92 |
+
fill="#A3BAD9"
|
93 |
+
/>
|
94 |
+
<path
|
95 |
+
d="M32.4824 8.67236H11.649C10.0053 8.67236 8.67285 10.0048 8.67285 11.6486V32.4819C8.67285 34.1256 10.0053 35.4581 11.649 35.4581H32.4824C34.1261 35.4581 35.4586 34.1256 35.4586 32.4819V11.6486C35.4586 10.0048 34.1261 8.67236 32.4824 8.67236Z"
|
96 |
+
fill="url(#paint0_radial_253_107)"
|
97 |
+
/>
|
98 |
+
<path
|
99 |
+
fill-rule="evenodd"
|
100 |
+
clip-rule="evenodd"
|
101 |
+
d="M24.8953 17.78H15.0332L17.8712 20.7599V24.875L24.8953 17.78ZM19.2193 26.294H29.0814L26.2434 23.3141V19.199L19.2193 26.294Z"
|
102 |
+
fill="white"
|
103 |
+
/>
|
104 |
+
<defs>
|
105 |
+
<radialGradient
|
106 |
+
id="paint0_radial_253_107"
|
107 |
+
cx="0"
|
108 |
+
cy="0"
|
109 |
+
r="1"
|
110 |
+
gradientUnits="userSpaceOnUse"
|
111 |
+
gradientTransform="translate(4.7524 1.97593) rotate(45.3703) scale(40.7748 35.4976)"
|
112 |
+
>
|
113 |
+
<stop stop-color="#FF0099" />
|
114 |
+
<stop offset="1" stop-color="#FF7A00" />
|
115 |
+
</radialGradient>
|
116 |
+
</defs>
|
117 |
+
</svg>
|
frontend/src/lib/Icons/LoadingIcon.svelte
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
xmlns="http://www.w3.org/2000/svg"
|
8 |
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
9 |
+
aria-hidden="true"
|
10 |
+
fill="none"
|
11 |
+
focusable="false"
|
12 |
+
role="img"
|
13 |
+
width="1em"
|
14 |
+
height="1em"
|
15 |
+
preserveAspectRatio="xMidYMid meet"
|
16 |
+
viewBox="0 0 24 24"
|
17 |
+
>
|
18 |
+
<circle class="opacity-50" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
19 |
+
<path
|
20 |
+
fill="currentColor"
|
21 |
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
22 |
+
/>
|
23 |
+
</svg>
|
frontend/src/lib/Icons/Mask.svelte
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
width="1em"
|
8 |
+
height="1em"
|
9 |
+
viewBox="-2 -2 14 14"
|
10 |
+
fill="none"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
>
|
13 |
+
<path
|
14 |
+
d="M3.07066 9.3043L8.07078 4.30418L5.69582 1.92922L0.695701 6.92934C0.626865 6.99826 0.577967 7.08453 0.554193 7.17899L0 10L2.82047 9.44581C2.91516 9.42213 3.00179 9.37317 3.07066 9.3043V9.3043ZM9.68493 2.69003C9.88667 2.48823 10 2.21457 10 1.92922C10 1.64388 9.88667 1.37022 9.68493 1.16842L8.83158 0.315069C8.62978 0.11333 8.35612 0 8.07078 0C7.78543 0 7.51177 0.11333 7.30997 0.315069L6.45662 1.16842L8.83158 3.54338L9.68493 2.69003Z"
|
15 |
+
fill="currentColor"
|
16 |
+
/>
|
17 |
+
</svg>
|
frontend/src/lib/Icons/Move.svelte
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
width="1em"
|
8 |
+
height="1em"
|
9 |
+
viewBox="0 0 11 11"
|
10 |
+
fill="none"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
>
|
13 |
+
<path
|
14 |
+
d="M2.4 4.26667L1 5.66667L2.4 7.06667M4.26667 2.4L5.66667 1L7.06667 2.4M7.06667 8.93333L5.66667 10.3333L4.26667 8.93333M8.93333 4.26667L10.3333 5.66667L8.93333 7.06667M1 5.66667H10.3333M5.66667 1V10.3333"
|
15 |
+
stroke="currentColor"
|
16 |
+
stroke-width="0.933333"
|
17 |
+
stroke-linecap="round"
|
18 |
+
stroke-linejoin="round"
|
19 |
+
/>
|
20 |
+
</svg>
|
frontend/src/lib/Icons/People.svelte
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
height="1em"
|
8 |
+
width="1em"
|
9 |
+
viewBox="0 0 9 7"
|
10 |
+
fill="none"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
>
|
13 |
+
<path
|
14 |
+
fill-rule="evenodd"
|
15 |
+
clip-rule="evenodd"
|
16 |
+
d="M6.41113 3.73486C6.97158 4.11531 7.3643 4.63076 7.3643 5.31801V6.54526H8.59155C8.81654 6.54526 9.00063 6.36118 9.00063 6.13618V5.31801C9.00063 4.42621 7.5402 3.8985 6.41113 3.73486Z"
|
17 |
+
fill="currentColor"
|
18 |
+
/>
|
19 |
+
<path
|
20 |
+
d="M3.27305 3.27266C4.17677 3.27266 4.90938 2.54005 4.90938 1.63633C4.90938 0.732611 4.17677 0 3.27305 0C2.36933 0 1.63672 0.732611 1.63672 1.63633C1.63672 2.54005 2.36933 3.27266 3.27305 3.27266Z"
|
21 |
+
fill="currentColor"
|
22 |
+
/>
|
23 |
+
<path
|
24 |
+
fill-rule="evenodd"
|
25 |
+
clip-rule="evenodd"
|
26 |
+
d="M5.72716 3.27266C6.63124 3.27266 7.36349 2.54041 7.36349 1.63633C7.36349 0.732258 6.63124 0 5.72716 0C5.53489 0 5.3549 0.0409082 5.18308 0.0981798C5.53511 0.533533 5.72715 1.07646 5.72715 1.63633C5.72715 2.1962 5.53511 2.73913 5.18308 3.17448C5.3549 3.23176 5.53489 3.27266 5.72716 3.27266ZM3.27266 3.68175C2.18041 3.68175 0 4.22992 0 5.31808V6.13624C0 6.36124 0.184087 6.54533 0.409083 6.54533H6.13625C6.36124 6.54533 6.54533 6.36124 6.54533 6.13624V5.31808C6.54533 4.22992 4.36492 3.68175 3.27266 3.68175Z"
|
27 |
+
fill="currentColor"
|
28 |
+
/>
|
29 |
+
</svg>
|
frontend/src/lib/Icons/Pin.svelte
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
height="1em"
|
8 |
+
viewBox="0 0 7 9"
|
9 |
+
fill="none"
|
10 |
+
xmlns="http://www.w3.org/2000/svg"
|
11 |
+
>
|
12 |
+
<path
|
13 |
+
d="M3.5 4.58599C3.74063 4.58599 3.94669 4.4961 4.11819 4.31633C4.2894 4.13687 4.375 3.92102 4.375 3.66879C4.375 3.41656 4.2894 3.20056 4.11819 3.02079C3.94669 2.84132 3.74063 2.75159 3.5 2.75159C3.25937 2.75159 3.05346 2.84132 2.88225 3.02079C2.71075 3.20056 2.625 3.41656 2.625 3.66879C2.625 3.92102 2.71075 4.13687 2.88225 4.31633C3.05346 4.4961 3.25937 4.58599 3.5 4.58599ZM3.5 7.95669C4.38958 7.10064 5.04948 6.32285 5.47969 5.62334C5.9099 4.92413 6.125 4.30318 6.125 3.76051C6.125 2.92739 5.87154 2.24515 5.36462 1.71378C4.858 1.18273 4.23646 0.917197 3.5 0.917197C2.76354 0.917197 2.14185 1.18273 1.63494 1.71378C1.12831 2.24515 0.875 2.92739 0.875 3.76051C0.875 4.30318 1.0901 4.92413 1.52031 5.62334C1.95052 6.32285 2.61042 7.10064 3.5 7.95669ZM3.5 9C3.44167 9 3.38333 8.98853 3.325 8.9656C3.26667 8.94267 3.21562 8.9121 3.17188 8.87389C2.10729 7.8879 1.3125 6.97269 0.7875 6.12825C0.2625 5.28352 0 4.49427 0 3.76051C0 2.61401 0.351896 1.70064 1.05569 1.02038C1.75919 0.340127 2.57396 0 3.5 0C4.42604 0 5.24081 0.340127 5.94431 1.02038C6.6481 1.70064 7 2.61401 7 3.76051C7 4.49427 6.7375 5.28352 6.2125 6.12825C5.6875 6.97269 4.89271 7.8879 3.82812 8.87389C3.78438 8.9121 3.73333 8.94267 3.675 8.9656C3.61667 8.98853 3.55833 9 3.5 9Z"
|
14 |
+
fill="white"
|
15 |
+
/>
|
16 |
+
</svg>
|
frontend/src/lib/Icons/Room.svelte
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg class={classList} height="1em" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
6 |
+
<rect x="1" y="1" width="7" height="7" rx="1" stroke="#2F6DCB" />
|
7 |
+
<rect
|
8 |
+
x="3.05566"
|
9 |
+
y="3.05566"
|
10 |
+
width="2.88889"
|
11 |
+
height="2.88889"
|
12 |
+
rx="0.5"
|
13 |
+
fill="#2F6DCB"
|
14 |
+
stroke="#2F6DCB"
|
15 |
+
/>
|
16 |
+
</svg>
|
frontend/src/lib/Icons/Undo.svelte
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
width="1em"
|
8 |
+
viewBox="0 0 10 9"
|
9 |
+
fill="none"
|
10 |
+
xmlns="http://www.w3.org/2000/svg"
|
11 |
+
>
|
12 |
+
<g opacity="0.5">
|
13 |
+
<path
|
14 |
+
d="M6.33333 2.66667H2.27167L3.46733 1.47133L3 1L1 3L3 5L3.46733 4.52833L2.27267 3.33333H6.33333C6.86377 3.33333 7.37247 3.54405 7.74755 3.91912C8.12262 4.29419 8.33333 4.8029 8.33333 5.33333C8.33333 5.86377 8.12262 6.37247 7.74755 6.74755C7.37247 7.12262 6.86377 7.33333 6.33333 7.33333H3.66667V8H6.33333C7.04058 8 7.71885 7.71905 8.21895 7.21895C8.71905 6.71885 9 6.04058 9 5.33333C9 4.62609 8.71905 3.94781 8.21895 3.44772C7.71885 2.94762 7.04058 2.66667 6.33333 2.66667Z"
|
15 |
+
fill="black"
|
16 |
+
stroke="black"
|
17 |
+
stroke-width="0.5"
|
18 |
+
stroke-linejoin="round"
|
19 |
+
/>
|
20 |
+
</g>
|
21 |
+
</svg>
|
frontend/src/lib/Menu.svelte
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import RoomsSelector from '$lib/Buttons/RoomsSelector.svelte';
|
3 |
+
import AboutButton from '$lib/Buttons/AboutButton.svelte';
|
4 |
+
import { toggleAbout } from '$lib/store';
|
5 |
+
import { createEventDispatcher } from 'svelte';
|
6 |
+
|
7 |
+
const dispatch = createEventDispatcher();
|
8 |
+
// const broadcast = useBroadcastEvent();
|
9 |
+
|
10 |
+
export let isLoading = false;
|
11 |
+
</script>
|
12 |
+
|
13 |
+
<svelte:window
|
14 |
+
on:keypress={(e) => {
|
15 |
+
if (e.key === 'Enter') {
|
16 |
+
dispatch('showModal', { showModal: true });
|
17 |
+
}
|
18 |
+
}}
|
19 |
+
/>
|
20 |
+
<div class="flex flex-col md:flex-row items-center justify-between px-4 md:px-12 gap-3 md:gap-0">
|
21 |
+
<AboutButton
|
22 |
+
{isLoading}
|
23 |
+
on:click={() => {
|
24 |
+
$toggleAbout = !$toggleAbout;
|
25 |
+
}}
|
26 |
+
/>
|
27 |
+
|
28 |
+
<button
|
29 |
+
on:click={() => dispatch('showModal', { showModal: true })}
|
30 |
+
title="Click to prompt, and paint. The generated image will show up in the frame."
|
31 |
+
disabled={isLoading}
|
32 |
+
class="{isLoading
|
33 |
+
? 'cursor-wait'
|
34 |
+
: 'cursor-pointer'} order-first md:order-none text-xl md:text-3xl bg-blue-600 text-white px-4 py-1 md:px-6 md:py-2 rounded-2xl ring ring-blue-500 font-semibold shadow-2xl shadow-blue-500 self-center flex items-center hover:saturate-150"
|
35 |
+
><span class="mr-3">🖍</span>Paint
|
36 |
+
<span
|
37 |
+
class="bg-blue-800 text-gray-300 rounded-lg px-2 py-0.5 text-base ml-4 hidden sm:flex items-center translate-y-[2px]"
|
38 |
+
><svg
|
39 |
+
class="text-sm mr-1.5"
|
40 |
+
width="1em"
|
41 |
+
height="1em"
|
42 |
+
viewBox="0 0 10 13"
|
43 |
+
fill="currentColor"
|
44 |
+
xmlns="http://www.w3.org/2000/svg"
|
45 |
+
>
|
46 |
+
<path
|
47 |
+
fill-rule="evenodd"
|
48 |
+
clip-rule="evenodd"
|
49 |
+
d="M8.5 8.5V0H9.5V9.5H1.70711L4.03553 11.8284C4.2308 12.0237 4.2308 12.3403 4.03553 12.5355C3.84027 12.7308 3.52369 12.7308 3.32843 12.5355L0.146447 9.35355C-0.0488155 9.15829 -0.0488155 8.84171 0.146447 8.64645L3.32843 5.46447C3.52369 5.2692 3.84027 5.2692 4.03553 5.46447C4.2308 5.65973 4.2308 5.97631 4.03553 6.17157L1.70711 8.5H8.5Z"
|
50 |
+
fill="currentColor"
|
51 |
+
/>
|
52 |
+
</svg>
|
53 |
+
Enter</span
|
54 |
+
></button
|
55 |
+
>
|
56 |
+
<RoomsSelector {isLoading} />
|
57 |
+
</div>
|
frontend/src/lib/PaintCanvas.svelte
ADDED
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { zoom, zoomIdentity } from 'd3-zoom';
|
3 |
+
import { min } from 'd3-array';
|
4 |
+
import { select } from 'd3-selection';
|
5 |
+
import { onMount } from 'svelte';
|
6 |
+
import { PUBLIC_UPLOADS } from '$env/static/public';
|
7 |
+
import {
|
8 |
+
currZoomTransform,
|
9 |
+
canvasEl,
|
10 |
+
isRenderingCanvas,
|
11 |
+
canvasSize,
|
12 |
+
selectedRoomID
|
13 |
+
} from '$lib/store';
|
14 |
+
|
15 |
+
import { useMyPresence, useObject } from '$lib/liveblocks';
|
16 |
+
import { LiveObject } from '@liveblocks/client';
|
17 |
+
|
18 |
+
import type { PromptImgObject } from '$lib/types';
|
19 |
+
import { FRAME_SIZE, GRID_SIZE } from '$lib/constants';
|
20 |
+
|
21 |
+
const myPresence = useMyPresence();
|
22 |
+
const promptImgStorage = useObject('promptImgStorage');
|
23 |
+
|
24 |
+
const height = $canvasSize.height;
|
25 |
+
const width = $canvasSize.width;
|
26 |
+
|
27 |
+
let containerEl: HTMLDivElement;
|
28 |
+
let canvasCtx: CanvasRenderingContext2D;
|
29 |
+
|
30 |
+
const imagesOnCanvas = new Set();
|
31 |
+
|
32 |
+
function getpromptImgList(
|
33 |
+
promptImgList: Record<string, LiveObject<PromptImgObject> | PromptImgObject>
|
34 |
+
): PromptImgObject[] {
|
35 |
+
if (promptImgList) {
|
36 |
+
//sorted by last updated
|
37 |
+
const roomid = $selectedRoomID || '';
|
38 |
+
const canvasPixels = new Map();
|
39 |
+
for (const x of Array.from(Array(width / GRID_SIZE).keys())) {
|
40 |
+
for (const y of Array.from(Array(height / GRID_SIZE).keys())) {
|
41 |
+
canvasPixels.set(`${x * GRID_SIZE}_${y * GRID_SIZE}`, null);
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
// Object.values(promptImgList).map((e) => {
|
46 |
+
// let obj;
|
47 |
+
// if (e instanceof LiveObject) {
|
48 |
+
// obj = e.toObject();
|
49 |
+
// } else {
|
50 |
+
// obj = e;
|
51 |
+
// }
|
52 |
+
// // const obj = promptImg.toObject();
|
53 |
+
// if (obj.position) {
|
54 |
+
// const key = `${obj.position.x}_${obj.position.y}`;
|
55 |
+
// console.log('key', key);
|
56 |
+
// const promptImgParams = {
|
57 |
+
// imgURL: obj.imgURL
|
58 |
+
// };
|
59 |
+
// $promptImgStorage.set(key, promptImgParams);
|
60 |
+
// }
|
61 |
+
// });
|
62 |
+
|
63 |
+
const list: PromptImgObject[] = Object.values(promptImgList)
|
64 |
+
.map((e) => {
|
65 |
+
if (e instanceof LiveObject) {
|
66 |
+
return e.toObject();
|
67 |
+
} else {
|
68 |
+
return e;
|
69 |
+
}
|
70 |
+
})
|
71 |
+
.map((e) => {
|
72 |
+
const split_str = e.imgURL.split(/-|.jpg|.webp/);
|
73 |
+
const date = parseInt(split_str[0]);
|
74 |
+
const id = split_str[1];
|
75 |
+
const [x, y] = split_str[2].split('_');
|
76 |
+
const prompt = split_str.slice(3).join(' ');
|
77 |
+
return {
|
78 |
+
id,
|
79 |
+
date,
|
80 |
+
position: {
|
81 |
+
x: parseInt(x),
|
82 |
+
y: parseInt(y)
|
83 |
+
},
|
84 |
+
imgURL: e.imgURL,
|
85 |
+
prompt,
|
86 |
+
room: roomid
|
87 |
+
};
|
88 |
+
})
|
89 |
+
.sort((a, b) => b.date - a.date);
|
90 |
+
|
91 |
+
// init
|
92 |
+
for (const promptImg of list) {
|
93 |
+
const x = promptImg.position.x;
|
94 |
+
const y = promptImg.position.y;
|
95 |
+
for (const i of [...Array(FRAME_SIZE / GRID_SIZE).keys()]) {
|
96 |
+
for (const j of [...Array(FRAME_SIZE / GRID_SIZE).keys()]) {
|
97 |
+
const key = `${x + i * GRID_SIZE}_${y + j * GRID_SIZE}`;
|
98 |
+
if (!canvasPixels.get(key)) {
|
99 |
+
canvasPixels.set(key, promptImg.id);
|
100 |
+
}
|
101 |
+
}
|
102 |
+
}
|
103 |
+
}
|
104 |
+
const ids = new Set([...canvasPixels.values()]);
|
105 |
+
const filteredImages = list.filter((promptImg) => ids.has(promptImg.id));
|
106 |
+
|
107 |
+
// remove images that are under other images
|
108 |
+
list
|
109 |
+
.filter((promptImg) => !ids.has(promptImg.id))
|
110 |
+
.map((promptImg) => {
|
111 |
+
const key = `${promptImg.position.x}_${promptImg.position.y}`;
|
112 |
+
$promptImgStorage.delete(key);
|
113 |
+
console.log('deleted', key)
|
114 |
+
});
|
115 |
+
return filteredImages.reverse().filter((promptImg) => !imagesOnCanvas.has(promptImg.id));
|
116 |
+
}
|
117 |
+
return [];
|
118 |
+
}
|
119 |
+
let promptImgList: PromptImgObject[] = [];
|
120 |
+
$: promptImgList = getpromptImgList($promptImgStorage?.toObject());
|
121 |
+
|
122 |
+
$: if (promptImgList) {
|
123 |
+
renderImages(promptImgList);
|
124 |
+
}
|
125 |
+
|
126 |
+
function to_bbox(
|
127 |
+
W: number,
|
128 |
+
H: number,
|
129 |
+
center: { x: number; y: number },
|
130 |
+
w: number,
|
131 |
+
h: number,
|
132 |
+
margin: number
|
133 |
+
) {
|
134 |
+
//https://bl.ocks.org/fabiovalse/b9224bfd64ca96c47f8cdcb57b35b8e2
|
135 |
+
const kw = (W - margin) / w;
|
136 |
+
const kh = (H - margin) / h;
|
137 |
+
const k = min([kw, kh]) || 1;
|
138 |
+
const x = W / 2 - center.x * k;
|
139 |
+
const y = H / 2 - center.y * k;
|
140 |
+
return zoomIdentity.translate(x, y).scale(k);
|
141 |
+
}
|
142 |
+
onMount(() => {
|
143 |
+
const padding = 50;
|
144 |
+
const scale =
|
145 |
+
(width + padding * 2) /
|
146 |
+
(containerEl.clientHeight > containerEl.clientWidth
|
147 |
+
? containerEl.clientWidth
|
148 |
+
: containerEl.clientHeight);
|
149 |
+
const zoomHandler = zoom()
|
150 |
+
.scaleExtent([1 / scale / 2, 3])
|
151 |
+
// .translateExtent([
|
152 |
+
// [-padding, -padding],
|
153 |
+
// [width + padding, height + padding]
|
154 |
+
// ])
|
155 |
+
.tapDistance(10)
|
156 |
+
.on('zoom', zoomed);
|
157 |
+
|
158 |
+
const selection = select($canvasEl.parentElement)
|
159 |
+
.call(zoomHandler as any)
|
160 |
+
.call(
|
161 |
+
zoomHandler.transform as any,
|
162 |
+
to_bbox(
|
163 |
+
containerEl.clientWidth,
|
164 |
+
containerEl.clientHeight,
|
165 |
+
{ x: width / 2, y: height / 2 },
|
166 |
+
width,
|
167 |
+
height,
|
168 |
+
padding
|
169 |
+
)
|
170 |
+
)
|
171 |
+
// .call(zoomHandler.scaleTo as any, 1 / scale)
|
172 |
+
.on('pointermove', handlePointerMove)
|
173 |
+
.on('pointerleave', handlePointerLeave);
|
174 |
+
|
175 |
+
canvasCtx = $canvasEl.getContext('2d') as CanvasRenderingContext2D;
|
176 |
+
function zoomReset() {
|
177 |
+
const scale =
|
178 |
+
(width + padding * 2) /
|
179 |
+
(containerEl.clientHeight > containerEl.clientWidth
|
180 |
+
? containerEl.clientWidth
|
181 |
+
: containerEl.clientHeight);
|
182 |
+
zoomHandler.scaleExtent([1 / scale / 2, 3]);
|
183 |
+
selection.call(
|
184 |
+
zoomHandler.transform as any,
|
185 |
+
to_bbox(
|
186 |
+
containerEl.clientWidth,
|
187 |
+
containerEl.clientHeight,
|
188 |
+
{ x: width / 2, y: height / 2 },
|
189 |
+
width,
|
190 |
+
height,
|
191 |
+
padding
|
192 |
+
)
|
193 |
+
);
|
194 |
+
}
|
195 |
+
window.addEventListener('resize', zoomReset);
|
196 |
+
return () => {
|
197 |
+
window.removeEventListener('resize', zoomReset);
|
198 |
+
};
|
199 |
+
});
|
200 |
+
|
201 |
+
type ImageRendered = {
|
202 |
+
img: HTMLImageElement;
|
203 |
+
position: { x: number; y: number };
|
204 |
+
id: string;
|
205 |
+
};
|
206 |
+
async function renderImages(promptImgList: PromptImgObject[]) {
|
207 |
+
if (promptImgList.length === 0) return;
|
208 |
+
$isRenderingCanvas = true;
|
209 |
+
|
210 |
+
await Promise.allSettled(
|
211 |
+
promptImgList.map(
|
212 |
+
({ imgURL, position, id, room }) =>
|
213 |
+
new Promise<ImageRendered>((resolve, reject) => {
|
214 |
+
const img = new Image();
|
215 |
+
img.crossOrigin = 'anonymous';
|
216 |
+
img.onload = () => {
|
217 |
+
const res: ImageRendered = { img, position, id };
|
218 |
+
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
|
219 |
+
resolve(res);
|
220 |
+
};
|
221 |
+
img.onerror = (err) => {
|
222 |
+
reject(err);
|
223 |
+
};
|
224 |
+
img.src = `${PUBLIC_UPLOADS}/${room}/${imgURL}`;
|
225 |
+
})
|
226 |
+
)
|
227 |
+
).then((values) => {
|
228 |
+
const images = values
|
229 |
+
.filter((v) => v.status === 'fulfilled')
|
230 |
+
.map((v) => (v as PromiseFulfilledResult<ImageRendered>).value);
|
231 |
+
images.forEach(({ img, position, id }) => {
|
232 |
+
// keep track of images already rendered
|
233 |
+
//re draw in order
|
234 |
+
imagesOnCanvas.add(id);
|
235 |
+
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
|
236 |
+
});
|
237 |
+
});
|
238 |
+
$isRenderingCanvas = false;
|
239 |
+
}
|
240 |
+
function zoomed(e: Event) {
|
241 |
+
const transform = ($currZoomTransform = e.transform);
|
242 |
+
$canvasEl.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
|
243 |
+
}
|
244 |
+
|
245 |
+
// Update cursor presence to current pointer location
|
246 |
+
function handlePointerMove(event: PointerEvent) {
|
247 |
+
event.preventDefault();
|
248 |
+
const x = $currZoomTransform.invertX(event.clientX);
|
249 |
+
const y = $currZoomTransform.invertY(event.clientY);
|
250 |
+
|
251 |
+
myPresence.update({
|
252 |
+
cursor: {
|
253 |
+
x,
|
254 |
+
y
|
255 |
+
}
|
256 |
+
});
|
257 |
+
}
|
258 |
+
|
259 |
+
// When the pointer leaves the page, set cursor presence to null
|
260 |
+
function handlePointerLeave() {
|
261 |
+
myPresence.update({
|
262 |
+
cursor: null
|
263 |
+
});
|
264 |
+
}
|
265 |
+
</script>
|
266 |
+
|
267 |
+
<div
|
268 |
+
bind:this={containerEl}
|
269 |
+
class="absolute top-0 left-0 right-0 bottom-0 overflow-hidden z-0 bg-blue-200"
|
270 |
+
>
|
271 |
+
<canvas
|
272 |
+
bind:this={$canvasEl}
|
273 |
+
{width}
|
274 |
+
{height}
|
275 |
+
class="absolute top-0 left-0 bg-white shadow-2xl shadow-blue-500/20"
|
276 |
+
/>
|
277 |
+
<slot />
|
278 |
+
</div>
|
279 |
+
|
280 |
+
<style lang="postcss" scoped>
|
281 |
+
canvas {
|
282 |
+
transform-origin: 0 0;
|
283 |
+
}
|
284 |
+
</style>
|