translate DOM to shapes

This commit is contained in:
Orion Reed 2024-03-24 22:10:30 -07:00
parent 52383179c1
commit 9c50ec8d66
7 changed files with 159 additions and 70 deletions

View File

@ -12,7 +12,8 @@
href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap"
rel="stylesheet">
</head>
<body id="root">
<body>
<div id="root"></div>
<script type="module" src="/src/App.tsx"></script>
</body>
</html>

View File

@ -14,10 +14,9 @@
"dependencies": {
"@dimforge/rapier2d": "latest",
"@tldraw/tldraw": "2.0.2",
"@types/react-helmet": "^6.1.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0"
"react-helmet-async": "^2.0.4"
},
"devDependencies": {
"@biomejs/biome": "1.4.1",
@ -32,4 +31,4 @@
"vite-plugin-top-level-await": "^1.3.1",
"vite-plugin-wasm": "^3.2.2"
}
}
}

View File

@ -1,30 +1,119 @@
import { createTLUser, setUserPreferences, Tldraw, track, useEditor } from "@tldraw/tldraw";
import { atom, createShapeId, createTLUser, setUserPreferences, StoreSnapshot, Tldraw, TLGeoShape, TLInstance, TLRecord, TLShape, TLUiComponents, TLUnknownShape, TLUserPreferences, track, useEditor } from "@tldraw/tldraw";
import "@tldraw/tldraw/tldraw.css";
import { SimControls } from "./physics/ui/PhysicsControls";
import { uiOverrides } from "./physics/ui/overrides";
import { Helmet } from "react-helmet";
import React, { useEffect, useState } from "react";
import { Helmet, HelmetProvider } from "react-helmet-async";
import React, { Suspense, useEffect, useState } from "react";
import ReactDOM from "react-dom/client";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
const components: TLUiComponents = {
HelpMenu: null,
StylePanel: null,
PageMenu: null,
NavigationPanel: null,
DebugMenu: null,
MenuPanel: null,
// ContextMenu: null,
// ActionsMenu: null,
// ZoomMenu: null,
// MainMenu: null,
// Minimap: null,
// Toolbar: null,
// KeyboardShortcutsDialog: null,
// QuickActions: null,
// HelperButtons: null,
// SharePanel: null,
// TopPanel: null,
}
function App() {
const [isPhysicsEnabled, setIsPhysicsEnabled] = useState(false);
const [elementsInfo, setElementsInfo] = useState<any[]>([]);
useEffect(() => {
const togglePhysics = () => setIsPhysicsEnabled(prev => !prev);
const togglePhysics = async () => {
if (!isPhysicsEnabled) {
const info = await gatherElementsInfo();
setElementsInfo(info);
setIsPhysicsEnabled(true); // Enable physics only after gathering info
} else {
setIsPhysicsEnabled(false);
setElementsInfo([]); // Reset elements info when disabling physics
}
};
window.addEventListener('togglePhysicsEvent', togglePhysics);
return () => {
window.removeEventListener('togglePhysicsEvent', togglePhysics);
};
}, []);
}, [isPhysicsEnabled]);
// Function to gather elements info asynchronously
async function gatherElementsInfo() {
const rootElement = document.getElementById('root');
const info: any[] = [];
if (rootElement) {
for (const child of rootElement.children) {
const rect = child.getBoundingClientRect();
let w = rect.width
if (!['P', 'UL'].includes(child.tagName)) {
w = measureElementTextWidth(child);
}
console.log(w)
info.push({
tagName: child.tagName,
position: { x: rect.left, y: rect.top },
dimensions: { width: w, height: rect.height },
});
};
}
// Example usage
// const element = document.getElementById('yourElementId'); // Replace 'yourElementId' with the actual ID
// if (element) {
// console.log(`Text width: ${textWidth}px`);
// }
// console.log(info.length);
// console.log(info);
return info;
}
const shapes: TLGeoShape[] = elementsInfo.map((element, index) => ({
id: createShapeId(),
type: 'geo',
x: element.position.x,
y: element.position.y,
props: {
geo: "rectangle",
w: element.dimensions.width,
h: element.dimensions.height,
fill: 'solid',
color: 'green'
}
}))
shapes.push({
id: createShapeId(),
type: 'geo',
x: 0,
y: window.innerHeight,
props: {
geo: "rectangle",
w: window.innerWidth,
h: 20,
fill: 'solid'
}
})
return (
<React.StrictMode>
<Toggle />
{isPhysicsEnabled ? <Canvas /> : <Default />}
<HelmetProvider>
<Toggle />
{isPhysicsEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : <Default />}
</HelmetProvider>
</React.StrictMode>
);
};
@ -56,10 +145,8 @@ function Default() {
<h1>My work</h1>
<p>
Alongside my independent work I am a researcher at
<a href="https://block.science/">Block Science</a> building
<i>knowledge organisation infrastructure</i> and at
<a href="https://economicspace.agency/">ECSA</a> working on
Alongside my independent work I am a researcher at <a href="https://block.science/">Block Science</a> building
<i>knowledge organisation infrastructure</i> and at <a href="https://economicspace.agency/">ECSA</a> working on
<i>computational media</i>. I am also part of the nascent <a href="https://libcomp.org/">Liberatory Computing</a>
collective and a co-organiser of the <a href="https://canvasprotocol.org/">OCWG</a>.
</p>
@ -92,7 +179,7 @@ function Default() {
);
}
function Canvas() {
function Canvas({ shapes }: { shapes: TLShape[] }) {
return (
<div className="tldraw__editor">
@ -101,13 +188,9 @@ function Canvas() {
</Helmet>
<Tldraw
overrides={uiOverrides}
// user={createTLUser(opts={id: 'orion', isDarkMode: true})}
components={components}
>
{/* <SimControls /> */}
{/* {()=> {
setUserPreferences({id: 'orion', isDarkMode: true })
}} */}
<Toggle />
<SimControls shapes={shapes} />
</Tldraw>
</div>
);
@ -126,3 +209,42 @@ function Toggle() {
);
}
function Contact() {
return (
<div>
<h1>Contact</h1>
<p>Twitter: <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a></p>
<p>Mastodon: <a href="https://hci.social/@orion">orion@hci.social</a></p>
<p>Email: <a href="mailto:me@orionreed.com">me@orionreed.com</a></p>
<p>GitHub: <a href="https://github.com/orionreed">OrionReed</a></p>
</div>
)
}
function measureElementTextWidth(element: Element) {
// Create a temporary span element
const tempElement = document.createElement('span');
// Get the text content from the passed element
tempElement.textContent = element.textContent || element.innerText;
// Get the computed style of the passed element
const computedStyle = window.getComputedStyle(element);
// Apply relevant styles to the temporary element
tempElement.style.font = computedStyle.font;
tempElement.style.fontWeight = computedStyle.fontWeight;
tempElement.style.fontSize = computedStyle.fontSize;
tempElement.style.fontFamily = computedStyle.fontFamily;
tempElement.style.letterSpacing = computedStyle.letterSpacing;
// Ensure the temporary element is not visible in the viewport
tempElement.style.position = 'absolute';
tempElement.style.visibility = 'hidden';
tempElement.style.whiteSpace = 'nowrap'; // Prevent text from wrapping
// Append to the body to make measurements possible
document.body.appendChild(tempElement);
// Measure the width
const width = tempElement.getBoundingClientRect().width;
// Remove the temporary element from the document
document.body.removeChild(tempElement);
// Return the measured width
return width === 0 ? 10 : width;
}

View File

@ -1,7 +0,0 @@
<div>
<h1>Contact</h1>
<p>Twitter: <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a></p>
<p>Mastodon: <a href="https://hci.social/@orion">orion@hci.social</a></p>
<p>Email: <a href="mailto:me@orionreed.com">me@orionreed.com</a></p>
<p>GitHub: <a href="https://github.com/orionreed">OrionReed</a></p>
</div>

View File

@ -26,5 +26,9 @@ html,
}
.tl-background {
background-color: transparent !important;
background-color: transparent;
}
.tlui-debug-panel {
display: none;
}

View File

@ -28,7 +28,7 @@ export class PhysicsWorld {
public start() {
this.world = new RAPIER.World(GRAVITY);
this.addShapes(this.editor.getSelectedShapes());
this.addShapes(this.editor.getCurrentPageShapes());
const simLoop = () => {
this.world.step();

View File

@ -1,47 +1,17 @@
import { track, useEditor } from "@tldraw/tldraw";
import { useEffect, useState } from "react";
import { TLUnknownShape, useEditor } from "@tldraw/tldraw";
import { useEffect } from "react";
import { usePhysicsSimulation } from "../simulation";
// import "../../css/physics-ui.css";
export const SimControls = track(() => {
export const SimControls = ({ shapes }: { shapes: TLUnknownShape[] }) => {
const editor = useEditor();
const [physicsEnabled, setPhysics] = useState(false);
useEffect(() => {
const togglePhysics = () => {
setPhysics(prev => !prev);
};
window.addEventListener('togglePhysicsEvent', togglePhysics);
return () => {
window.removeEventListener('togglePhysicsEvent', togglePhysics);
};
editor.createShapes(shapes)
return () => { editor.deleteShapes(editor.getCurrentPageShapes()) }
}, []);
const { addShapes } = usePhysicsSimulation(editor, physicsEnabled);
return (
<div className="custom-layout">
<div className="custom-toolbar">
<button
type="button"
className="custom-button"
data-isactive={physicsEnabled}
title="Toggle physics (P)"
onClick={() => setPhysics(!physicsEnabled)}
>
Physics
</button>
<button
type="button"
className="custom-button"
title="Add to physics simulation"
onClick={() => addShapes(editor.getSelectedShapes())}
>
+
</button>
</div>
</div>
);
});
const { addShapes } = usePhysicsSimulation(editor, true);
return (<></>);
};