Skip to content
Open
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
103 changes: 65 additions & 38 deletions docs/utilities/embed-calculators.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,86 @@ sidebar_position: 3
title: Embedding calculators
---

MDX pages can host interactive utilities in several ways:
Some calculators ship as standalone, same-origin bundles under
`static/utility-apps/<slug>/app.html`. For external sites (manufacturer pages,
supplier portals, engineering blogs) we provide **embed routes** with minimal
chrome: no navbar, no footer, just the tool plus a small "Powered by CAD
AutoScript" bar.

## 1. Inline iframe
Currently available embeds:

```mdx
<iframe
src="/utilities/pipe-cutter/"
title="Pipe Cutter"
height="640"
style={{width: '100%', border: '1px solid rgba(255,255,255,0.08)', borderRadius: '18px'}}
/>
```
| Calculator | Embed URL |
|---|---|
| Blind Flange Calculator | `https://cadautoscript.com/embed/blind-flange-calculator/` |
| Dished End (Vessel Head) Calculator | `https://cadautoscript.com/embed/pressure-vessel-dished-end-calc/` |

Use this when a calculator ships as a standalone bundle under `static/utility-apps/<slug>/app.html`.
## Copy-paste snippet

## 2. Wrap as a React component
```html
<iframe
id="cad-blind-flange"
src="https://cadautoscript.com/embed/blind-flange-calculator/"
title="Blind Flange Calculator"
style="width: 100%; height: 640px; border: 1px solid #e8ebf0; border-radius: 12px;"
loading="lazy"
></iframe>

<script>
// The embed reports its height via postMessage so the iframe can grow
// and shrink with the calculator's content — no manual height tuning.
window.addEventListener('message', (event) => {
const data = event.data;
if (
data &&
data.source === 'cadautoscript-embed' &&
data.type === 'resize' &&
document.getElementById('cad-blind-flange')
) {
document.getElementById('cad-blind-flange').style.height = data.height + 'px';
}
});
</script>
```

If the tool exposes a React build (for example, a DXF generator exported with Vite), create a component under `src/components`:
Swap the `src` and the element id to embed the dished end calculator instead:

```tsx
type Props = {height?: number};

export default function PipeCutterEmbed({height = 620}: Props) {
return (
<iframe
src="/utilities/pipe-cutter/"
title="Pipe Cutter"
height={height}
style={{width: '100%', border: 'none'}}
loading="lazy"
/>
);
}
```html
<iframe
id="cad-dished-end"
src="https://cadautoscript.com/embed/pressure-vessel-dished-end-calc/"
title="Dished End (Vessel Head) Calculator"
style="width: 100%; height: 640px; border: 1px solid #e8ebf0; border-radius: 12px;"
loading="lazy"
></iframe>
```

Then import it directly inside MDX:

```mdx
import PipeCutterEmbed from '@site/src/components/PipeCutterEmbed';
## How the embed works

<PipeCutterEmbed height={720} />
```
- **postMessage-based resize handling** — the embed page measures the tool's
real content height and posts
`{source: 'cadautoscript-embed', type: 'resize', height, slug}` to the parent
page. The parent snippet above just listens and updates the iframe height.
- **No third-party cookies** — the embed route sets none; the calculator itself
runs entirely in the browser.
- **Powered-by backlink** — every embed renders a slim footer linking back to
the full calculator page, which keeps the widget compliant with the
integration guidelines and brings qualified visitors back to the site.
- **Noindex** — embed pages carry `meta name="robots" content="noindex"` so the
main calculator pages keep their search visibility.

## 3. Render JSX utilities
## Extending to more calculators

For calculators written purely in React, export them from `src/components` and import into MDX without iframes. This keeps styling consistent with the rest of the site.
Embed routes live in `src/pages/embed/<slug>.tsx` and are three lines each:

```mdx
import KFactorPlayground from '@site/src/components/KFactorPlayground';
```tsx
import {EmbedUtilityPage} from '@site/src/components/Utilities/EmbedUtilityPage';

<KFactorPlayground defaultMaterial="S235" />
export default EmbedUtilityPage('<slug>');
```

Add the slug to `UtilityPageSlug` configs as usual — the embed wrapper reuses
the same `appPath`, `title`, and `iframeAllow` values as the full shell page.

## Styling tips

- Keep containers fluid so the utilities work on kiosks, tablets, and laptops.
Expand Down
133 changes: 133 additions & 0 deletions src/components/Utilities/EmbedUtilityPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import Head from '@docusaurus/Head';
import useBaseUrl from '@docusaurus/useBaseUrl';
import {
utilityPageConfigs,
type UtilityPageSlug,
} from '@site/src/data/utilityShellPages';

export const EMBED_MESSAGE_SOURCE = 'cadautoscript-embed';

const FULL_PAGE_ORIGIN = 'https://cadautoscript.com';

/**
* Minimal-chrome embed route for a utility calculator.
*
* - No navbar/footer: the page renders just the tool iframe plus a slim
* "Powered by" bar, so external sites can frame it cleanly.
* - Auto-height: the wrapper measures the (same-origin) tool iframe content,
* sizes the iframe to it, and reports the final page height to the embedding
* parent via postMessage (`{source: 'cadautoscript-embed', type: 'resize',
* height, slug}`). No third-party cookies are set.
*/
export function EmbedUtilityPage(slug: UtilityPageSlug) {
return function EmbedUtilityRoute() {
const config = utilityPageConfigs[slug];
if (!config) {
throw new Error(`Utility page configuration missing for slug "${slug}"`);
}
const {title, appPath, iframeAllow = ''} = config;
const iframeSrc = useBaseUrl(appPath ?? `/utility-apps/${slug}/app.html`);
const fullPageUrl = `${FULL_PAGE_ORIGIN}/utilities/${slug}/`;

const frameRef = React.useRef<HTMLIFrameElement>(null);
const [frameHeight, setFrameHeight] = React.useState(640);

// 1) Size the wrapper to the tool's real content height (same-origin).
React.useEffect(() => {
const measure = () => {
const frame = frameRef.current;
if (!frame) return;
try {
const doc = frame.contentDocument ?? frame.contentWindow?.document;
if (!doc) return;
const h = Math.max(
doc.documentElement?.scrollHeight ?? 0,
doc.body?.scrollHeight ?? 0,
);
if (h > 120) setFrameHeight(h);
} catch {
// Cross-origin fallback: keep the last measured height.
}
};
measure();
const interval = window.setInterval(measure, 800);
const stop = window.setTimeout(() => window.clearInterval(interval), 15000);
return () => {
window.clearInterval(interval);
window.clearTimeout(stop);
};
}, []);

// 2) Report our total height to the embedding parent page.
React.useEffect(() => {
const report = () => {
const height = Math.ceil(document.documentElement.scrollHeight);
if (window.parent && window.parent !== window) {
window.parent.postMessage(
{source: EMBED_MESSAGE_SOURCE, type: 'resize', height, slug},
'*',
);
}
};
report();
const observer = new ResizeObserver(report);
observer.observe(document.documentElement);
window.addEventListener('load', report);
const interval = window.setInterval(report, 600);
const stop = window.setTimeout(() => window.clearInterval(interval), 15000);
return () => {
observer.disconnect();
window.clearInterval(interval);
window.clearTimeout(stop);
window.removeEventListener('load', report);
};
}, [slug]);

return (
<>
<Head>
<title>{`${title} — Embed`}</title>
<meta name="robots" content="noindex" />
</Head>
<main style={{margin: 0, padding: 0, background: '#ffffff'}}>
<iframe
ref={frameRef}
src={iframeSrc}
title={title}
style={{
width: '100%',
height: `${frameHeight}px`,
border: 'none',
display: 'block',
}}
allow={iframeAllow}
loading="eager"
/>
<footer
style={{
padding: '10px 14px',
textAlign: 'right',
fontSize: '13px',
lineHeight: 1.4,
color: '#5b6472',
background: '#f7f8fa',
fontFamily: 'system-ui, -apple-system, sans-serif',
borderTop: '1px solid #e8ebf0',
}}
>
Powered by{' '}
<a
href={fullPageUrl}
target="_blank"
rel="noopener noreferrer"
style={{color: '#2f6feb', fontWeight: 600, textDecoration: 'none'}}
>
CAD AutoScript
</a>
</footer>
</main>
</>
);
};
}
3 changes: 3 additions & 0 deletions src/pages/embed/blind-flange-calculator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {EmbedUtilityPage} from '@site/src/components/Utilities/EmbedUtilityPage';

export default EmbedUtilityPage('blind-flange-calculator');
3 changes: 3 additions & 0 deletions src/pages/embed/pressure-vessel-dished-end-calc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {EmbedUtilityPage} from '@site/src/components/Utilities/EmbedUtilityPage';

export default EmbedUtilityPage('pressure-vessel-dished-end-calc');