feat: live preview iframe, pass data update event

This commit is contained in:
Daniel Lautzenheiser 2023-07-19 12:56:52 -04:00
parent 2834f3535b
commit ba27a456da
4 changed files with 94 additions and 23 deletions

View File

@ -173,7 +173,7 @@ export function applyDefaults<EF extends BaseField = UnknownField>(
let collectionI18n = collection[I18N];
if (config.editor && !collection.editor) {
collection.editor = { preview: config.editor.preview, frame: config.editor.frame };
collection.editor = config.editor;
}
collection.media_library = {
@ -248,7 +248,7 @@ export function applyDefaults<EF extends BaseField = UnknownField>(
}
if (collection.editor && !file.editor) {
file.editor = { preview: collection.editor.preview, frame: collection.editor.frame };
file.editor = collection.editor;
}
}
}

View File

@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ScrollSyncPane } from 'react-scroll-sync';
import { EDITOR_SIZE_COMPACT } from '@staticcms/core/constants/views';
import { summaryFormatter } from '@staticcms/core/lib/formatters';
import useBreadcrumbs from '@staticcms/core/lib/hooks/useBreadcrumbs';
import { getI18nInfo, hasI18n } from '@staticcms/core/lib/i18n';
import classNames from '@staticcms/core/lib/util/classNames.util';
@ -10,6 +11,8 @@ import {
selectEntryCollectionTitle,
} from '@staticcms/core/lib/util/collection.util';
import { customPathFromSlug } from '@staticcms/core/lib/util/nested.util';
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
import { useAppSelector } from '@staticcms/core/store/hooks';
import MainView from '../MainView';
import EditorToolbar from './EditorToolbar';
import EditorControlPane from './editor-control-pane/EditorControlPane';
@ -95,6 +98,8 @@ const EditorInterface = ({
submitted,
slug,
}: TranslatedProps<EditorInterfaceProps>) => {
const config = useAppSelector(selectConfig);
const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {};
const translatedLocales = useMemo(
() => locales?.filter(locale => locale !== defaultLocale) ?? [],
@ -151,28 +156,55 @@ const EditorInterface = ({
setSelectedLocale(locale);
}, []);
const [showPreviewToggle, previewInFrame, editorSize] = useMemo(() => {
let preview = collection.editor?.preview ?? true;
let frame = collection.editor?.frame ?? true;
const { livePreviewUrlTemplate, showPreviewToggle, previewInFrame, editorSize } = useMemo(() => {
let livePreviewUrlTemplate =
typeof collection.editor?.live_preview === 'string' ? collection.editor.live_preview : false;
let preview = true;
let frame = true;
let size = collection.editor?.size ?? EDITOR_SIZE_COMPACT;
if ('files' in collection) {
const file = getFileFromSlug(collection, entry.slug);
if (file?.editor?.preview !== undefined) {
preview = file.editor.preview;
if (collection.editor) {
if ('preview' in collection.editor) {
preview = collection.editor.preview ?? true;
}
if (file?.editor?.frame !== undefined) {
frame = file.editor.frame;
}
if (file?.editor?.size !== undefined) {
size = file.editor.size;
if ('frame' in collection.editor) {
preview = collection.editor.frame ?? true;
}
}
return [preview, frame, size];
}, [collection, entry.slug]);
if ('files' in collection) {
const file = getFileFromSlug(collection, entry.slug);
if (file?.editor) {
if (typeof file.editor.live_preview === 'string') {
livePreviewUrlTemplate = file.editor.live_preview;
}
if ('preview' in file.editor && file.editor.preview !== undefined) {
preview = file.editor.preview;
}
if ('frame' in file.editor && file.editor.frame !== undefined) {
frame = file.editor.frame;
}
if (file?.editor?.size !== undefined) {
size = file.editor.size;
}
}
}
return {
livePreviewUrlTemplate: livePreviewUrlTemplate
? summaryFormatter(livePreviewUrlTemplate, entry, collection, config?.slug)
: undefined,
showPreviewToggle: preview,
previewInFrame: frame,
editorSize: size,
};
}, [collection, config?.slug, entry]);
const finalPreviewActive = useMemo(
() => showPreviewToggle && previewActive,
@ -318,6 +350,7 @@ const EditorInterface = ({
<EditorPreviewPane
collection={collection}
previewInFrame={previewInFrame}
livePreviewUrlTemplate={livePreviewUrlTemplate}
entry={entry}
fields={fields}
editorSize={editorSize}

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useRef } from 'react';
import { createPortal } from 'react-dom';
import Frame from 'react-frame-component';
import { translate } from 'react-polyglot';
@ -8,6 +8,7 @@ import { EDITOR_SIZE_COMPACT } from '@staticcms/core/constants/views';
import { getPreviewStyles, getPreviewTemplate } from '@staticcms/core/lib/registry';
import classNames from '@staticcms/core/lib/util/classNames.util';
import { selectTemplateName } from '@staticcms/core/lib/util/collection.util';
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI';
import { useAppSelector } from '@staticcms/core/store/hooks';
@ -25,6 +26,7 @@ import type {
TemplatePreviewProps,
TranslatedProps,
} from '@staticcms/core/interface';
import type DataUpdateEvent from '@staticcms/core/lib/util/events/DataEvent';
import type { FC } from 'react';
const FrameGlobalStyles = `
@ -110,12 +112,22 @@ export interface EditorPreviewPaneProps {
fields: Field[];
entry: Entry;
previewInFrame: boolean;
livePreviewUrlTemplate: string | undefined;
editorSize: EditorSize;
showMobilePreview: boolean;
}
const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
const { editorSize, entry, collection, fields, previewInFrame, showMobilePreview, t } = props;
const {
editorSize,
entry,
collection,
fields,
previewInFrame,
livePreviewUrlTemplate,
showMobilePreview,
t,
} = props;
const config = useAppSelector(selectConfig);
@ -171,6 +183,15 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
[props, theme, widgetFor, widgetsFor],
);
const livePreviewIframe = useRef<HTMLIFrameElement>(null);
const passEventToIframe = useCallback((event: DataUpdateEvent) => {
if (livePreviewIframe.current) {
livePreviewIframe.current.contentWindow?.postMessage(event);
}
}, []);
useWindowEvent('data:update', passEventToIframe);
return useMemo(() => {
if (!element) {
return null;
@ -196,7 +217,13 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
)}
>
<ErrorBoundary config={config}>
{previewInFrame ? (
{livePreviewUrlTemplate ? (
<iframe
ref={livePreviewIframe}
src={`${livePreviewUrlTemplate}?useCmsData=true`}
className="w-full h-full"
/>
) : previewInFrame ? (
<Frame
key="preview-frame"
id="preview-pane"
@ -251,6 +278,7 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
editorSize,
element,
initialFrameContent,
livePreviewUrlTemplate,
previewComponent,
previewInFrame,
previewProps,

View File

@ -190,12 +190,22 @@ export interface FileNameFilterRule {
export type FilterRule = FieldFilterRule | FileNameFilterRule;
export interface EditorConfig {
preview?: boolean;
frame?: boolean;
export interface BaseEditorConfig {
size?: EditorSize;
}
export interface DefaultEditorConfig extends BaseEditorConfig {
preview?: boolean;
frame?: boolean;
live_preview?: false;
}
export interface LiveEditorConfig extends BaseEditorConfig {
live_preview: string;
}
export type EditorConfig = DefaultEditorConfig | LiveEditorConfig;
export interface CollectionFile<EF extends BaseField = UnknownField> {
name: string;
label: string;