Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions embed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Embed Wrapper

This directory contains a small wrapper page for embedding Meshviewer in an iframe while keeping deep-links in sync in both directions.

## Development

Start the dev server:

```bash
npm run dev:fixtures
```

Then open:

```text
http://127.0.0.1:5173/embed/
```

The wrapper loads `embed/index.html` in an iframe and synchronizes the hash between parent page and embedded Meshviewer via `postMessage`.

## Embedding

Use `embed/index.html` as the outer page. Deep-links work in both directions:

- parent hash changes update the embedded Meshviewer
- hash changes inside the embedded Meshviewer update the parent URL

The synchronization uses `postMessage`, so the setup also works when parent page and embedded Meshviewer are served from different domains.
54 changes: 54 additions & 0 deletions embed/embed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const iframe = document.getElementById("meshviewer-embedded");

if (!(iframe instanceof HTMLIFrameElement)) {
console.warn("IFrame 'meshviewer-embedded' not found or invalid");
} else {
const baseSrc = (iframe.getAttribute("src") || "../index.html").split("#")[0];

function normalizeHash(hash) {
if (!hash) {
return "";
}

return hash.startsWith("#") ? hash : `#${hash}`;
}

function syncIframeSrc(hash) {
iframe.setAttribute("src", `${baseSrc}${normalizeHash(hash)}`);
}

function postHashToIframe(hash) {
if (!iframe.contentWindow) {
syncIframeSrc(hash);
return;
}

iframe.contentWindow.postMessage({ hash: normalizeHash(hash) }, "*");
}

function updateParentHash(hash) {
const nextHash = normalizeHash(hash);

if (!nextHash || nextHash === normalizeHash(window.location.hash)) {
return;
}

window.location.replace(nextHash);
}

syncIframeSrc(window.location.hash);

iframe.addEventListener("load", function () {
postHashToIframe(window.location.hash);
});

window.addEventListener("message", function (event) {
if (event && event.data && typeof event.data.hash === "string") {
updateParentHash(event.data.hash);
}
});

window.addEventListener("hashchange", function () {
postHashToIframe(window.location.hash);
});
}
67 changes: 67 additions & 0 deletions embed/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Meshviewer Embed</title>
<style>
html,
body {
margin: 0;
height: 100%;
font-family: sans-serif;
background: #f3f5f7;
color: #1f2933;
}

body {
box-sizing: border-box;
padding: 24px;
}

.embed-layout {
box-sizing: border-box;
height: 100%;
display: grid;
gap: 12px;
grid-template-rows: auto 1fr;
}

.embed-note {
margin: 0;
padding: 12px 14px;
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 10px;
box-shadow: 0 1px 2px rgb(15 23 42 / 0.06);
}

.embed-frame {
min-height: 0;
background: #ffffff;
border: 1px solid #bcccdc;
border-radius: 12px;
box-shadow: 0 10px 30px rgb(15 23 42 / 0.12);
overflow: hidden;
}

iframe {
border: 0;
display: block;
height: 100%;
width: 100%;
}
</style>
<script src="./embed.js" type="module"></script>
</head>
<body>
<div class="embed-layout">
<p class="embed-note">
Below, the map is loaded inside an iframe. The Meshviewer itself runs inside that embedded frame.
</p>
<div class="embed-frame">
<iframe id="meshviewer-embedded" src="../index.html" title="Meshviewer"></iframe>
</div>
</div>
</body>
</html>
56 changes: 56 additions & 0 deletions lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,59 @@ import { Link } from "./utils/node.js";
import { resolveValidLinks } from "./mainDataUtils.js";

export const main = () => {
function normalizeHash(hash: string) {
if (!hash) {
return "";
}

return hash.startsWith("#") ? hash : `#${hash}`;
}

function replaceHash(hash: string) {
const nextHash = normalizeHash(hash);

if (!nextHash || nextHash === window.location.hash) {
return;
}

window.location.replace(nextHash);
}

function postHashToParent() {
console.log("post: ", window.location.hash);
if (window.parent && window.parent !== window) {
window.parent.postMessage({ hash: window.location.hash }, "*");
}
}

function initEmbedSync() {
if (window.parent === window) {
return;
}

// Attach to Navigo's internal methods to post the hash to the parent whenever it changes
type HistoryMethodName = "pushState" | "replaceState";
function wrapHistoryMethod(method: HistoryMethodName) {
const originalMethod = window.history[method].bind(window.history);

window.history[method] = function (...args) {
const result = originalMethod(...args);
postHashToParent();
return result;
} as History[HistoryMethodName];
}

wrapHistoryMethod("pushState");
wrapHistoryMethod("replaceState");

window.addEventListener("hashchange", postHashToParent);
window.addEventListener("message", function (event) {
if (event && event.data && typeof event.data.hash === "string") {
replaceHash(event.data.hash);
}
});
}

function handleData(data: { links: Link[]; nodes: Node[]; timestamp: string }[]) {
let config = window.config;
let timestamp: string;
Expand Down Expand Up @@ -82,6 +135,8 @@ export const main = () => {
let language = Language();
let router = (window.router = new Router(language));

initEmbedSync();

config.dataPath.forEach(function (_element, i) {
config.dataPath[i] += "meshviewer.json";
});
Expand Down Expand Up @@ -113,6 +168,7 @@ export const main = () => {
gui.setData(nodesData);
router.setData(nodesData);
router.resolve();
postHashToParent();

window.setInterval(function () {
update().then(function (nodesData: any) {
Expand Down
1 change: 1 addition & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default defineConfig(({ command, mode }) => ({
sourcemap: true,
rollupOptions: {
input: {
embed: resolve(__dirname, "embed/index.html"),
index: resolve(__dirname, "index.html"),
offline: resolve(__dirname, "offline.html"),
},
Expand Down
Loading