commit 6860ab45ccb4024787f418c19ff5e6f4362789ea Author: Michael Bobbitt Date: Thu Dec 19 20:35:08 2024 -0500 batman diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6988d2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/* +client/node_modules/* +package-lock.json diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..40395f0 --- /dev/null +++ b/client/index.html @@ -0,0 +1,13 @@ + + + + + + Excalidraw App + + +
+ + + + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..cf15d8e --- /dev/null +++ b/client/package.json @@ -0,0 +1,15 @@ +{ + "dependencies": { + "@excalidraw/excalidraw": "^0.17.6", + "@solidjs/router": "^0.6.0", + "@vitejs/plugin-react": "^4.3.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "solid-js": "^1.7.0", + "solid-react-compat": "^0.0.2" + }, + "scripts": { + "build": "vite build", + "dev": "vite dev" + } +} diff --git a/client/src/index.jsx b/client/src/index.jsx new file mode 100644 index 0000000..c706ed5 --- /dev/null +++ b/client/src/index.jsx @@ -0,0 +1,16 @@ +import { render } from "solid-js/web"; +import { Excalidraw } from "@excalidraw/excalidraw"; + +function App() { + return ( +
+

Excalidraw Example

+
+ +
+
+ ); +} + +render(() => , document.getElementById("root")); + diff --git a/client/src/initExcalidraw.js b/client/src/initExcalidraw.js new file mode 100644 index 0000000..e2eab01 --- /dev/null +++ b/client/src/initExcalidraw.js @@ -0,0 +1,19 @@ +// src/initExcalidraw.js +// Helper to initialize Excalidraw in a SolidJS environment. +// We import the necessary modules from 'excalidraw' and create the editor. +import { initializeApp, importFromLocalStorage } from "@excalidraw/excalidraw"; + +export async function initializeExcalidraw(container, { initialData }) { + // In a real scenario, you'd use the official Excalidraw package API. + // The pseudo code below assumes a function `initializeApp` that returns { app, api }. + // Check Excalidraw docs for actual integration steps. + + const { app, api } = await initializeApp({ + target: container, + // initialData may be: { elements, appState, ... } + initialData: initialData || {}, + UIOptions: { dockedSidebar: true }, + }); + + return api; +} diff --git a/client/vite.config.js b/client/vite.config.js new file mode 100644 index 0000000..ace6fdd --- /dev/null +++ b/client/vite.config.js @@ -0,0 +1,7 @@ +// vite.config.js +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..baae9e5 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "vite": "^6.0.3" + } +} diff --git a/server/cmd/main.go b/server/cmd/main.go new file mode 100644 index 0000000..2090630 --- /dev/null +++ b/server/cmd/main.go @@ -0,0 +1,68 @@ +// main.go +// (Run: go build && ./yourapp) +// This server serves the static files for the client from "./public" +// and provides endpoints to load/save a single .excalidraw file. +// +// Make sure to create a "public" folder and build the SolidJS client into it. +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "os" +) + +const stateFile = "drawing.excalidraw" + +func main() { + http.Handle("/", http.FileServer(http.Dir("public"))) + + http.HandleFunc("/api/state", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + data, err := os.ReadFile(stateFile) + if err != nil { + log.Println("No file yet or error reading file:", err) + // If no file yet, return empty object + w.Write([]byte(`{}`)) + return + } + w.Write(data) + return + } else if r.Method == http.MethodPost { + log.Println("POST /api/state") + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "cannot read body", http.StatusBadRequest) + return + } + // Validate JSON + var js interface{} + if err := json.Unmarshal(body, &js); err != nil { + http.Error(w, "invalid json", http.StatusBadRequest) + return + } + if err := os.WriteFile(stateFile, body, 0644); err != nil { + http.Error(w, "cannot save file", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + return + } + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + }) + + log.Println("Server running on http://localhost:8080") + http.ListenAndServe(":8080", nil) +} diff --git a/server/drawing.excalidraw b/server/drawing.excalidraw new file mode 100644 index 0000000..0696be9 --- /dev/null +++ b/server/drawing.excalidraw @@ -0,0 +1 @@ +{"type":"excalidraw","version":2,"source":"excalidraw","elements":[{"type":"rectangle","version":184,"versionNonce":1988320512,"isDeleted":false,"id":"OdC9WAFR3WXXQ2XinxF-T","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":141.5491424486063,"y":1248.7179763038112,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":1295,"height":301,"seed":723743842,"groupIds":[],"frameId":null,"roundness":{"type":3},"boundElements":[],"updated":1733561941129,"link":null,"locked":false},{"type":"rectangle","version":94,"versionNonce":1749448939,"isDeleted":false,"id":"2UCbbNhS-6AdpLrIWI_MB","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":336,"y":384,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":1074,"height":141,"seed":144425804,"groupIds":[],"frameId":null,"roundness":{"type":3},"boundElements":[],"updated":1733561426441,"link":null,"locked":false},{"type":"text","version":296,"versionNonce":1359012722,"isDeleted":false,"id":"3fHuXQyk4ktLmeYinLdRJ","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":1063.3946704101563,"y":414,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":296.73333740234375,"height":70.99999999999996,"seed":1940463224,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148620,"link":null,"locked":false,"fontSize":56.79999999999997,"fontFamily":1,"text":"this is new","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"this is new","lineHeight":1.25,"baseline":50},{"type":"freedraw","version":5,"versionNonce":108929931,"isDeleted":true,"id":"kG1QctXrkHZUzasndAsga","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":407,"y":440,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":0.0001,"height":0.0001,"seed":1510982315,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733561426441,"link":null,"locked":false,"points":[[0,0],[0.0001,0.0001]],"lastCommittedPoint":null,"simulatePressure":true,"pressures":[]},{"type":"freedraw","version":4,"versionNonce":1759886469,"isDeleted":true,"id":"pz57op1ffQtxCh1M4Zzzl","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":405,"y":438,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":0.0001,"height":0.0001,"seed":377070501,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733561426223,"link":null,"locked":false,"points":[[0,0],[0.0001,0.0001]],"lastCommittedPoint":null,"simulatePressure":true,"pressures":[]},{"type":"text","version":49,"versionNonce":1720108206,"isDeleted":false,"id":"TjJYzzpsekDNkjfyHmA0R","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":376,"y":408,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":416.6166687011719,"height":25,"seed":1638365739,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148621,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"If i do anything significant it gets saved?","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"If i do anything significant it gets saved?","lineHeight":1.25,"baseline":18},{"type":"rectangle","version":11,"versionNonce":28527544,"isDeleted":false,"id":"MzDDzI_y9Dd-d-9Tv5v4A","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":532,"y":684,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":426,"height":128,"seed":1179000760,"groupIds":[],"frameId":null,"roundness":{"type":3},"boundElements":[{"type":"text","id":"UcxBDGxuViyKfLT-fAbUg"}],"updated":1733561705558,"link":null,"locked":false},{"type":"text","version":31,"versionNonce":1733264056,"isDeleted":false,"id":"UcxBDGxuViyKfLT-fAbUg","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":590.6750030517578,"y":735.5,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":308.6499938964844,"height":25,"seed":425084104,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733561713106,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"Idk how to make it consistent?","textAlign":"center","verticalAlign":"middle","containerId":"MzDDzI_y9Dd-d-9Tv5v4A","originalText":"Idk how to make it consistent?","lineHeight":1.25,"baseline":20},{"type":"text","version":36,"versionNonce":1035445554,"isDeleted":false,"id":"jNqeRkDn3iICZ9hqs_0Cr","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":578,"y":476,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":289.3999938964844,"height":25,"seed":534804324,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148621,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"can i get mobile to work tho?","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"can i get mobile to work tho?","lineHeight":1.25,"baseline":18},{"type":"text","version":26,"versionNonce":1457913582,"isDeleted":false,"id":"qXz309N836p7cZZimAA41","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":772,"y":576,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":192.38333129882812,"height":25,"seed":822126207,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148621,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"I sure do hope so…","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"I sure do hope so…","lineHeight":1.25,"baseline":18},{"type":"rectangle","version":120,"versionNonce":1394662656,"isDeleted":false,"id":"JqfqumtLd74bZ46gst1BX","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":1118,"y":672,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":351.79487620860976,"height":379.7991532070927,"seed":1409585387,"groupIds":[],"frameId":null,"roundness":{"type":3},"boundElements":[{"type":"text","id":"--0Gy1ZgLYBIN9YAbq9zM"}],"updated":1733561911190,"link":null,"locked":false},{"type":"text","version":223,"versionNonce":1519516928,"isDeleted":false,"id":"--0Gy1ZgLYBIN9YAbq9zM","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":1176.7074356628987,"y":849.3995766035464,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":234.3800048828125,"height":25,"seed":965956197,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733561911190,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"maybe a race condition?","textAlign":"center","verticalAlign":"middle","containerId":"JqfqumtLd74bZ46gst1BX","originalText":"maybe a race condition?","lineHeight":1.25,"baseline":20},{"type":"text","version":2,"versionNonce":584139051,"isDeleted":true,"id":"yI51Gyece-2aONd8n-ayN","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":1184,"y":759,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":10,"height":25,"seed":73793515,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733561840950,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"","lineHeight":1.25,"baseline":20},{"type":"text","version":47,"versionNonce":1907371762,"isDeleted":false,"id":"2-apK0_FhiX1XbCRUsYxH","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":375.84254291079446,"y":872.5065472869493,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":654.9166870117188,"height":176.2460820869865,"seed":40926464,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148622,"link":null,"locked":false,"fontSize":140.9968656695892,"fontFamily":1,"text":"Resizing??","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"Resizing??","lineHeight":1.25,"baseline":124},{"type":"text","version":41,"versionNonce":1675882798,"isDeleted":false,"id":"3B2P6mhB_qE2j0oJ7zkjD","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":989.9110032614005,"y":1405.0756145309106,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":247.28334045410156,"height":25,"seed":1867241272,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148622,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"Please don’t be rotation","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"Please don’t be rotation","lineHeight":1.25,"baseline":18},{"type":"text","version":26,"versionNonce":829522098,"isDeleted":false,"id":"_n_JzBBVG-8PtChFDKLok","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":823.805597532504,"y":1111.1892606916829,"strokeColor":"#1e1e1e","backgroundColor":"transparent","width":223.89999389648438,"height":25,"seed":1573910112,"groupIds":[],"frameId":null,"roundness":null,"boundElements":[],"updated":1733592148622,"link":null,"locked":false,"fontSize":20,"fontFamily":1,"text":"If I close the other..?","textAlign":"left","verticalAlign":"top","containerId":null,"originalText":"If I close the other..?","lineHeight":1.25,"baseline":18},{"type":"rectangle","version":20,"versionNonce":382379157,"isDeleted":false,"id":"KrQbRGmtkxlJgr4YC1xo6","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"angle":0,"x":291.635902271447,"y":1063.6125964361931,"strokeColor":"#e03131","backgroundColor":"transparent","width":357.539406522013,"height":136.17354507311188,"seed":416921205,"groupIds":[],"frameId":null,"roundness":{"type":3},"boundElements":[],"updated":1733592129345,"link":null,"locked":false}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":[],"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#e03131","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":1,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2024-12-07-0324","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{},"resizingElement":null,"scrolledOutside":false,"scrollX":209.25499560868758,"scrollY":-243.116595680081,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#ffffff","zenModeEnabled":false,"zoom":{"value":0.6477390605393817},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":8,"offsetTop":8,"width":1904,"height":964},"files":{}} \ No newline at end of file diff --git a/server/public/index.html b/server/public/index.html new file mode 100644 index 0000000..56e6e78 --- /dev/null +++ b/server/public/index.html @@ -0,0 +1,21 @@ + + + + Excalidraw in browser + + + + + + + + +
+
+
+ + + diff --git a/server/public/packages/excalidraw/index.js b/server/public/packages/excalidraw/index.js new file mode 100644 index 0000000..be754c7 --- /dev/null +++ b/server/public/packages/excalidraw/index.js @@ -0,0 +1,57 @@ +// public/packages/excalidraw/index.js +// This version fetches initial data from /api/state and saves changes back. +// Make sure your Go server (from previous snippets) is running and serving /api/state. +// Assumes a simple backend that returns/accepts JSON of the Excalidraw file. + +const { useState, useEffect } = React; + +function App() { + const [initialData, setInitialData] = useState(null); + + useEffect(() => { + fetch("/api/state") + .then((res) => { + console.log("Fetch response:", res); + return res.json(); + }) + .then((data) => { + console.log("Fetched data:", data); + setInitialData(data); + }) + .catch((err) => { + console.error("Error loading initial data:", err); + setInitialData({}); + }); + }, []); + + const handleChange = (elements, appState) => { + const data = { + type: "excalidraw", + version: 2, + source: "excalidraw", + elements: elements, + appState: appState, + files: {}, + }; + + fetch("/api/state", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }).catch(console.error); + }; + + return React.createElement( + "div", + { style: { height: "100vh" } }, + initialData && + React.createElement(ExcalidrawLib.Excalidraw, { + initialData: initialData, + onChange: handleChange, + }), + ); +} + +const excalidrawWrapper = document.getElementById("app"); +const root = ReactDOM.createRoot(excalidrawWrapper); +root.render(React.createElement(App));