translate DOM to shapes
This commit is contained in:
parent
52383179c1
commit
9c50ec8d66
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
158
src/App.tsx
158
src/App.tsx
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -26,5 +26,9 @@ html,
|
|||
}
|
||||
|
||||
.tl-background {
|
||||
background-color: transparent !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.tlui-debug-panel {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 (<></>);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue