vertices for arrows

This commit is contained in:
“chrisshank” 2024-10-25 14:03:17 -07:00
parent c0dd007495
commit 65f1e16f5e
5 changed files with 119 additions and 46 deletions

45
demo/arrows.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Arrows</title>
<style>
html {
height: 100%;
}
body {
min-height: 100%;
position: relative;
margin: 0;
}
spatial-geometry {
border: 1px solid black;
border-radius: 3px;
}
spatial-connection {
display: block;
position: absolute;
inset: 0 0 0 0;
}
</style>
</head>
<body>
<spatial-geometry id="box1" x="100" y="100" width="50" height="50"></spatial-geometry>
<spatial-geometry id="box2" x="200" y="300" width="50" height="50">Hello World</spatial-geometry>
<spatial-connection source="#box1" target="#box2"></spatial-connection>
<spatial-connection source="#box1" target="400,100"></spatial-connection>
<script type="module">
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
import { SpatialConnection } from '../src/arrows/spatial-connection.ts';
SpatialGeometry.register();
SpatialConnection.register();
</script>
</body>
</html>

View File

@ -33,6 +33,7 @@ class SpatialThought extends HTMLElement {
handleEvent(event: PointerEvent): void { handleEvent(event: PointerEvent): void {
if (event.type === 'click' && event.target === this.#deleteButton) { if (event.type === 'click' && event.target === this.#deleteButton) {
this.#geometry.remove(); this.#geometry.remove();
document document
.querySelectorAll( .querySelectorAll(
`spatial-connection[source="spatial-geometry[id='${this.#geometry.id}']"], `spatial-connection[source="spatial-geometry[id='${this.#geometry.id}']"],

View File

@ -24,7 +24,7 @@
<li><a href="/maps">Maps</a></li> <li><a href="/maps">Maps</a></li>
<li><a href="/music">Music</a></li> <li><a href="/music">Music</a></li>
<li><a href="/ink">Ink</a></li> <li><a href="/ink">Ink</a></li>
<li><a href="/arrow">Arrow</a></li> <li><a href="/arrows">Arrows</a></li>
<li><a href="/canvasify">Canvasify</a></li> <li><a href="/canvasify">Canvasify</a></li>
<li><a href="/spreadsheet">Spreadsheet</a></li> <li><a href="/spreadsheet">Spreadsheet</a></li>
<li><a href="/chains-of-thought/index.html">Chains of thought</a></li> <li><a href="/chains-of-thought/index.html">Chains of thought</a></li>

View File

@ -2,6 +2,24 @@ import { VisualObserverEntry, VisualObserverManager } from './visual-observer';
const visualObserver = new VisualObserverManager(); const visualObserver = new VisualObserverManager();
interface Vertex {
x: number;
y: number;
}
const vertexRegex = /(?<x>-?([0-9]*[.])?[0-9]+),\s*(?<y>-?([0-9]*[.])?[0-9]+)/;
function parseVertex(str: string): Vertex | null {
const results = vertexRegex.exec(str);
if (results === null) return null;
return {
x: Number(results.groups?.x),
y: Number(results.groups?.y),
};
}
export class AbstractArrow extends HTMLElement { export class AbstractArrow extends HTMLElement {
static tagName = 'abstract-arrow'; static tagName = 'abstract-arrow';
@ -9,8 +27,6 @@ export class AbstractArrow extends HTMLElement {
customElements.define(this.tagName, this); customElements.define(this.tagName, this);
} }
static observedAttributes = ['source', 'target'];
#source = ''; #source = '';
/** A CSS selector for the source of the arrow. */ /** A CSS selector for the source of the arrow. */
get source() { get source() {
@ -18,10 +34,15 @@ export class AbstractArrow extends HTMLElement {
} }
set source(source) { set source(source) {
this.setAttribute('source', source); this.#source = source;
this.observeSource();
} }
#sourceRect!: DOMRectReadOnly; #sourceRect!: DOMRectReadOnly;
get sourceRect() {
return this.#sourceRect;
}
#sourceElement: Element | null = null; #sourceElement: Element | null = null;
get sourceElement() { get sourceElement() {
@ -38,14 +59,17 @@ export class AbstractArrow extends HTMLElement {
get target() { get target() {
return this.#target; return this.#target;
} }
set target(target) { set target(target) {
this.setAttribute('target', target); this.#target = target;
this.observeTarget();
} }
#targetRect!: DOMRectReadOnly; #targetRect!: DOMRectReadOnly;
#targetElement: Element | null = null; get targetRect() {
return this.#targetRect;
}
#targetElement: Element | null = null;
get targetElement() { get targetElement() {
return this.#targetElement; return this.#targetElement;
} }
@ -55,14 +79,9 @@ export class AbstractArrow extends HTMLElement {
this.update(); this.update();
}; };
attributeChangedCallback(name: string, _oldValue: string, newValue: string) { connectedCallback() {
if (name === 'source') { this.source = this.getAttribute('source') || '';
this.#source = newValue; this.target = this.getAttribute('target') || '';
this.observeSource();
} else if (name === 'target') {
this.#target = newValue;
this.observeTarget();
}
} }
disconnectedCallback() { disconnectedCallback() {
@ -70,8 +89,20 @@ export class AbstractArrow extends HTMLElement {
this.unobserveTarget(); this.unobserveTarget();
} }
// TODO: why reparse the vertex?
setSourceVertex(vertex: Vertex) {
this.target = `${vertex.x},${vertex.y}`;
}
observeSource() { observeSource() {
this.unobserveSource(); this.unobserveSource();
const vertex = parseVertex(this.#source);
if (vertex) {
this.#sourceRect = DOMRectReadOnly.fromRect(vertex);
this.update();
} else {
const el = document.querySelector(this.source); const el = document.querySelector(this.source);
if (el === null) { if (el === null) {
@ -81,6 +112,7 @@ export class AbstractArrow extends HTMLElement {
this.#sourceElement = el; this.#sourceElement = el;
visualObserver.observe(this.#sourceElement, this.#sourceCallback); visualObserver.observe(this.#sourceElement, this.#sourceCallback);
} }
}
unobserveSource() { unobserveSource() {
if (this.#sourceElement === null) return; if (this.#sourceElement === null) return;
@ -90,7 +122,14 @@ export class AbstractArrow extends HTMLElement {
observeTarget() { observeTarget() {
this.unobserveTarget(); this.unobserveTarget();
this.#targetElement = document.querySelector(this.target);
const vertex = parseVertex(this.#target);
if (vertex) {
this.#targetRect = DOMRectReadOnly.fromRect(vertex);
this.update();
} else {
this.#targetElement = document.querySelector(this.#target);
if (!this.#targetElement) { if (!this.#targetElement) {
throw new Error('target is not a valid element'); throw new Error('target is not a valid element');
@ -98,6 +137,7 @@ export class AbstractArrow extends HTMLElement {
visualObserver.observe(this.#targetElement, this.#targetCallback); visualObserver.observe(this.#targetElement, this.#targetCallback);
} }
}
unobserveTarget() { unobserveTarget() {
if (this.#targetElement === null) return; if (this.#targetElement === null) return;
@ -106,25 +146,10 @@ export class AbstractArrow extends HTMLElement {
} }
update() { update() {
if ( if (this.#sourceRect === undefined || this.#targetRect === undefined) return;
this.#sourceRect === undefined ||
this.#targetRect === undefined ||
this.#sourceElement === null ||
this.#targetElement === null
)
return;
this.render(this.#sourceRect, this.#targetRect, this.#sourceElement, this.#targetElement); this.render();
} }
render( render() {}
// @ts-ignore
sourceRect: DOMRectReadOnly,
// @ts-ignore
targetRect: DOMRectReadOnly,
// @ts-ignore
sourceElement: Element,
// @ts-ignore
targetElement: Element
) {}
} }

View File

@ -53,8 +53,10 @@ export class SpatialConnection extends AbstractArrow {
}, },
}; };
render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) { render() {
const [sx, sy, cx, cy, ex, ey, ae] = getBoxToBoxArrow( const { sourceRect, targetRect } = this;
const [sx, sy, cx, cy, ex, ey] = getBoxToBoxArrow(
sourceRect.x, sourceRect.x,
sourceRect.y, sourceRect.y,
sourceRect.width, sourceRect.width,