some fixes
This commit is contained in:
parent
a9e29ae196
commit
e595cc1135
|
|
@ -15,7 +15,8 @@
|
||||||
"@dimforge/rapier2d": "latest",
|
"@dimforge/rapier2d": "latest",
|
||||||
"@tldraw/tldraw": "2.0.2",
|
"@tldraw/tldraw": "2.0.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.22.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.4.1",
|
"@biomejs/biome": "1.4.1",
|
||||||
|
|
@ -23,10 +24,9 @@
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
"concurrently": "^8.2.0",
|
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.4.5",
|
"vite": "^4.4.5",
|
||||||
"vite-plugin-top-level-await": "^1.3.1",
|
"vite-plugin-top-level-await": "^1.3.1",
|
||||||
"vite-plugin-wasm": "^3.2.2"
|
"vite-plugin-wasm": "^3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
src/App.tsx
81
src/App.tsx
|
|
@ -7,76 +7,35 @@ import { HTMLShape } from "./ts/shapes/HTMLShapeUtil";
|
||||||
import { Default } from "./ts/components/Default";
|
import { Default } from "./ts/components/Default";
|
||||||
import { Canvas } from "./ts/components/Canvas";
|
import { Canvas } from "./ts/components/Canvas";
|
||||||
import { Toggle } from "./ts/components/Toggle";
|
import { Toggle } from "./ts/components/Toggle";
|
||||||
import { gatherElementsInfo } from "./utils";
|
import { usePhysics } from "./ts/hooks/usePhysics.ts"
|
||||||
|
import { createShapes } from "./utils";
|
||||||
|
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||||
|
import { Contact } from "./ts/components/Contact.tsx";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isPhysicsEnabled, setIsPhysicsEnabled] = useState(false);
|
|
||||||
const [elementsInfo, setElementsInfo] = useState<any[]>([]);
|
|
||||||
const [fadeClass, setFadeClass] = useState(''); // State to control the fade class
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const togglePhysics = async () => {
|
|
||||||
if (!isPhysicsEnabled) {
|
|
||||||
const info = await gatherElementsInfo();
|
|
||||||
setElementsInfo(info);
|
|
||||||
setIsPhysicsEnabled(true); // Enable physics only after gathering info
|
|
||||||
setFadeClass('fade-out'); // Start fading out the Default component
|
|
||||||
// setTimeout(() => setFadeClass('fade-in'), 500); // Wait for fade-out to complete before fading in Canvas
|
|
||||||
} else {
|
|
||||||
setIsPhysicsEnabled(false);
|
|
||||||
setElementsInfo([]); // Reset elements info when disabling physics
|
|
||||||
setFadeClass(''); // Reset fade class to show Default component normally
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('togglePhysicsEvent', togglePhysics);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('togglePhysicsEvent', togglePhysics);
|
|
||||||
};
|
|
||||||
}, [isPhysicsEnabled]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const shapes: HTMLShape[] = elementsInfo.map((element) => ({
|
|
||||||
id: createShapeId(),
|
|
||||||
type: 'html',
|
|
||||||
x: element.x,
|
|
||||||
y: element.y,
|
|
||||||
props: {
|
|
||||||
w: element.w,
|
|
||||||
h: element.h,
|
|
||||||
html: element.html,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
shapes.push({
|
|
||||||
id: createShapeId(),
|
|
||||||
type: 'geo',
|
|
||||||
x: 0,
|
|
||||||
y: window.innerHeight,
|
|
||||||
props: {
|
|
||||||
w: window.innerWidth,
|
|
||||||
h: 50,
|
|
||||||
color: 'grey',
|
|
||||||
fill: 'solid'
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
fixed: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Toggle />
|
<BrowserRouter>
|
||||||
<div style={{ zIndex: 999999 }} className={`default-component ${fadeClass}`}>
|
<Routes>
|
||||||
{<Default />}
|
<Route path="/" element={<Home />} />
|
||||||
</div>
|
<Route path="/card/contact" element={<Contact />} />
|
||||||
{isPhysicsEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const { isPhysicsEnabled, elementsInfo, fadeClass } = usePhysics();
|
||||||
|
const shapes = createShapes(elementsInfo)
|
||||||
|
return (
|
||||||
|
<><Toggle />
|
||||||
|
<div style={{ zIndex: 999999 }} className={`default-component ${fadeClass}`}>
|
||||||
|
{<Default />}
|
||||||
|
</div>
|
||||||
|
{isPhysicsEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}</>)
|
||||||
|
}
|
||||||
|
|
@ -64,6 +64,14 @@ p {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
font-variation-settings: "wght" 350;
|
font-variation-settings: "wght" 350;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dinkus {
|
.dinkus {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import React from "react";
|
export function Contact() {
|
||||||
|
|
||||||
function Contact() {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<main>
|
||||||
|
<header>
|
||||||
|
<a href="/">
|
||||||
|
Orion Reed
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
<h1>Contact</h1>
|
<h1>Contact</h1>
|
||||||
<p>Twitter: <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a></p>
|
<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>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>Email: <a href="mailto:me@orionreed.com">me@orionreed.com</a></p>
|
||||||
<p>GitHub: <a href="https://github.com/orionreed">OrionReed</a></p>
|
<p>GitHub: <a href="https://github.com/orionreed">OrionReed</a></p>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export function Default() {
|
export function Default() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -24,9 +22,9 @@ export function Default() {
|
||||||
|
|
||||||
<h1>My work</h1>
|
<h1>My work</h1>
|
||||||
<p>
|
<p>
|
||||||
Alongside my independent work I am a researcher at <a href="https://block.science/">Block Science</a> building
|
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>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>
|
<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>.
|
collective and a co-organiser of the <a href="https://canvasprotocol.org/">OCWG</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -42,7 +40,7 @@ export function Default() {
|
||||||
<h1>Talks</h1>
|
<h1>Talks</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a
|
<li><a
|
||||||
href="objects/causal-islands-integration-domain.pdf">Spatial
|
href="artifact/causal-islands-integration-domain.pdf">Spatial
|
||||||
Canvases: Towards an Integration Domain for HCI @ Causal Islands LA</a></li>
|
Canvases: Towards an Integration Domain for HCI @ Causal Islands LA</a></li>
|
||||||
<li><a
|
<li><a
|
||||||
href="https://www.youtube.com/watch?v=-q-kk-NMFbA">Knowledge Organisation Infrastructure Demo @ NPC
|
href="https://www.youtube.com/watch?v=-q-kk-NMFbA">Knowledge Organisation Infrastructure Demo @ NPC
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export function Toggle() {
|
export function Toggle() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface ElementInfo {
|
||||||
|
tagName: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
html: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePhysics() {
|
||||||
|
const [isPhysicsEnabled, setIsPhysicsEnabled] = useState(false);
|
||||||
|
const [elementsInfo, setElementsInfo] = useState<ElementInfo[]>([]);
|
||||||
|
const [fadeClass, setFadeClass] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const togglePhysics = async () => {
|
||||||
|
if (!isPhysicsEnabled) {
|
||||||
|
const info = await gatherElementsInfo();
|
||||||
|
setElementsInfo(info);
|
||||||
|
setIsPhysicsEnabled(true);
|
||||||
|
setFadeClass('fade-out');
|
||||||
|
} else {
|
||||||
|
setIsPhysicsEnabled(false);
|
||||||
|
setElementsInfo([]);
|
||||||
|
setFadeClass('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('togglePhysicsEvent', togglePhysics);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('togglePhysicsEvent', togglePhysics);
|
||||||
|
};
|
||||||
|
}, [isPhysicsEnabled]);
|
||||||
|
|
||||||
|
return { isPhysicsEnabled, elementsInfo, fadeClass };
|
||||||
|
}
|
||||||
|
|
||||||
|
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 as HTMLElement);
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export class HTMLShapeUtil extends ShapeUtil<HTMLShape> {
|
||||||
return resizeBox(shape, info)
|
return resizeBox(shape, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
getGeometry(shape: IHTMLShape) {
|
getGeometry(shape: HTMLShape) {
|
||||||
return new Rectangle2d({
|
return new Rectangle2d({
|
||||||
width: shape.props.w,
|
width: shape.props.w,
|
||||||
height: shape.props.h,
|
height: shape.props.h,
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,33 @@
|
||||||
export function measureElementTextWidth(element: HTMLElement) {
|
import { createShapeId } from "@tldraw/tldraw";
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function createShapes(elementsInfo: any) {
|
||||||
|
const shapes = elementsInfo.map((element: any) => ({
|
||||||
|
id: createShapeId(),
|
||||||
|
type: 'html',
|
||||||
|
x: element.x,
|
||||||
|
y: element.y,
|
||||||
|
props: {
|
||||||
|
w: element.w,
|
||||||
|
h: element.h,
|
||||||
|
html: element.html,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// Function to gather elements info asynchronously
|
shapes.push({
|
||||||
export async function gatherElementsInfo() {
|
id: createShapeId(),
|
||||||
const rootElement = document.getElementsByTagName('main')[0];
|
type: 'geo',
|
||||||
const info: any[] = [];
|
x: 0,
|
||||||
if (rootElement) {
|
y: window.innerHeight,
|
||||||
for (const child of rootElement.children) {
|
props: {
|
||||||
if (['BUTTON'].includes(child.tagName)) continue;
|
w: window.innerWidth,
|
||||||
const rect = child.getBoundingClientRect();
|
h: 50,
|
||||||
let w = rect.width;
|
color: 'grey',
|
||||||
if (!['P', 'UL'].includes(child.tagName)) {
|
fill: 'solid'
|
||||||
w = measureElementTextWidth(child);
|
},
|
||||||
}
|
meta: {
|
||||||
// Check if the element is centered
|
fixed: true
|
||||||
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({
|
return shapes;
|
||||||
tagName: child.tagName,
|
}
|
||||||
x: x,
|
|
||||||
y: rect.top,
|
|
||||||
w: w,
|
|
||||||
h: rect.height,
|
|
||||||
html: child.outerHTML
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue