@seeed-studio/solution-export
v0.3.0
Published
Poster/deck projection and layout for Seeed solution pages: page_data -> PosterData -> A4 poster (React) / pptx deck.
Keywords
Readme
@seeed-studio/solution-export
Poster / deck projection and layout for Seeed solution pages.
The export pipeline has four layers. This package owns the middle two; the host
application (today wp_next) keeps the outer two:
| Layer | Owner |
|---|---|
| fetch page_data, resolve reference designs, routing, download triggers | host |
| projection — untyped page_data → typed PosterData | this package |
| layout — PosterData → A4 poster (React) / 3-slide deck (pptxgenjs) | this package |
Nothing here talks to Typesense, Next.js or the network, and the package has zero runtime dependencies. React, react-dom and pptxgenjs are optional peers, used only by the entry point that needs them.
Entry points
// projection. No React, no DOM, no pptxgenjs.
import { projectSolutionPoster, getStructuredTable } from '@seeed-studio/solution-export'
import type { PosterData, ReferenceDesignInput } from '@seeed-studio/solution-export'
// the A4 poster component (React 19)
import { PosterView } from '@seeed-studio/solution-export/poster'
import '@seeed-studio/solution-export/poster.css'
// the pptxgenjs deck builder — separate entry so hosts that never install
// the optional pptxgenjs peer never see its types from the main entry
import { buildDeck } from '@seeed-studio/solution-export/deck'poster.css is never auto-injected — the host imports it explicitly. It is one
file for all poster variants.
Poster variants
| Page type | Input (host-localized) | Projection | View |
|---|---|---|---|
| tech / scenario page | raw page_data | projectSolutionPoster | PosterView (front + optional back) |
| industry solution page | SolutionPageInput | projectSolutionPage | SolutionPosterView |
| success case | CaseInput | projectCase | CasePosterView |
| reference design | RefDesignInput | projectRefDesign | RefDesignPosterView |
The three variant inputs are pure, already-localized data — none of them
references Typesense or page_data field names. Two host-side helpers are
exported for convenience: solutionPageInputFromPageData(pageData, archByScenarioSlug)
builds a SolutionPageInput from a solution page's page_data plus the
architecture image/heading/lines of each related scenario page, and
parsePresetsTable(markdown) extracts the reference-design presets mini
table. Each view takes { poster, title, pageTypeLabel?, labels?, fontClassName?, style? },
the same shape as PosterView. Field-level contracts and the deviations from
the design canvas are in docs/poster-variants.md.
The PosterData contract
projectSolutionPoster(pageData, referenceDesigns?) takes the raw, untyped
page_data blob of a solution / scenario / tech page plus the host's
already-localized reference designs, and returns:
interface PosterData {
hero?: PosterHero // banner.images[0]
arch?: PosterArch // first deployment tab carrying a card image
features: PosterFeature[] // solution_introduce.features, max 3
scenes: PosterScene[] // scenes.tabs, max 3
deployableApps: PosterDeployableApp[] // from `referenceDesigns`, max 4
cases: PosterCase[] // related_posts.list, max 3
deviceTables: PosterDeviceTable[] // deployment tabs with card.table_data
bundles: PosterBundle[] // deployment.bom_configurator.bundles
selectionGuide: PosterSelectionStep[] // ...bom_configurator.steps, max 4
}Sections the input doesn't carry come back empty / undefined; the renderer
decides whether to draw a band at all. The visible gates mirror the host
page's own render gates: scenes, deployment and solution_introduce need
visible === true, related_posts only needs visible !== false.
The projection is defensive about every field — null, a string, or a blob
with the wrong shape all yield an empty PosterData instead of throwing.
What the host must supply
Localized reference designs. The package does not know the host's reference-design document shape, so the host maps each document to:
interface ReferenceDesignInput { id: string name: string // already localized; the id is used when empty tagline?: string image?: string kpi?: Array<{ value: string; label: string } | null> // last populated cell wins }Font variables.
poster.cssreads--sx-font-sansand--sx-font-mono, each falling back to a system stack. A host usingnext/fontwires them up on the poster root:<PosterView poster={poster} title={title} fontClassName={`${notoSansSC.variable} ${ibmPlexMono.variable}`} style={{ '--sx-font-sans': 'var(--font-poster-sc)', '--sx-font-mono': 'var(--font-poster-mono)' } as CSSProperties} />pageTypeLabel. The front-page eyebrow (技术方案 / 应用场景 / 行业方案), resolved and translated by the host.Labels, if not Chinese. The fixed chrome copy defaults to Chinese (
DEFAULT_POSTER_LABELS); passlabels={{ bundlesTitle: '…' }}to override any subset.二维码 (QR code). Pass
qrUrl— the landing page's canonical URL — toPosterView/SolutionPosterView/CasePosterView/RefDesignPosterView. The package appendsutm_source=poster&utm_medium=qrviaposterQrUrl(noutm_campaign: the landing page already identifies the solution, and a shorter payload keeps the 40px back-page QR decodable), unless the URL already carriesutm_source, and encodes the result into a real, scannable QR code. Omitted, both the front contact-block QR and the back-page footer QR keep rendering the decorativeQrPlaceholder— not scannable.Images for the deck.
buildDeck(pptx, poster, images, title)(imported from@seeed-studio/solution-export/deck) takes a{ [src]: dataUrl }map the host has already fetched and re-encoded, plus a pptxgenjs instance it constructed. A missing entry drops that one picture; the deck always builds.
Local development against a host
cd packages/export && pnpm install && pnpm build
# in the host repo:
pnpm add "@seeed-studio/solution-export@file:../seeed-solutions-hub/packages/export"The host resolves dist/, so re-run pnpm build here after every change.
Scripts
| script | what it does |
|---|---|
| pnpm build | Vite lib build → dist/{index,poster,deck}.js + dist/poster.css + .d.ts |
| pnpm test | Vitest. Projection fixtures are this repo's own content JSON (content/scenarios/environment-monitoring/zh-hans.json and the solution / case / reference-design equivalents), read at test time |
| pnpm typecheck | tsc --noEmit |
| pnpm prepare-release | typecheck + test + build (what CI runs before publishing) |
Publishing runs from .github/workflows/publish-export-package.yml on an
export-v* tag.
