Compare commits
10 Commits
39a6efc076
...
c0a07eb6cd
| Author | SHA1 | Date | |
|---|---|---|---|
| c0a07eb6cd | |||
| 7f4594e9eb | |||
| 9e02b6ecff | |||
| 4a6b461045 | |||
| 6a9b95309c | |||
| 9400cc6a41 | |||
| f8d3f528a5 | |||
| 5d80253168 | |||
| 584926530c | |||
| bfe44bec1e |
16
index.html
16
index.html
@ -9,20 +9,8 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
<!-- Bereich für Buttons, um neue Formen anzulegen -->
|
<script type="module" src="/src/main.ts"></script>
|
||||||
<div id="controls">
|
|
||||||
<button id="addCircleButton">Kreis hinzufügen</button>
|
|
||||||
<button id="addTriangleButton">Dreieck hinzufügen</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Canvas, auf dem wir die Formen zeichnen -->
|
|
||||||
<svg id="shapesCanvas" width="800" height="600"
|
|
||||||
style="border: 1px solid black;"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
version="1.1">
|
|
||||||
</svg>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
264
package-lock.json
generated
264
package-lock.json
generated
@ -11,13 +11,40 @@
|
|||||||
"@automerge/automerge-repo": "^1.2.1",
|
"@automerge/automerge-repo": "^1.2.1",
|
||||||
"@automerge/automerge-repo-network-websocket": "^1.2.1",
|
"@automerge/automerge-repo-network-websocket": "^1.2.1",
|
||||||
"@automerge/automerge-repo-storage-indexeddb": "^1.2.1",
|
"@automerge/automerge-repo-storage-indexeddb": "^1.2.1",
|
||||||
|
"svelte": "^5.0.0-next.272",
|
||||||
"vite-plugin-top-level-await": "^1.4.4",
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
"vite-plugin-wasm": "^3.4.1"
|
"vite-plugin-wasm": "^3.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
"vite": "^6.0.5"
|
"vite": "^6.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ampproject/remapping": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||||
|
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@automerge/automerge": {
|
"node_modules/@automerge/automerge": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@automerge/automerge/-/automerge-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@automerge/automerge/-/automerge-2.2.8.tgz",
|
||||||
@ -558,6 +585,30 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||||
|
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/set-array": "^1.2.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||||
|
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
@ -567,6 +618,15 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/set-array": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||||
@ -859,6 +919,46 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"kleur": "^4.1.5",
|
||||||
|
"magic-string": "^0.30.15",
|
||||||
|
"vitefu": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || ^20.0.0 || >=22"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^5.0.0",
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sveltejs/vite-plugin-svelte-inspector": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || ^20.0.0 || >=22"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
|
"svelte": "^5.0.0",
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/core": {
|
"node_modules/@swc/core": {
|
||||||
"version": "1.10.4",
|
"version": "1.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.4.tgz",
|
||||||
@ -1103,13 +1203,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.10.5",
|
"version": "20.17.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz",
|
||||||
"integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
|
"integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
@ -1124,6 +1223,15 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn-typescript": {
|
||||||
|
"version": "1.4.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
|
||||||
|
"integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"acorn": ">=8.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn-walk": {
|
"node_modules/acorn-walk": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||||
@ -1142,6 +1250,24 @@
|
|||||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/aria-query": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/axobject-query": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base-x": {
|
"node_modules/base-x": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
|
||||||
@ -1198,6 +1324,15 @@
|
|||||||
"cbor-extract": "^2.2.0"
|
"cbor-extract": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/create-require": {
|
"node_modules/create-require": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
@ -1221,6 +1356,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||||
@ -1280,6 +1425,21 @@
|
|||||||
"@esbuild/win32-x64": "0.24.2"
|
"@esbuild/win32-x64": "0.24.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esm-env": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/esrap": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
@ -1306,6 +1466,15 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-reference": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isomorphic-ws": {
|
"node_modules/isomorphic-ws": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||||
@ -1315,6 +1484,31 @@
|
|||||||
"ws": "*"
|
"ws": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kleur": {
|
||||||
|
"version": "4.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||||
|
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/locate-character": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/magic-string": {
|
||||||
|
"version": "0.30.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||||
|
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/make-error": {
|
"node_modules/make-error": {
|
||||||
"version": "1.3.6",
|
"version": "1.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
@ -1441,6 +1635,31 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svelte": {
|
||||||
|
"version": "5.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.18.0.tgz",
|
||||||
|
"integrity": "sha512-/Eb81lB8bVUxQPmkPVNBYrU9cZ544+9hE91ZUUXTMf7eWcGW84N1hS3gvv/XsUNOWLLg3IicXP2qa8W3KpTUHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ampproject/remapping": "^2.3.0",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
|
"@types/estree": "^1.0.5",
|
||||||
|
"acorn": "^8.12.1",
|
||||||
|
"acorn-typescript": "^1.4.13",
|
||||||
|
"aria-query": "^5.3.1",
|
||||||
|
"axobject-query": "^4.1.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"esm-env": "^1.2.1",
|
||||||
|
"esrap": "^1.4.3",
|
||||||
|
"is-reference": "^3.0.3",
|
||||||
|
"locate-character": "^3.0.0",
|
||||||
|
"magic-string": "^0.30.11",
|
||||||
|
"zimmerframe": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tiny-typed-emitter": {
|
"node_modules/tiny-typed-emitter": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
||||||
@ -1495,7 +1714,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@ -1505,11 +1723,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.1",
|
||||||
@ -1637,6 +1854,25 @@
|
|||||||
"vite": "^2 || ^3 || ^4 || ^5 || ^6"
|
"vite": "^2 || ^3 || ^4 || ^5 || ^6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitefu": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"tests/deps/*",
|
||||||
|
"tests/projects/*"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
@ -1676,6 +1912,12 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zimmerframe": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,16 +5,20 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
"vite": "^6.0.5"
|
"vite": "^6.0.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automerge/automerge-repo": "^1.2.1",
|
"@automerge/automerge-repo": "^1.2.1",
|
||||||
"@automerge/automerge-repo-network-websocket": "^1.2.1",
|
"@automerge/automerge-repo-network-websocket": "^1.2.1",
|
||||||
"@automerge/automerge-repo-storage-indexeddb": "^1.2.1",
|
"@automerge/automerge-repo-storage-indexeddb": "^1.2.1",
|
||||||
|
"svelte": "^5.0.0-next.272",
|
||||||
"vite-plugin-top-level-await": "^1.4.4",
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
"vite-plugin-wasm": "^3.4.1"
|
"vite-plugin-wasm": "^3.4.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
// js/canvasManager.js
|
|
||||||
|
|
||||||
import { Circle } from './shapes/Circle';
|
|
||||||
import { Triangle } from './shapes/Triangle';
|
|
||||||
|
|
||||||
export function createOrUpdateShapes(container, shapesArray, patches = []) {
|
|
||||||
if (!container || !(container instanceof SVGElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure patches is an array
|
|
||||||
const patchArray = patches?.patches || patches || [];
|
|
||||||
if (!Array.isArray(patchArray)) {
|
|
||||||
console.warn("Expected patches to be an array, got:", typeof patchArray, patchArray);
|
|
||||||
patches = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no patches, do a full redraw
|
|
||||||
if (!patchArray || patchArray.length === 0) {
|
|
||||||
// Clear container
|
|
||||||
while (container.firstChild) {
|
|
||||||
container.removeChild(container.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw all shapes
|
|
||||||
shapesArray.forEach(shapeData => {
|
|
||||||
const shape = createShapeInstance(shapeData);
|
|
||||||
if (shape) {
|
|
||||||
const element = shape.createSVGElement();
|
|
||||||
container.appendChild(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process patches
|
|
||||||
patchArray.forEach(patch => {
|
|
||||||
if (patch.path[0] === 'shapes') {
|
|
||||||
if (patch.action === 'put') {
|
|
||||||
const shapeIndex = patch.path[1];
|
|
||||||
const shapeData = shapesArray[shapeIndex];
|
|
||||||
if (!shapeData) return;
|
|
||||||
|
|
||||||
const shape = createShapeInstance(shapeData);
|
|
||||||
if (!shape) return;
|
|
||||||
|
|
||||||
let element = document.getElementById(shapeData.id);
|
|
||||||
if (!element) {
|
|
||||||
// Create new element
|
|
||||||
element = shape.createSVGElement();
|
|
||||||
container.appendChild(element);
|
|
||||||
} else {
|
|
||||||
// Update existing element
|
|
||||||
shape.updateAttributes(element);
|
|
||||||
}
|
|
||||||
} else if (patch.action === 'del') {
|
|
||||||
const element = document.getElementById(patch.path[1]);
|
|
||||||
if (element) {
|
|
||||||
container.removeChild(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createShapeInstance(shapeData) {
|
|
||||||
switch (shapeData.type) {
|
|
||||||
case "circle":
|
|
||||||
return new Circle(shapeData.id, shapeData.x, shapeData.y, shapeData.radius, shapeData.color);
|
|
||||||
case "triangle":
|
|
||||||
return new Triangle(shapeData.id, shapeData.coordinates, shapeData.color);
|
|
||||||
default:
|
|
||||||
console.warn(`Unknown shape type: ${shapeData.type}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
122
src/components/Canvas.svelte
Normal file
122
src/components/Canvas.svelte
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Circle from "./shapes/Circle.svelte";
|
||||||
|
import Triangle from "./shapes/Triangle.svelte";
|
||||||
|
import type { Shape } from "../types";
|
||||||
|
import type { DocHandle } from "@automerge/automerge-repo";
|
||||||
|
import type { AppDocument } from "../types";
|
||||||
|
import {
|
||||||
|
updateDocWithNewCircle,
|
||||||
|
updateDocWithNewTriangle,
|
||||||
|
updateDocWithShapeMove,
|
||||||
|
} from "../docManager";
|
||||||
|
|
||||||
|
const { handle } = $props<{
|
||||||
|
handle: DocHandle<AppDocument>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let shapes = $state<Shape[]>([]);
|
||||||
|
let draggedShape = $state<{
|
||||||
|
id: string;
|
||||||
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
} | null>(null);
|
||||||
|
let svgElement = $state<SVGSVGElement>();
|
||||||
|
|
||||||
|
// Automerge document subscription
|
||||||
|
// This is called once to initialize the shapes
|
||||||
|
$effect(() => {
|
||||||
|
console.log("Subscribing to document changes"); // Debug log
|
||||||
|
const doc = handle.docSync();
|
||||||
|
if (doc) {
|
||||||
|
shapes = doc.shapes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.on("change", ({ doc }: { doc: AppDocument | undefined }) => {
|
||||||
|
if (doc) {
|
||||||
|
console.log("Document changed:", doc.shapes); // Debug log
|
||||||
|
// it looks like Svelte is clever enough to make only the necessary DOM changes
|
||||||
|
// without having to use patches
|
||||||
|
shapes = doc.shapes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleDragStart(e: MouseEvent, shape: Shape) {
|
||||||
|
if (!svgElement) return;
|
||||||
|
|
||||||
|
const point = svgElement.createSVGPoint();
|
||||||
|
point.x = e.clientX;
|
||||||
|
point.y = e.clientY;
|
||||||
|
const { x, y } = point.matrixTransform(svgElement.getScreenCTM()!.inverse());
|
||||||
|
|
||||||
|
draggedShape = { id: String(shape.id), startX: x, startY: y };
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragMove(e: MouseEvent) {
|
||||||
|
if (!draggedShape || !svgElement) return;
|
||||||
|
const point = svgElement.createSVGPoint();
|
||||||
|
point.x = e.clientX;
|
||||||
|
point.y = e.clientY;
|
||||||
|
const { x, y } = point.matrixTransform(svgElement.getScreenCTM()!.inverse());
|
||||||
|
updateDocWithShapeMove(draggedShape.id, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTestCircle() {
|
||||||
|
updateDocWithNewCircle(100, 100, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTestTriangle() {
|
||||||
|
updateDocWithNewTriangle(200, 200, 220, 220, 180, 220);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<svg
|
||||||
|
bind:this={svgElement}
|
||||||
|
id="shapesCanvas"
|
||||||
|
viewBox="0 0 800 600"
|
||||||
|
role="presentation"
|
||||||
|
onmousemove={(e) => {
|
||||||
|
handleDragMove(e);
|
||||||
|
}}
|
||||||
|
onmouseup={() => (draggedShape = null)}
|
||||||
|
>
|
||||||
|
{#each shapes as shape (shape.id)}
|
||||||
|
{#if shape.type === "circle"}
|
||||||
|
<Circle
|
||||||
|
{...shape}
|
||||||
|
handleDragStart={(e: MouseEvent, shape: Shape) => handleDragStart(e, shape)}
|
||||||
|
/>
|
||||||
|
{:else if shape.type === "triangle"}
|
||||||
|
<Triangle
|
||||||
|
{...shape}
|
||||||
|
handleDragStart={(e: MouseEvent, shape: Shape) => handleDragStart(e, shape)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button onclick={addTestCircle}>Add Circle</button>
|
||||||
|
<button onclick={addTestTriangle}>Add Triangle</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
24
src/components/shapes/Circle.svelte
Normal file
24
src/components/shapes/Circle.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { id, x, y, radius, color, handleDragStart } = $props();
|
||||||
|
|
||||||
|
const shape = {
|
||||||
|
id,
|
||||||
|
type: 'circle',
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
radius,
|
||||||
|
color
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<circle
|
||||||
|
{id}
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={radius}
|
||||||
|
fill={color}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-label="Draggable circle"
|
||||||
|
onmousedown={(e) => handleDragStart(e, shape)}
|
||||||
|
/>
|
||||||
27
src/components/shapes/Triangle.svelte
Normal file
27
src/components/shapes/Triangle.svelte
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { id, coordinates, color, handleDragStart } = $props();
|
||||||
|
|
||||||
|
let points = $state('');
|
||||||
|
$effect(() => {
|
||||||
|
points = coordinates
|
||||||
|
.map((p: { x: any; y: any; }) => `${p.x},${p.y}`)
|
||||||
|
.join(" ");
|
||||||
|
});
|
||||||
|
|
||||||
|
const shape = {
|
||||||
|
id,
|
||||||
|
type: 'triangle',
|
||||||
|
coordinates,
|
||||||
|
color
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<polygon
|
||||||
|
{id}
|
||||||
|
{points}
|
||||||
|
fill={color}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-label="Draggable triangle"
|
||||||
|
onmousedown={(e) => handleDragStart(e, shape)}
|
||||||
|
/>
|
||||||
@ -1,130 +0,0 @@
|
|||||||
// docManager.js (Automerge 2.x style)
|
|
||||||
|
|
||||||
import { Repo, isValidAutomergeUrl } from "@automerge/automerge-repo"
|
|
||||||
import * as Automerge from "@automerge/automerge"
|
|
||||||
// For IndexedDB storage
|
|
||||||
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb'
|
|
||||||
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket"
|
|
||||||
|
|
||||||
let handle
|
|
||||||
|
|
||||||
// Define the current schema version
|
|
||||||
const CURRENT_SCHEMA_VERSION = 1;
|
|
||||||
|
|
||||||
export async function initRepo() {
|
|
||||||
const repo = new Repo({
|
|
||||||
network: [
|
|
||||||
new BrowserWebSocketClientAdapter("wss://automerge.rheinheim.fraction.ch")
|
|
||||||
],
|
|
||||||
storage: new IndexedDBStorageAdapter()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check for existing docId in URL hash
|
|
||||||
const rootDocUrl = document.location.hash.substring(1)
|
|
||||||
|
|
||||||
if (rootDocUrl && isValidAutomergeUrl(rootDocUrl)) {
|
|
||||||
handle = repo.find(rootDocUrl)
|
|
||||||
// Wait for handle to be ready
|
|
||||||
await handle.whenReady()
|
|
||||||
|
|
||||||
// Check and upgrade schema if needed
|
|
||||||
handle.change(doc => {
|
|
||||||
if (!doc.schemaVersion) {
|
|
||||||
// Handle legacy documents (pre-versioning)
|
|
||||||
doc.schemaVersion = 1
|
|
||||||
}
|
|
||||||
// Add future upgrade logic here
|
|
||||||
// if (doc.schemaVersion < 2) {
|
|
||||||
// // Example: upgrade shapes to include a new required field
|
|
||||||
// doc.shapes.forEach(shape => {
|
|
||||||
// shape.opacity = 1.0 // Add new field with default value
|
|
||||||
// })
|
|
||||||
// doc.schemaVersion = 2
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Create new document with current schema version
|
|
||||||
handle = repo.create({
|
|
||||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
||||||
shapes: []
|
|
||||||
})
|
|
||||||
// Wait for handle to be ready
|
|
||||||
await handle.whenReady()
|
|
||||||
document.location.hash = handle.url
|
|
||||||
}
|
|
||||||
|
|
||||||
return handle
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new circle to the document
|
|
||||||
*/
|
|
||||||
export function addCircle(x, y, radius) {
|
|
||||||
handle.change(doc => {
|
|
||||||
doc.shapes.push({
|
|
||||||
id: Date.now(),
|
|
||||||
type: "circle",
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
radius,
|
|
||||||
color: "red"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt dem Dokument ein Dreieck hinzu
|
|
||||||
*/
|
|
||||||
export function addTriangle(x1, y1, x2, y2, x3, y3) {
|
|
||||||
handle.change(doc => {
|
|
||||||
doc.shapes.push({
|
|
||||||
id: Date.now(),
|
|
||||||
type: "triangle",
|
|
||||||
coordinates: [
|
|
||||||
{ x: x1, y: y1 },
|
|
||||||
{ x: x2, y: y2 },
|
|
||||||
{ x: x3, y: y3 }
|
|
||||||
],
|
|
||||||
color: "blue"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move a shape in the document
|
|
||||||
*
|
|
||||||
* Watch out: do not use the Array.find method, because these are Proxies and the find method is not supported on Proxies.
|
|
||||||
* Also make sure that you compare numbers with numbers, not strings.
|
|
||||||
*/
|
|
||||||
export function moveShape(shapeId, newX, newY) {
|
|
||||||
handle.change(doc => {
|
|
||||||
// Find the index of the shape instead of using find()
|
|
||||||
// TODO faster implementation than for loop
|
|
||||||
for (let i = 0; i < doc.shapes.length; i++) {
|
|
||||||
if (doc.shapes[i].id === Number(shapeId)) {
|
|
||||||
if (doc.shapes[i].type === "circle") {
|
|
||||||
doc.shapes[i].x = newX;
|
|
||||||
doc.shapes[i].y = newY;
|
|
||||||
} else if (doc.shapes[i].type === "triangle") {
|
|
||||||
// Calculate the current center
|
|
||||||
const center = {
|
|
||||||
x: doc.shapes[i].coordinates.reduce((sum, p) => sum + p.x, 0) / 3,
|
|
||||||
y: doc.shapes[i].coordinates.reduce((sum, p) => sum + p.y, 0) / 3
|
|
||||||
};
|
|
||||||
// Calculate the movement delta
|
|
||||||
const dx = newX - center.x;
|
|
||||||
const dy = newY - center.y;
|
|
||||||
|
|
||||||
// Move all points
|
|
||||||
doc.shapes[i].coordinates = doc.shapes[i].coordinates.map(p => ({
|
|
||||||
x: p.x + dx,
|
|
||||||
y: p.y + dy
|
|
||||||
}));
|
|
||||||
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
95
src/docManager.ts
Normal file
95
src/docManager.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { Repo, DocHandle, isValidAutomergeUrl, NetworkAdapterInterface } from "@automerge/automerge-repo";
|
||||||
|
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb';
|
||||||
|
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
|
||||||
|
import { AppDocument, CircleShape, TriangleShape } from "./types";
|
||||||
|
|
||||||
|
let handle: DocHandle<AppDocument>;
|
||||||
|
|
||||||
|
const CURRENT_SCHEMA_VERSION = 1;
|
||||||
|
|
||||||
|
export async function initRepo(): Promise<DocHandle<AppDocument>> {
|
||||||
|
const repo = new Repo({
|
||||||
|
network: [
|
||||||
|
new BrowserWebSocketClientAdapter("wss://automerge.rheinheim.fraction.ch") as unknown as NetworkAdapterInterface
|
||||||
|
],
|
||||||
|
storage: new IndexedDBStorageAdapter()
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootDocUrl = document.location.hash.substring(1);
|
||||||
|
|
||||||
|
if (rootDocUrl && isValidAutomergeUrl(rootDocUrl)) {
|
||||||
|
handle = repo.find<AppDocument>(rootDocUrl);
|
||||||
|
} else {
|
||||||
|
handle = repo.create<AppDocument>();
|
||||||
|
handle.change((doc: AppDocument) => {
|
||||||
|
doc.schemaVersion = CURRENT_SCHEMA_VERSION;
|
||||||
|
doc.shapes = [];
|
||||||
|
});
|
||||||
|
document.location.hash = handle.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
await handle.whenReady();
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDocWithNewCircle(x: number, y: number, radius: number): void {
|
||||||
|
console.log('Adding circle:', { x, y, radius }); // Debug log
|
||||||
|
handle.change((doc: AppDocument) => {
|
||||||
|
const circle: CircleShape = {
|
||||||
|
id: Date.now(),
|
||||||
|
type: "circle",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
radius,
|
||||||
|
color: "red"
|
||||||
|
};
|
||||||
|
if (!doc.shapes) {
|
||||||
|
doc.shapes = [];
|
||||||
|
}
|
||||||
|
doc.shapes.push(circle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDocWithNewTriangle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void {
|
||||||
|
console.log('Adding triangle:', { x1, y1, x2, y2, x3, y3 }); // Debug log
|
||||||
|
handle.change((doc: AppDocument) => {
|
||||||
|
const triangle: TriangleShape = {
|
||||||
|
id: Date.now(),
|
||||||
|
type: "triangle",
|
||||||
|
coordinates: [
|
||||||
|
{ x: x1, y: y1 },
|
||||||
|
{ x: x2, y: y2 },
|
||||||
|
{ x: x3, y: y3 }
|
||||||
|
],
|
||||||
|
color: "blue"
|
||||||
|
};
|
||||||
|
if (!doc.shapes) {
|
||||||
|
doc.shapes = [];
|
||||||
|
}
|
||||||
|
doc.shapes.push(triangle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateDocWithShapeMove = (shapeId: string, x: number, y: number) => {
|
||||||
|
handle.change((doc: AppDocument) => {
|
||||||
|
const shape = doc.shapes.find(s => s.id === Number(shapeId));
|
||||||
|
if (!shape) return;
|
||||||
|
|
||||||
|
if (shape.type === "circle") {
|
||||||
|
shape.x = x;
|
||||||
|
shape.y = y;
|
||||||
|
} else if (shape.type === "triangle") {
|
||||||
|
const center = {
|
||||||
|
x: shape.coordinates.reduce((sum, p) => sum + p.x, 0) / 3,
|
||||||
|
y: shape.coordinates.reduce((sum, p) => sum + p.y, 0) / 3
|
||||||
|
};
|
||||||
|
const dx = x - center.x;
|
||||||
|
const dy = y - center.y;
|
||||||
|
|
||||||
|
shape.coordinates = shape.coordinates.map(p => ({
|
||||||
|
x: p.x + dx,
|
||||||
|
y: p.y + dy
|
||||||
|
})) as [{ x: number; y: number }, { x: number; y: number }, { x: number; y: number }];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
85
src/main.js
85
src/main.js
@ -1,85 +0,0 @@
|
|||||||
// main.js
|
|
||||||
import { initRepo, addCircle, addTriangle, moveShape } from "./docManager.js"
|
|
||||||
import { createOrUpdateShapes } from "./canvasManager.js"
|
|
||||||
|
|
||||||
let isDragging = false;
|
|
||||||
let selectedShapeId = null;
|
|
||||||
let offsetX = 0;
|
|
||||||
let offsetY = 0;
|
|
||||||
|
|
||||||
const container = document.getElementById("shapesCanvas");
|
|
||||||
|
|
||||||
// Add throttle function at the top
|
|
||||||
function throttle(func, limit) {
|
|
||||||
let inThrottle;
|
|
||||||
return function(...args) {
|
|
||||||
if (!inThrottle) {
|
|
||||||
func.apply(this, args);
|
|
||||||
inThrottle = true;
|
|
||||||
setTimeout(() => inThrottle = false, limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the document
|
|
||||||
const handle = await initRepo()
|
|
||||||
|
|
||||||
|
|
||||||
// draw current state
|
|
||||||
createOrUpdateShapes(container, handle.docSync().shapes)
|
|
||||||
|
|
||||||
// 2. Subscribe so we can re-render whenever doc changes
|
|
||||||
handle.on("change", (change) => {
|
|
||||||
createOrUpdateShapes(container, change.doc.shapes, change.patches);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. Buttons holen
|
|
||||||
const addCircleButton = document.getElementById("addCircleButton");
|
|
||||||
const addTriangleButton = document.getElementById("addTriangleButton");
|
|
||||||
|
|
||||||
// 4. Event Listener für Buttons
|
|
||||||
addCircleButton.addEventListener("click", () => {
|
|
||||||
// z. B. zufällige Position/Radius
|
|
||||||
addCircle(100, 100, 30);
|
|
||||||
});
|
|
||||||
|
|
||||||
addTriangleButton.addEventListener("click", () => {
|
|
||||||
// z. B. zufällige Koordinaten
|
|
||||||
addTriangle( 200, 200, 220, 220, 180, 220);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mousedown: Prüfen, ob wir auf ein Shape klicken
|
|
||||||
container.addEventListener("mousedown", (e) => {
|
|
||||||
const clickedElement = e.target;
|
|
||||||
if (clickedElement.tagName === 'circle' || clickedElement.tagName === 'polygon') {
|
|
||||||
isDragging = true;
|
|
||||||
selectedShapeId = clickedElement.id;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Throttled version of moveShape
|
|
||||||
const throttledMoveShape = throttle((shapeId, x, y) => {
|
|
||||||
moveShape(shapeId, x, y);
|
|
||||||
}, 16); // 50ms throttle time (20 updates per second)
|
|
||||||
|
|
||||||
// Mousemove: Shape verschieben, sofern dragging aktiv ist
|
|
||||||
container.addEventListener("mousemove", (e) => {
|
|
||||||
if (!isDragging || selectedShapeId === null) return;
|
|
||||||
|
|
||||||
const pt = container.createSVGPoint();
|
|
||||||
pt.x = e.clientX;
|
|
||||||
pt.y = e.clientY;
|
|
||||||
const svgP = pt.matrixTransform(container.getScreenCTM().inverse());
|
|
||||||
|
|
||||||
const newX = svgP.x - offsetX;
|
|
||||||
const newY = svgP.y - offsetY;
|
|
||||||
|
|
||||||
throttledMoveShape(selectedShapeId, newX, newY);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mouseup: Dragging beenden
|
|
||||||
container.addEventListener("mouseup", () => {
|
|
||||||
isDragging = false;
|
|
||||||
selectedShapeId = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
16
src/main.ts
Normal file
16
src/main.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Canvas from './components/Canvas.svelte';
|
||||||
|
import { initRepo } from './docManager';
|
||||||
|
import { mount } from 'svelte';
|
||||||
|
|
||||||
|
async function initApp() {
|
||||||
|
const handle = await initRepo();
|
||||||
|
|
||||||
|
mount(Canvas, {
|
||||||
|
target: document.getElementById('app')!,
|
||||||
|
props: {
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initApp().catch(console.error);
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { Shape } from './Shape';
|
|
||||||
|
|
||||||
export class Circle extends Shape {
|
|
||||||
constructor(id, x, y, radius, color = "red") {
|
|
||||||
super(id, "circle", color);
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.radius = radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSVGElementType() {
|
|
||||||
return "circle";
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAttributes(element) {
|
|
||||||
element.setAttributeNS(null, "cx", this.x);
|
|
||||||
element.setAttributeNS(null, "cy", this.y);
|
|
||||||
element.setAttributeNS(null, "r", this.radius);
|
|
||||||
element.setAttributeNS(null, "fill", this.color);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
||||||
|
|
||||||
export class Shape {
|
|
||||||
constructor(id, type, color = "black") {
|
|
||||||
this.id = id;
|
|
||||||
this.type = type;
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
createSVGElement() {
|
|
||||||
const element = document.createElementNS(SVG_NS, this.getSVGElementType());
|
|
||||||
element.id = this.id;
|
|
||||||
this.updateAttributes(element);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abstract methods
|
|
||||||
getSVGElementType() {
|
|
||||||
throw new Error("Must be implemented by derived class");
|
|
||||||
}
|
|
||||||
// this update also takes care of movements. The actual movement is done in documentManager.js
|
|
||||||
updateAttributes(element) {
|
|
||||||
throw new Error("Must be implemented by derived class");
|
|
||||||
}
|
|
||||||
|
|
||||||
move(newX, newY) {
|
|
||||||
throw new Error("Must be implemented by derived class");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { Shape } from './Shape';
|
|
||||||
|
|
||||||
export class Triangle extends Shape {
|
|
||||||
constructor(id, coordinates, color = "blue") {
|
|
||||||
super(id, "triangle", color);
|
|
||||||
this.coordinates = coordinates;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSVGElementType() {
|
|
||||||
return "polygon";
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAttributes(element) {
|
|
||||||
const points = this.coordinates
|
|
||||||
.map(p => `${p.x},${p.y}`)
|
|
||||||
.join(" ");
|
|
||||||
element.setAttributeNS(null, "points", points);
|
|
||||||
element.setAttributeNS(null, "fill", this.color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
35
src/types.ts
Normal file
35
src/types.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export interface ShapeBase {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CircleShape extends ShapeBase {
|
||||||
|
type: 'circle';
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Coordinate {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TriangleShape extends ShapeBase {
|
||||||
|
type: 'triangle';
|
||||||
|
coordinates: [Coordinate, Coordinate, Coordinate];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Shape = CircleShape | TriangleShape;
|
||||||
|
|
||||||
|
export interface AppDocument {
|
||||||
|
schemaVersion: number;
|
||||||
|
shapes: Shape[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Patch {
|
||||||
|
action: 'put' | 'del';
|
||||||
|
path: string[];
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ES2020",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
import wasm from "vite-plugin-wasm"
|
import wasm from "vite-plugin-wasm"
|
||||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
import topLevelAwait from "vite-plugin-top-level-await"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// customize this to your repo name for github pages deploy
|
// customize this to your repo name for github pages deploy
|
||||||
@ -10,10 +11,14 @@ export default defineConfig({
|
|||||||
target: "esnext",
|
target: "esnext",
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [wasm(), topLevelAwait()],
|
plugins: [
|
||||||
|
wasm(),
|
||||||
|
topLevelAwait(),
|
||||||
|
svelte()
|
||||||
|
],
|
||||||
|
|
||||||
worker: {
|
worker: {
|
||||||
format: "es",
|
format: "es",
|
||||||
plugins: () => [wasm()],
|
plugins: () => [svelte()],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user