An Annotated Research Context web viewer in the style of GitHub's Primer ProductUI.
Displays basic information about an ARC (Annotated Research Context) and allows to navigate through its structure.
👀 check out the demo
npm i @nfdi4plants/arc-web-viewimport { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BaseStyles, ThemeProvider } from '@primer/react'
import '@primer/primitives/dist/css/functional/themes/light.css'
import "@primer/css/dist/primer.css";
import { WebViewer } from '@nfdi4plants/arc-web-view'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ThemeProvider>
<BaseStyles>
<WebViewer /> // will load with empty view
</BaseStyles>
</ThemeProvider>
</StrictMode>,
)This component uses GitHubs Primer ProductUI.
Find more information below:
- Components: https://primer.style/product/components/
- Responsive: https://github.com/primer/css/blob/b5e009778ec01b6e983cba6cbf89cebfdc5a4124/docs/content/support/breakpoints.md#media-query-mixins
The application entry point is src/main.tsx; the npm library exports WebViewer through src/lib.ts. Both use App, which calls resolveViewerConfig in src/config.ts as the single decision point for configuration:
flowchart TD
Start["resolveViewerConfig(props)"] --> Bridge{"Non-null window.arcwebview?"}
Bridge -- Yes --> Plugin["Plugin mode: use bridge only"]
Bridge -- No --> Props{"Any supported prop is not undefined?"}
Props -- Yes --> Component["Component mode: use props"]
Props -- No --> Showcase["Showcase mode: use defaults"]
Plugin --> Defaults["Apply defaults for omitted options"]
Component --> Defaults
Showcase --> Defaults
Defaults --> Loader{"Selected JSON loader?"}
Loader -- Yes --> Load["Load ARC; show an error on failure"]
Loader -- No --> Empty["Show upload and example data"]
| Priority | Entry point | Configuration source |
|---|---|---|
| 1 | Embedded in an iframe by the Invenio plugin | window.arcwebview, including an empty {} |
| 2 | Used as a component in another React application | Supported component props, when the bridge is absent |
| 3 | GitHub Pages showcase | Defaults, when neither a bridge nor supported prop values are supplied |
The bridge is an explicit convention for plugin mode. It takes precedence over all component props. Missing bridge fields use defaults, without falling back to props. An absent or null bridge allows component mode. React passes a props object even for <WebViewer />, so the resolver checks supported values against undefined; showUnloadButton: false selects component mode and hides the unload button. Unrelated props do not affect the decision.
| Option | Bridge field | React prop | Default |
|---|---|---|---|
| Asynchronous ROC-JSON loader | getROCJson |
jsonString |
No loader |
| Asynchronous README loader | readmefetch |
readmefetch |
No README |
| Asynchronous license loader | licensefetch |
licensefetch |
No license |
| Show unload button | showUnloadButton |
showUnloadButton |
true |
All loaders return Promise<string>. The optional bridge and its settings share one TypeScript declaration in src/config.ts; ArcWebViewBridge and WebViewerProps are exported from the npm library. Boolean defaults use nullish coalescing (??), preserving explicit false.
For example, with a loadArcJson callback supplied by the integration:
// Initialize inside the Invenio iframe before React mounts.
window.arcwebview = { getROCJson: loadArcJson, showUnloadButton: false };// In another React app, with no window.arcwebview bridge:
<WebViewer jsonString={loadArcJson} showUnloadButton={false} />
// With neither bridge nor props, use the showcase defaults:
<WebViewer />Without a selected JSON loader, all modes start with an empty view offering upload and example data. With a loader, the viewer shows a spinner until loading completes. Only the selected source's loader runs; a failure displays an error without trying another source. When the selected loader changes, previous ARC data, errors, and example mode are cleared. Results from superseded requests or unmounted viewers are ignored. React Strict Mode may invoke the selected loader again during development, so loaders should be safe to repeat.
Integration considerations:
- Initialize the bridge in the iframe's own window before mounting React. Setting it on the parent page does not configure the iframe; direct iframe access is subject to the same-origin policy. Cross-origin integrations need communication such as
postMessage, with bridge initialization inside the iframe. - Bridge mutation is not reactive. For later configuration changes, use React props in component mode, or remount the plugin viewer after updating its bridge. Keep loader references stable to avoid unnecessary reloads.
- Every viewer in the same window shares the bridge. Use props in a window without a bridge for independently configured React instances.
- The resolver guards access to
window, allowing configuration resolution during server rendering. Data loading starts after the component mounts in the browser.
- installed NodeJS (v20 or higher)
- Install dependencies
npm install(once per setup) - Start development server
npm start
Run the configuration regression tests with npm test.
- Apply changes
- Change version in
package.json - Build component library
npm run build:lib
Note
Remember to login before the next step (verify with npm whoami).
npm login to authenticate your npm account before publishing.
- Publish component library
npm publish --access public(or usenpm publish --access public --tag nextfor prerelease)
- Deploy using
gh-pagesnpm library:npm run deploy
- Build web app using
npm run build