vertices for arrows
This commit is contained in:
parent
c0dd007495
commit
65f1e16f5e
|
|
@ -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>
|
||||||
|
|
@ -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}']"],
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,16 +89,29 @@ 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 el = document.querySelector(this.source);
|
|
||||||
|
|
||||||
if (el === null) {
|
const vertex = parseVertex(this.#source);
|
||||||
throw new Error('source is not a valid element');
|
|
||||||
|
if (vertex) {
|
||||||
|
this.#sourceRect = DOMRectReadOnly.fromRect(vertex);
|
||||||
|
this.update();
|
||||||
|
} else {
|
||||||
|
const el = document.querySelector(this.source);
|
||||||
|
|
||||||
|
if (el === null) {
|
||||||
|
throw new Error('source is not a valid element');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#sourceElement = el;
|
||||||
|
visualObserver.observe(this.#sourceElement, this.#sourceCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#sourceElement = el;
|
|
||||||
visualObserver.observe(this.#sourceElement, this.#sourceCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unobserveSource() {
|
unobserveSource() {
|
||||||
|
|
@ -90,13 +122,21 @@ export class AbstractArrow extends HTMLElement {
|
||||||
|
|
||||||
observeTarget() {
|
observeTarget() {
|
||||||
this.unobserveTarget();
|
this.unobserveTarget();
|
||||||
this.#targetElement = document.querySelector(this.target);
|
|
||||||
|
|
||||||
if (!this.#targetElement) {
|
const vertex = parseVertex(this.#target);
|
||||||
throw new Error('target is not a valid element');
|
|
||||||
|
if (vertex) {
|
||||||
|
this.#targetRect = DOMRectReadOnly.fromRect(vertex);
|
||||||
|
this.update();
|
||||||
|
} else {
|
||||||
|
this.#targetElement = document.querySelector(this.#target);
|
||||||
|
|
||||||
|
if (!this.#targetElement) {
|
||||||
|
throw new Error('target is not a valid element');
|
||||||
|
}
|
||||||
|
|
||||||
|
visualObserver.observe(this.#targetElement, this.#targetCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
visualObserver.observe(this.#targetElement, this.#targetCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unobserveTarget() {
|
unobserveTarget() {
|
||||||
|
|
@ -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
|
|
||||||
) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue