First incomplete but working version
This commit is contained in:
commit
777d27a5d7
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
24
index.html
Normal file
24
index.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Local-First Geometry App</title>
|
||||||
|
<link rel="stylesheet" href="src/style.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Bereich für Buttons, um neue Formen anzulegen -->
|
||||||
|
<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 -->
|
||||||
|
<canvas id="shapesCanvas" width="600" height="400"></canvas>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
1681
package-lock.json
generated
Normal file
1681
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "automerge-tutorial",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@automerge/automerge-repo": "^1.2.1",
|
||||||
|
"@automerge/automerge-repo-network-websocket": "^1.2.1",
|
||||||
|
"@automerge/automerge-repo-storage-indexeddb": "^1.2.1",
|
||||||
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
|
"vite-plugin-wasm": "^3.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
50
src/canvasManager.js
Normal file
50
src/canvasManager.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// js/canvasManager.js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet alle Shapes auf dem Canvas neu
|
||||||
|
* @param {HTMLCanvasElement} canvas
|
||||||
|
* @param {Array} shapesArray - Array der Shapes aus dem Dokument
|
||||||
|
*/
|
||||||
|
export function drawAllShapesOnCanvas(canvas, shapesArray) {
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// Canvas leerräumen
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
shapesArray.forEach(shape => {
|
||||||
|
if (shape.type === "circle") {
|
||||||
|
drawCircle(ctx, shape);
|
||||||
|
} else if (shape.type === "triangle") {
|
||||||
|
drawTriangle(ctx, shape);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet einen Kreis
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
* @param {Object} circle
|
||||||
|
*/
|
||||||
|
function drawCircle(ctx, circle) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = circle.color || "black";
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet ein Dreieck
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
* @param {Object} triangle
|
||||||
|
*/
|
||||||
|
function drawTriangle(ctx, triangle) {
|
||||||
|
const [p1, p2, p3] = triangle.coordinates;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(p1.x, p1.y);
|
||||||
|
ctx.lineTo(p2.x, p2.y);
|
||||||
|
ctx.lineTo(p3.x, p3.y);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = triangle.color || "black";
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
86
src/docManager.js
Normal file
86
src/docManager.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// docManager.js (Automerge 2.x style)
|
||||||
|
|
||||||
|
import { Repo } from "@automerge/automerge-repo"
|
||||||
|
// For IndexedDB storage
|
||||||
|
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb'
|
||||||
|
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket"
|
||||||
|
|
||||||
|
let handle
|
||||||
|
|
||||||
|
export function initRepo() {
|
||||||
|
const repo = new Repo({
|
||||||
|
// Choose a network adapter or omit if you’re doing pure local
|
||||||
|
network: [new BrowserWebSocketClientAdapter("wss://automerge.rheinheim.fraction.ch")],
|
||||||
|
|
||||||
|
// Use IndexedDB instead of localStorage
|
||||||
|
storage: new IndexedDBStorageAdapter({
|
||||||
|
// optionally specify a database name (defaults to "automerge")
|
||||||
|
databaseName: "myAutomergeDB"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Now create or open a document
|
||||||
|
handle = repo.create()
|
||||||
|
|
||||||
|
// Listen for 'change' events
|
||||||
|
handle.on("change", ({doc}) => {
|
||||||
|
console.log("Document changed!")
|
||||||
|
// The up-to-date document is accessible via handle.doc
|
||||||
|
console.log("Current doc state:", doc)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize the doc with some data
|
||||||
|
handle.change(doc => {
|
||||||
|
doc.shapes = []
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
export function moveShape(shapeId, newX, newY) {
|
||||||
|
handle.change(doc => {
|
||||||
|
const shape = doc.shapes.find(s => s.id === shapeId)
|
||||||
|
if (shape) {
|
||||||
|
shape.x = newX
|
||||||
|
shape.y = newY
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
35
src/main.js
Normal file
35
src/main.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// main.js
|
||||||
|
import { initRepo, addCircle, addTriangle, moveShape } from "./docManager.js"
|
||||||
|
import { drawAllShapesOnCanvas } from "./canvasManager.js"
|
||||||
|
|
||||||
|
const canvas = document.getElementById("shapesCanvas")
|
||||||
|
|
||||||
|
// 1. Init the repo & document handle
|
||||||
|
const handle = initRepo()
|
||||||
|
// TODO is the whenReady() needed ?
|
||||||
|
// await handle.whenReady()
|
||||||
|
|
||||||
|
|
||||||
|
// 2. Subscribe so we can re-render whenever doc changes
|
||||||
|
// Listen for 'change' events
|
||||||
|
handle.on("change", ({doc}) => {
|
||||||
|
console.log("Document changed! Drawallshapes", doc)
|
||||||
|
drawAllShapesOnCanvas(canvas, doc.shapes)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// TODO render();
|
||||||
|
});
|
||||||
|
|
||||||
|
addTriangleButton.addEventListener("click", () => {
|
||||||
|
// z. B. zufällige Koordinaten
|
||||||
|
addTriangle( 200, 200, 220, 220, 180, 220);
|
||||||
|
// TODO render();
|
||||||
|
});
|
||||||
104
src/style.css
Normal file
104
src/style.css
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 6em;
|
||||||
|
padding: 1.5em;
|
||||||
|
will-change: filter;
|
||||||
|
transition: filter 300ms;
|
||||||
|
}
|
||||||
|
.logo:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #646cffaa);
|
||||||
|
}
|
||||||
|
.logo.vanilla:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #f7df1eaa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
vite.config.ts
Normal file
19
vite.config.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineConfig } from "vite"
|
||||||
|
import wasm from "vite-plugin-wasm"
|
||||||
|
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// customize this to your repo name for github pages deploy
|
||||||
|
base: "",
|
||||||
|
|
||||||
|
build: {
|
||||||
|
target: "esnext",
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [wasm(), topLevelAwait()],
|
||||||
|
|
||||||
|
worker: {
|
||||||
|
format: "es",
|
||||||
|
plugins: () => [wasm()],
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user