physics go brrrrrr
This commit is contained in:
parent
d2f3171069
commit
366cba1ec1
|
|
@ -15,8 +15,7 @@
|
|||
"@dimforge/rapier2d": "latest",
|
||||
"@tldraw/tldraw": "2.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^2.0.4"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.4.1",
|
||||
|
|
@ -25,7 +24,6 @@
|
|||
"@types/react-dom": "^18.2.7",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"concurrently": "^8.2.0",
|
||||
"gh-pages": "^6.1.1",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-top-level-await": "^1.3.1",
|
||||
|
|
|
|||
199
src/App.tsx
199
src/App.tsx
|
|
@ -1,35 +1,16 @@
|
|||
import { createShapeId, Tldraw, TLGeoShape, TLShape, TLUiComponents } from "@tldraw/tldraw";
|
||||
import { createShapeId, TLUiComponents } from "@tldraw/tldraw";
|
||||
import "@tldraw/tldraw/tldraw.css";
|
||||
import "./css/style.css"
|
||||
import { SimControls } from "./physics/ui/PhysicsControls";
|
||||
import { uiOverrides } from "./physics/ui/overrides";
|
||||
import { Helmet, HelmetProvider } from "react-helmet-async";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { HTMLShapeUtil, HTMLShape } from "./HTMLShapeUtil";
|
||||
import { HTMLShape } from "./ts/shapes/HTMLShapeUtil";
|
||||
import { Default } from "./ts/components/Default";
|
||||
import { Canvas } from "./ts/components/Canvas";
|
||||
import { Toggle } from "./ts/components/Toggle";
|
||||
import { gatherElementsInfo } from "./utils";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
||||
const components: TLUiComponents = {
|
||||
HelpMenu: null,
|
||||
StylePanel: null,
|
||||
PageMenu: null,
|
||||
NavigationPanel: null,
|
||||
DebugMenu: null,
|
||||
ContextMenu: null,
|
||||
ActionsMenu: null,
|
||||
QuickActions: null,
|
||||
MainMenu: null,
|
||||
MenuPanel: null,
|
||||
// ZoomMenu: null,
|
||||
// Minimap: null,
|
||||
// Toolbar: null,
|
||||
// KeyboardShortcutsDialog: null,
|
||||
// HelperButtons: null,
|
||||
// SharePanel: null,
|
||||
// TopPanel: null,
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [isPhysicsEnabled, setIsPhysicsEnabled] = useState(false);
|
||||
const [elementsInfo, setElementsInfo] = useState<any[]>([]);
|
||||
|
|
@ -57,31 +38,7 @@ function App() {
|
|||
};
|
||||
}, [isPhysicsEnabled]);
|
||||
|
||||
// Function to gather elements info asynchronously
|
||||
async function gatherElementsInfo() {
|
||||
const rootElement = document.getElementsByTagName('main')[0];
|
||||
const info: any[] = [];
|
||||
if (rootElement) {
|
||||
for (const child of rootElement.children) {
|
||||
if (['BUTTON'].includes(child.tagName)) continue
|
||||
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,
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
w: w,
|
||||
h: rect.height,
|
||||
html: child.outerHTML
|
||||
});
|
||||
};
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
const shapes: HTMLShape[] = elementsInfo.map((element) => ({
|
||||
id: createShapeId(),
|
||||
|
|
@ -97,157 +54,29 @@ function App() {
|
|||
|
||||
shapes.push({
|
||||
id: createShapeId(),
|
||||
type: 'html',
|
||||
type: 'geo',
|
||||
x: 0,
|
||||
y: window.innerHeight,
|
||||
props: {
|
||||
w: window.innerWidth,
|
||||
h: 20,
|
||||
html: "FOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
|
||||
h: 50,
|
||||
color: 'grey',
|
||||
fill: 'solid'
|
||||
},
|
||||
meta: {
|
||||
fixed: true
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<HelmetProvider>
|
||||
<Toggle />
|
||||
<div style={{ zIndex: 999999 }} className={`default-component ${fadeClass}`}>
|
||||
{<Default />}
|
||||
</div>
|
||||
{isPhysicsEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}
|
||||
</HelmetProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
};
|
||||
|
||||
function Default() {
|
||||
return (
|
||||
<main>
|
||||
{/* <Helmet>
|
||||
<link rel="stylesheet" href="src/css/default.css" />
|
||||
</Helmet> */}
|
||||
<header>
|
||||
Orion Reed
|
||||
</header>
|
||||
<h1>Hello! 👋</h1>
|
||||
<p>
|
||||
My research investigates the intersection of computing, human-system
|
||||
interfaces, and emancipatory politics. I am interested in the
|
||||
potential of computing as a medium for thought, as a tool for
|
||||
collective action, and as a means of emancipation.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
My current focus is basic research into the nature of digital
|
||||
organisation, developing theoretical toolkits to improve shared
|
||||
infrastructure, and applying this research to the design of new
|
||||
systems and protocols which support the self-organisation of knowledge
|
||||
and computational artifacts.
|
||||
</p>
|
||||
|
||||
<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
|
||||
<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>
|
||||
|
||||
<h1>Get in touch</h1>
|
||||
<p>
|
||||
I am on Twitter as <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a> and on
|
||||
Mastodon as <a href="https://hci.social/@orion">@orion@hci.social</a>. The best way to reach me is
|
||||
through Twitter or my email, <a href="mailto:me@orionreed.com">me@orionreed.com</a>
|
||||
</p>
|
||||
|
||||
<span className="dinkus">***</span>
|
||||
|
||||
<h1>Talks</h1>
|
||||
<ul>
|
||||
<li><a
|
||||
href="objects/causal-islands-integration-domain.pdf">Spatial
|
||||
Canvases: Towards an Integration Domain for HCI @ Causal Islands LA</a></li>
|
||||
<li><a
|
||||
href="https://www.youtube.com/watch?v=-q-kk-NMFbA">Knowledge Organisation Infrastructure Demo @ NPC
|
||||
Denver</a></li>
|
||||
</ul>
|
||||
<h1>Writing</h1>
|
||||
<ul>
|
||||
<li><a
|
||||
href="https://blog.block.science/objects-as-reference-toward-robust-first-principles-of-digital-organization/">Objects
|
||||
as Reference: Toward Robust First Principles of Digital Organization</a></li>
|
||||
</ul>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function Canvas({ shapes }: { shapes: TLShape[] }) {
|
||||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
{/* <Helmet>
|
||||
<link rel="stylesheet" href="src/css/tldraw.css" />
|
||||
</Helmet> */}
|
||||
<Tldraw
|
||||
overrides={uiOverrides}
|
||||
components={components}
|
||||
shapeUtils={[HTMLShapeUtil]}
|
||||
>
|
||||
<SimControls shapes={shapes} />
|
||||
</Tldraw>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Toggle() {
|
||||
return (
|
||||
<>
|
||||
{/* <Helmet>
|
||||
<link rel="stylesheet" href="src/css/toggle.css" />
|
||||
</Helmet> */}
|
||||
<button id="toggle-physics" onClick={() => window.dispatchEvent(new CustomEvent('togglePhysicsEvent'))}>
|
||||
<img src="src/assets/gravity.svg" alt="Toggle Physics" />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ ul {
|
|||
|
||||
.fade-out {
|
||||
opacity: 0 !important;
|
||||
transition: opacity 1s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
/* visibility: hidden; */
|
||||
/* display: none; */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import {
|
||||
TLUiEventSource,
|
||||
TLUiOverrides,
|
||||
TLUiTranslationKey,
|
||||
} from "@tldraw/tldraw";
|
||||
|
||||
// In order to see select our custom shape tool, we need to add it to the ui.
|
||||
export const uiOverrides: TLUiOverrides = {
|
||||
actions(_editor, actions) {
|
||||
actions['toggle-physics'] = {
|
||||
id: 'toggle-physics',
|
||||
label: 'Toggle Physics' as TLUiTranslationKey,
|
||||
readonlyOk: true,
|
||||
kbd: 'p',
|
||||
onSelect(_source: TLUiEventSource) {
|
||||
const event = new CustomEvent('togglePhysicsEvent');
|
||||
window.dispatchEvent(event);
|
||||
},
|
||||
}
|
||||
return actions
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { Tldraw, TLShape, TLUiComponents } from "@tldraw/tldraw";
|
||||
import { SimController } from "../physics/PhysicsControls";
|
||||
import { HTMLShapeUtil } from "../shapes/HTMLShapeUtil";
|
||||
|
||||
const components: TLUiComponents = {
|
||||
HelpMenu: null,
|
||||
StylePanel: null,
|
||||
PageMenu: null,
|
||||
NavigationPanel: null,
|
||||
DebugMenu: null,
|
||||
ContextMenu: null,
|
||||
ActionsMenu: null,
|
||||
QuickActions: null,
|
||||
MainMenu: null,
|
||||
MenuPanel: null,
|
||||
// ZoomMenu: null,
|
||||
// Minimap: null,
|
||||
// Toolbar: null,
|
||||
// KeyboardShortcutsDialog: null,
|
||||
// HelperButtons: null,
|
||||
// SharePanel: null,
|
||||
// TopPanel: null,
|
||||
}
|
||||
|
||||
export function Canvas({ shapes }: { shapes: TLShape[]; }) {
|
||||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
components={components}
|
||||
shapeUtils={[HTMLShapeUtil]}
|
||||
>
|
||||
<SimController shapes={shapes} />
|
||||
</Tldraw>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
|
||||
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>
|
||||
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import React from "react";
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<main>
|
||||
<header>
|
||||
Orion Reed
|
||||
</header>
|
||||
<h1>Hello! 👋</h1>
|
||||
<p>
|
||||
My research investigates the intersection of computing, human-system
|
||||
interfaces, and emancipatory politics. I am interested in the
|
||||
potential of computing as a medium for thought, as a tool for
|
||||
collective action, and as a means of emancipation.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
My current focus is basic research into the nature of digital
|
||||
organisation, developing theoretical toolkits to improve shared
|
||||
infrastructure, and applying this research to the design of new
|
||||
systems and protocols which support the self-organisation of knowledge
|
||||
and computational artifacts.
|
||||
</p>
|
||||
|
||||
<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
|
||||
<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>
|
||||
|
||||
<h1>Get in touch</h1>
|
||||
<p>
|
||||
I am on Twitter as <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a> and on
|
||||
Mastodon as <a href="https://hci.social/@orion">@orion@hci.social</a>. The best way to reach me is
|
||||
through Twitter or my email, <a href="mailto:me@orionreed.com">me@orionreed.com</a>
|
||||
</p>
|
||||
|
||||
<span className="dinkus">***</span>
|
||||
|
||||
<h1>Talks</h1>
|
||||
<ul>
|
||||
<li><a
|
||||
href="objects/causal-islands-integration-domain.pdf">Spatial
|
||||
Canvases: Towards an Integration Domain for HCI @ Causal Islands LA</a></li>
|
||||
<li><a
|
||||
href="https://www.youtube.com/watch?v=-q-kk-NMFbA">Knowledge Organisation Infrastructure Demo @ NPC
|
||||
Denver</a></li>
|
||||
</ul>
|
||||
<h1>Writing</h1>
|
||||
<ul>
|
||||
<li><a
|
||||
href="https://blog.block.science/objects-as-reference-toward-robust-first-principles-of-digital-organization/">Objects
|
||||
as Reference: Toward Robust First Principles of Digital Organization</a></li>
|
||||
</ul>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import React from "react";
|
||||
|
||||
export function Toggle() {
|
||||
return (
|
||||
<>
|
||||
<button id="toggle-physics" onClick={() => window.dispatchEvent(new CustomEvent('togglePhysicsEvent'))}>
|
||||
<img src="src/assets/gravity.svg" alt="Toggle Physics" />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { TLUnknownShape, useEditor } from "@tldraw/tldraw";
|
||||
import { useEffect } from "react";
|
||||
import { usePhysicsSimulation } from "../simulation";
|
||||
import { usePhysicsSimulation } from "./simulation";
|
||||
|
||||
export const SimControls = ({ shapes }: { shapes: TLUnknownShape[] }) => {
|
||||
export const SimController = ({ shapes }: { shapes: TLUnknownShape[] }) => {
|
||||
const editor = useEditor();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -9,6 +9,8 @@ type BodyWithShapeData = RAPIER.RigidBody & {
|
|||
};
|
||||
type RigidbodyLookup = { [key: TLShapeId]: RAPIER.RigidBody };
|
||||
|
||||
const START_DELAY = 1500;
|
||||
|
||||
export class PhysicsWorld {
|
||||
private editor: Editor;
|
||||
private world: RAPIER.World;
|
||||
|
|
@ -28,6 +30,7 @@ export class PhysicsWorld {
|
|||
public start() {
|
||||
this.world = new RAPIER.World(GRAVITY);
|
||||
|
||||
// setTimeout(() => {
|
||||
this.addShapes(this.editor.getCurrentPageShapes());
|
||||
|
||||
const simLoop = () => {
|
||||
|
|
@ -38,6 +41,7 @@ export class PhysicsWorld {
|
|||
};
|
||||
simLoop();
|
||||
return () => cancelAnimationFrame(this.animFrame);
|
||||
// }, START_DELAY);
|
||||
};
|
||||
|
||||
public stop() {
|
||||
|
|
@ -55,6 +59,7 @@ export class PhysicsWorld {
|
|||
}
|
||||
|
||||
switch (shape.type) {
|
||||
case "html":
|
||||
case "geo":
|
||||
case "image":
|
||||
case "video":
|
||||
|
|
@ -72,12 +77,12 @@ export class PhysicsWorld {
|
|||
}
|
||||
|
||||
createShape(shape: TLGeoShape | TLDrawShape) {
|
||||
if (shape.props.dash === "dashed") return; // Skip dashed shapes
|
||||
if (isRigidbody(shape.props.color)) {
|
||||
const gravity = getGravityFromColor(shape.props.color)
|
||||
const rb = this.createRigidbody(shape, gravity);
|
||||
console.log('creating shape');
|
||||
if (!shape.meta.fixed) {
|
||||
const rb = this.createRigidbody(shape, 1);
|
||||
this.createCollider(shape, rb);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.createCollider(shape);
|
||||
}
|
||||
}
|
||||
|
|
@ -379,7 +384,6 @@ export function usePhysicsSimulation(editor: Editor, enabled: boolean) {
|
|||
useEffect(() => {
|
||||
if (enabled) {
|
||||
sim.current.start();
|
||||
editor.selectNone();
|
||||
return () => sim.current.stop();
|
||||
}
|
||||
}, [enabled, sim]);
|
||||
|
|
@ -23,7 +23,7 @@ export class HTMLShapeUtil extends ShapeUtil<HTMLShape> {
|
|||
}
|
||||
|
||||
component(shape: HTMLShape) {
|
||||
return <div dangerouslySetInnerHTML={{ __html: shape.props.html }} style={{ margin: 0 }} ></div>
|
||||
return <div dangerouslySetInnerHTML={{ __html: shape.props.html }}></div>
|
||||
}
|
||||
|
||||
indicator(shape: HTMLShape) {
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
export function measureElementTextWidth(element: HTMLElement) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// Function to gather elements info asynchronously
|
||||
export async function gatherElementsInfo() {
|
||||
const rootElement = document.getElementsByTagName('main')[0];
|
||||
const info: any[] = [];
|
||||
if (rootElement) {
|
||||
for (const child of rootElement.children) {
|
||||
if (['BUTTON'].includes(child.tagName)) continue;
|
||||
const rect = child.getBoundingClientRect();
|
||||
let w = rect.width;
|
||||
if (!['P', 'UL'].includes(child.tagName)) {
|
||||
w = measureElementTextWidth(child);
|
||||
}
|
||||
// Check if the element is centered
|
||||
const computedStyle = window.getComputedStyle(child);
|
||||
let x = rect.left; // Default x position
|
||||
if (computedStyle.display === 'block' && computedStyle.textAlign === 'center') {
|
||||
// Adjust x position for centered elements
|
||||
const parentWidth = child.parentElement ? child.parentElement.getBoundingClientRect().width : 0;
|
||||
x = (parentWidth - w) / 2 + window.scrollX + (child.parentElement ? child.parentElement.getBoundingClientRect().left : 0);
|
||||
}
|
||||
|
||||
info.push({
|
||||
tagName: child.tagName,
|
||||
x: x,
|
||||
y: rect.top,
|
||||
w: w,
|
||||
h: rect.height,
|
||||
html: child.outerHTML
|
||||
});
|
||||
};
|
||||
}
|
||||
return info;
|
||||
}
|
||||
Loading…
Reference in New Issue