diff --git a/docs/utilities/embed-calculators.md b/docs/utilities/embed-calculators.md index 4a2fafe..a9f3452 100644 --- a/docs/utilities/embed-calculators.md +++ b/docs/utilities/embed-calculators.md @@ -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//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 - + + +``` -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 ( - ``` -Then import it directly inside MDX: - -```mdx -import PipeCutterEmbed from '@site/src/components/PipeCutterEmbed'; +## How the embed works - -``` +- **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/.tsx` and are three lines each: -```mdx -import KFactorPlayground from '@site/src/components/KFactorPlayground'; +```tsx +import {EmbedUtilityPage} from '@site/src/components/Utilities/EmbedUtilityPage'; - +export default EmbedUtilityPage(''); ``` +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. diff --git a/src/components/Utilities/EmbedUtilityPage.tsx b/src/components/Utilities/EmbedUtilityPage.tsx new file mode 100644 index 0000000..2a18a28 --- /dev/null +++ b/src/components/Utilities/EmbedUtilityPage.tsx @@ -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(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 ( + <> + + {`${title} — Embed`} + + +
+