iframed arrows POC
This commit is contained in:
parent
c7d6243cb9
commit
3f6d9c9ea4
|
|
@ -25,22 +25,17 @@
|
|||
position: absolute;
|
||||
inset: 0 0 0 0;
|
||||
}
|
||||
|
||||
#box2 {
|
||||
max-width: 10ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<fc-geometry id="box1" x="100" y="100" width="50" height="50"></fc-geometry>
|
||||
<fc-geometry id="box2" x="200" y="300">Hello World</fc-geometry>
|
||||
<fc-geometry id="box1" x="50" y="100" width="50" height="50"></fc-geometry>
|
||||
<fc-geometry id="box2" x="150" y="300" width="50" height="50"></fc-geometry>
|
||||
<fc-connection source="#box1" target="#box2"></fc-connection>
|
||||
|
||||
<fc-connection source="#box1" target="400,100"></fc-connection>
|
||||
|
||||
<script type="module">
|
||||
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
|
||||
import { FolkConnection } from '../src/arrows/fc-connection.ts';
|
||||
import '../src/arrows/iframe-script.ts';
|
||||
|
||||
FolkGeometry.register();
|
||||
FolkConnection.register();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Iframe Arrows</title>
|
||||
<style>
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fc-geometry {
|
||||
border: 1px solid black;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
fc-rope {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0 0 0 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<fc-geometry x="10" y="100" width="250" height="400">
|
||||
<iframe id="frame1" src="./arrows.html"></iframe>
|
||||
</fc-geometry>
|
||||
<fc-geometry x="300" y="100" width="250" height="400">
|
||||
<iframe id="frame2" src="./arrows.html"></iframe>
|
||||
</fc-geometry>
|
||||
|
||||
<fc-rope source="#frame1 >>> #box1" target="#frame2 >>> #box1"></fc-rope>
|
||||
|
||||
<script type="module">
|
||||
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
|
||||
import { FolkRope } from '../src/arrows/fc-rope.ts';
|
||||
|
||||
FolkGeometry.register();
|
||||
FolkRope.register();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
<ul>
|
||||
<li><a href="animated-shapes.html">Animated Shapes</a></li>
|
||||
<li><a href="arrows.html">Arrows</a></li>
|
||||
<li><a href="iframed-arrows.html">Iframed Arrows</a></li>
|
||||
<li><a href="canvasify.html">Canvasify</a></li>
|
||||
<li><a href="collision.html">Collision</a></li>
|
||||
<li><a href="event-propagator.html">Event propagator</a></li>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ function parseVertex(str: string): Vertex | null {
|
|||
};
|
||||
}
|
||||
|
||||
function parseCSSSelector(selector) {
|
||||
return selector.split('>>>').map((s) => s.trim());
|
||||
}
|
||||
|
||||
export class AbstractArrow extends HTMLElement {
|
||||
static tagName = 'abstract-arrow';
|
||||
|
||||
|
|
@ -57,6 +61,49 @@ export class AbstractArrow extends HTMLElement {
|
|||
this.#update();
|
||||
};
|
||||
|
||||
#sourceIframeSelector = '';
|
||||
#sourceIframeRect = DOMRectReadOnly.fromRect();
|
||||
#sourceIframeChildRect = DOMRectReadOnly.fromRect();
|
||||
|
||||
#sourcePostMessage = (event: MessageEvent) => {
|
||||
const iframe = this.#sourceElement as HTMLIFrameElement;
|
||||
|
||||
if (event.source !== iframe.contentWindow) return;
|
||||
|
||||
switch (event.data.type) {
|
||||
case 'folk-iframe-ready': {
|
||||
event.source?.postMessage({
|
||||
type: 'folk-observe-element',
|
||||
selector: this.#sourceIframeSelector,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'folk-element-change': {
|
||||
if (this.#sourceIframeSelector === event.data.selector) {
|
||||
this.#sourceIframeChildRect = event.data.boundingBox;
|
||||
this.#updateSourceIframeRect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#sourceIframeCallback = (entry: VisualObserverEntry) => {
|
||||
this.#sourceIframeRect = entry.contentRect;
|
||||
this.#updateSourceIframeRect();
|
||||
};
|
||||
|
||||
#updateSourceIframeRect() {
|
||||
this.#sourceRect = DOMRectReadOnly.fromRect({
|
||||
x: this.#sourceIframeRect.x + this.#sourceIframeChildRect.x,
|
||||
y: this.#sourceIframeRect.y + this.#sourceIframeChildRect.y,
|
||||
height: this.#sourceIframeChildRect.height,
|
||||
width: this.#sourceIframeChildRect.width,
|
||||
});
|
||||
|
||||
this.#update();
|
||||
}
|
||||
|
||||
#target = '';
|
||||
/** A CSS selector for the target of the arrow. */
|
||||
get target() {
|
||||
|
|
@ -88,6 +135,49 @@ export class AbstractArrow extends HTMLElement {
|
|||
this.#update();
|
||||
};
|
||||
|
||||
#targetIframeSelector = '';
|
||||
#targetIframeRect = DOMRectReadOnly.fromRect();
|
||||
#targetIframeChildRect = DOMRectReadOnly.fromRect();
|
||||
|
||||
#targetPostMessage = (event: MessageEvent) => {
|
||||
const iframe = this.#targetElement as HTMLIFrameElement;
|
||||
|
||||
if (event.source !== iframe.contentWindow) return;
|
||||
|
||||
switch (event.data.type) {
|
||||
case 'folk-iframe-ready': {
|
||||
event.source?.postMessage({
|
||||
type: 'folk-observe-element',
|
||||
selector: this.#targetIframeSelector,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'folk-element-change': {
|
||||
if (this.#targetIframeSelector === event.data.selector) {
|
||||
this.#targetIframeChildRect = event.data.boundingBox;
|
||||
this.#updateTargetIframeRect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#targetIframeCallback = (entry: VisualObserverEntry) => {
|
||||
this.#targetIframeRect = entry.contentRect;
|
||||
this.#updateTargetIframeRect();
|
||||
};
|
||||
|
||||
#updateTargetIframeRect() {
|
||||
this.#targetRect = DOMRectReadOnly.fromRect({
|
||||
x: this.#targetIframeRect.x + this.#targetIframeChildRect.x,
|
||||
y: this.#targetIframeRect.y + this.#targetIframeChildRect.y,
|
||||
height: this.#targetIframeChildRect.height,
|
||||
width: this.#targetIframeChildRect.width,
|
||||
});
|
||||
|
||||
this.#update();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.source = this.getAttribute('source') || this.#source;
|
||||
this.target = this.getAttribute('target') || this.#target;
|
||||
|
|
@ -112,18 +202,27 @@ export class AbstractArrow extends HTMLElement {
|
|||
this.#sourceRect = DOMRectReadOnly.fromRect(vertex);
|
||||
this.#update();
|
||||
} else {
|
||||
this.#sourceElement = document.querySelector(this.source);
|
||||
const [selector, iframeSelector] = parseCSSSelector(this.#source);
|
||||
this.#sourceIframeSelector = iframeSelector;
|
||||
this.#sourceElement = document.querySelector(selector);
|
||||
|
||||
if (this.#sourceElement === null) {
|
||||
throw new Error('source is not a valid element');
|
||||
} else if (this.#sourceElement instanceof FolkGeometry) {
|
||||
this.#sourceElement.addEventListener('resize', this.#sourceHandler);
|
||||
this.#sourceElement.addEventListener('move', this.#sourceHandler);
|
||||
this.#sourceRect = this.#sourceElement.getBoundingClientRect();
|
||||
} else if (this.#sourceElement instanceof HTMLIFrameElement && this.#sourceIframeSelector) {
|
||||
window.addEventListener('message', this.#sourcePostMessage);
|
||||
visualObserver.observe(this.#sourceElement, this.#sourceIframeCallback);
|
||||
this.#sourceElement.contentWindow?.postMessage({
|
||||
type: 'folk-observe-element',
|
||||
selector: this.#sourceIframeSelector,
|
||||
});
|
||||
} else {
|
||||
visualObserver.observe(this.#sourceElement, this.#sourceCallback);
|
||||
this.#sourceRect = this.#sourceElement.getBoundingClientRect();
|
||||
}
|
||||
|
||||
this.#sourceRect = this.#sourceElement.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +232,13 @@ export class AbstractArrow extends HTMLElement {
|
|||
if (this.#sourceElement instanceof FolkGeometry) {
|
||||
this.#sourceElement.removeEventListener('resize', this.#sourceHandler);
|
||||
this.#sourceElement.removeEventListener('move', this.#sourceHandler);
|
||||
} else if (this.#sourceElement instanceof HTMLIFrameElement && this.#sourceIframeSelector) {
|
||||
window.removeEventListener('message', this.#sourcePostMessage);
|
||||
visualObserver.unobserve(this.#sourceElement, this.#sourceIframeCallback);
|
||||
this.#sourceElement.contentWindow?.postMessage({
|
||||
type: 'folk-unobserve-element',
|
||||
selector: this.#sourceIframeSelector,
|
||||
});
|
||||
} else {
|
||||
visualObserver.unobserve(this.#sourceElement, this.#sourceCallback);
|
||||
}
|
||||
|
|
@ -147,13 +253,22 @@ export class AbstractArrow extends HTMLElement {
|
|||
this.#targetRect = DOMRectReadOnly.fromRect(vertex);
|
||||
this.#update();
|
||||
} else {
|
||||
this.#targetElement = document.querySelector(this.#target);
|
||||
const [selector, iframeSelector] = parseCSSSelector(this.#target);
|
||||
this.#targetIframeSelector = iframeSelector;
|
||||
this.#targetElement = document.querySelector(selector);
|
||||
|
||||
if (!this.#targetElement) {
|
||||
throw new Error('target is not a valid element');
|
||||
} else if (this.#targetElement instanceof FolkGeometry) {
|
||||
this.#targetElement.addEventListener('resize', this.#targetHandler);
|
||||
this.#targetElement.addEventListener('move', this.#targetHandler);
|
||||
} else if (this.#targetElement instanceof HTMLIFrameElement && this.#targetIframeSelector) {
|
||||
window.addEventListener('message', this.#targetPostMessage);
|
||||
visualObserver.observe(this.#targetElement, this.#targetIframeCallback);
|
||||
this.#targetElement.contentWindow?.postMessage({
|
||||
type: 'folk-observe-element',
|
||||
selector: this.#targetIframeSelector,
|
||||
});
|
||||
} else {
|
||||
visualObserver.observe(this.#targetElement, this.#targetCallback);
|
||||
}
|
||||
|
|
@ -167,6 +282,13 @@ export class AbstractArrow extends HTMLElement {
|
|||
if (this.#targetElement instanceof FolkGeometry) {
|
||||
this.#targetElement.removeEventListener('resize', this.#targetHandler);
|
||||
this.#targetElement.removeEventListener('move', this.#targetHandler);
|
||||
} else if (this.#targetElement instanceof HTMLIFrameElement && this.#targetIframeSelector) {
|
||||
window.removeEventListener('message', this.#targetPostMessage);
|
||||
visualObserver.unobserve(this.#targetElement, this.#targetIframeCallback);
|
||||
this.#targetElement.contentWindow?.postMessage({
|
||||
type: 'folk-unobserve-element',
|
||||
selector: this.#targetIframeSelector,
|
||||
});
|
||||
} else {
|
||||
visualObserver.unobserve(this.#targetElement, this.#targetCallback);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { FolkGeometry } from '../canvas/fc-geometry';
|
||||
|
||||
// If this page is framed in then mock inject the following post message script
|
||||
if (window.parent !== window) {
|
||||
// keep track of count of elements being observed
|
||||
const observedElements = new Map();
|
||||
const observedSelectors = new Map();
|
||||
|
||||
function onGeometryChange(event) {
|
||||
window.parent.postMessage({
|
||||
type: 'folk-element-change',
|
||||
selector: observedSelectors.get(event.target),
|
||||
boundingBox: event.target.getClientRect(),
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
switch (event.data.type) {
|
||||
case 'folk-observe-element': {
|
||||
const selector = event.data.selector;
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element === null) return;
|
||||
|
||||
observedElements.set(selector, element);
|
||||
observedSelectors.set(element, selector);
|
||||
|
||||
if (element instanceof FolkGeometry) {
|
||||
element.addEventListener('move', onGeometryChange);
|
||||
element.addEventListener('resize', onGeometryChange);
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'folk-element-change',
|
||||
selector: selector,
|
||||
boundingBox: element.getClientRect(),
|
||||
});
|
||||
} else {
|
||||
// use BoundingBoxObserver
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'folk-unobserve-element': {
|
||||
const selector = event.data.selector;
|
||||
const element = observedElements.get(selector);
|
||||
|
||||
if (element === undefined) return;
|
||||
|
||||
if (element instanceof FolkGeometry) {
|
||||
element.removeEventListener('move', onGeometryChange);
|
||||
element.removeEventListener('resize', onGeometryChange);
|
||||
observedElements.delete(selector);
|
||||
observedSelectors.delete(element);
|
||||
} else {
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'folk-iframe-ready',
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue