@nrk/sanity-plugin-nrkno-iframe-preview

_This document assumes familiarity with [custom documents views in Sanity](https://www.sanity.io/docs/create-custom-document-views-with-structure-builder)._ _It builds on the [principles of nrkno-sanity](https://github.com/nrkno/nrkno-sanity-libs/blob/mas

Usage no npm install needed!

<script type="module">
  import nrkSanityPluginNrknoIframePreview from 'https://cdn.skypack.dev/@nrk/sanity-plugin-nrkno-iframe-preview';
</script>

README

@nrk/sanity-plugin-nrkno-iframe-preview

This document assumes familiarity with custom documents views in Sanity. It builds on the principles of nrkno-sanity and option driven design.

nrkno-iframe-preview provides a live-updated iframe preview component for Sanity Studio.

Queries are executed by Sanity Studio and passed to the iframe. The render-app in the iframe does not need to listen for sanity client updates itself, but rather to messages from parent window.

The app running in the iframe should use @nrk/nrkno-iframe-preview-api, which allows it to specify a GROQ-query for the Studio to execute.

The result of the query will be sent to the iframe whenever the query revision matches the studio revision (ie. whenever the data changes).

This means that the render app does not need to listen for changes itself, as data flows directly from the Studio via iframe.postMessage.

preview.png Figure 1: NRK image editor in the left pane, IFramePreview with desktopMinWidth configured in the right pane.

Installation

In Sanity studio project run:

npx sanity install @nrk/sanity-plugin-nrkno-iframe-preview

This will install & add the plugin to sanity.json plugin array.

At-a-glance

  • Use IFramePreview component in studio structure view.
    • Configure with render-app preview-url.
    • Optionally configure desktopMinWidth.
  • Use nrkno-sanity-iframe-preview-api in the render-app.
    • Configure a groq-query (or just use document directly from Sanity Studio as is).
  • Enjoy live-updated preview in Studio, with queries executed by the Studio on behalf of the render app.

Usage

Basic

Read IFramePreviewBasicProps jsdocs for config details.

At the most basic level, IFramePreview component can used as part of Sanity Studio StructureBuilder code when adding a second view:

//deskStructure.ts
import React from 'react'
import S from '@sanity/desk-tool/structure-builder'
import { IFramePreview } from '@nrk/sanity-plugin-nrkno-iframe-preview'

export const getDefaultDocumentNode = ({schemaType}: any) => {
  if(schemaName === 'schema-should-have-iframe-preivew') {
    return S.document().views([
      S.view.form(),
      S.view.component(IFramePreview)
            .options({
              url: (doc: SanityDocument) => 'http://iframe-url.example', // (doc) => (string |Promise<string>)
            })
            .icon(EyeIcon)
            .id("preview")
            .title("Preview")
    ])
  }
}

Responsive controls

When desktopMinWidth option is set, buttons for forcing iframe width using width & scale transform will be shown. This is useful if the target app has breakpoints for media queries that have vastly different layouts.

The value is the pixel breakpoint or desktop width.

S.view.component(IFramePreview)
      .options({
        url: (doc: SanityDocument) => 'http://iframe-url.example', 
        desktopMinWidth: 900
      })

Controlling what gets sendt to the iframe

If we dont want the full document to be passed to the iframe during 'doc' events, we can provide a mapDocument option to transform the document before it gets sendt.

Note: IFramePreview sends data to the iframe, _id & _eventType will always be appended. These fields are used to implement the iframe-protocol and therefor cannot be changed (will be overwritten).

S.view.component(IFramePreview)
      .options({
        url: (doc: SanityDocument) => 'http://iframe-url.example',
        mapDocument: (document: SanityDocument) => ({id: document._id})
      })

Customize display texts

Create a wrapper component, and provide DisplayTextsContext:

import React from 'react';
import {
  defaultDisplayTexts,
  DisplayTexts,
  DisplayTextsContext,
  IFramePreview,
  IFramePreviewProps,
} from '@nrk/sanity-plugin-nrkno-iframe-preview';

const texts: DisplayTexts = {
  ...defaultDisplayTexts,
  iframeTitle: 'Changed preview title',
};

export function TranslatedIFramePreview(props: IFramePreviewProps) {
  return (
    <DisplayTextsContext.Provider value={texts}>
      <IFramePreview {...props} />
    </DisplayTextsContext.Provider>
  );
}

Define preview component from schema

The above approach to document previews, where each schema gets a separate if-branch in getDefaultDocumentNode, goes counter to the principles of nrkno-sanity. It does not scale in a codebase with a large amount of schemas, and does not optimzie for deletion (schema one in one file, preview config in another).

We can enable iframe preview directly in the schema definition, more along the lines of option driven design though.

Alternativly, take a look at @nrkno/sanity-plugin-nrkno-schema-structure.

Assume sanity.json with parts structure implemented like so

{
  "name": "part:@sanity/desk-tool/structure",
  "path": "./structure.ts"
}
// someSchema.tsx
export const someSchema = {
  type: 'document',
  title: 'Some doc',
  livePreviewComponent: IFramePreview,
  fields: [/** omitted */]
}

// structure.ts
export function editAndPreviewViews<T>(previewComponent: PreviewComponent<T>) {
  return [
    S.view.form().title("Edit").icon(EditIcon),
    S.view
      .component(previewComponent)
      .icon(EyeIcon)
      .id("preview")
      .title("Preview"),
  ];
}

// https://www.sanity.io/docs/structure-builder-reference#97e44ce262c9
export const getDefaultDocumentNode = ({ schemaType }: any) => {
  const matchingTypes = S.documentTypeListItems()
    .filter((listItem: any) => listItem.spec.schemaType.name === schemaType)
    .map((listItem: any) => {
      const previewComponent = listItem.spec.schemaType.livePreviewComponent;
      if (previewComponent) {
        // add iframe preview using schema-provided component
        return S.document().views(editAndPreviewViews(previewComponent));
      }
      return S.document();
    });
  return matchingTypes.length ? matchingTypes[0] : S.document();
};

export default () => S.list().items(/* your structure */)

Sequence diagram for dataflow

sequence.png Figure 2: Sequence diagram for dataflow between Sanity Studio and the iframe

Develop

Build

npm run build

Test

In this directory

npm run build
npm link
cd /path/to/my-studio
npm link @nrk/sanity-plugin-nrkno-iframe-preview

Note: after running npm link, tests will start failing because of the way React versions are handles during symlinking. Run npm run init from root directory to fix.

Develop

This plugin is built with sanipack.