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]; let collectionI18n = collection[I18N];
if (config.editor && !collection.editor) { if (config.editor && !collection.editor) {
collection.editor = { preview: config.editor.preview, frame: config.editor.frame }; collection.editor = config.editor;
} }
collection.media_library = { collection.media_library = {
@ -248,7 +248,7 @@ export function applyDefaults<EF extends BaseField = UnknownField>(
} }
if (collection.editor && !file.editor) { 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 { ScrollSyncPane } from 'react-scroll-sync';
import { EDITOR_SIZE_COMPACT } from '@staticcms/core/constants/views'; 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 useBreadcrumbs from '@staticcms/core/lib/hooks/useBreadcrumbs';
import { getI18nInfo, hasI18n } from '@staticcms/core/lib/i18n'; import { getI18nInfo, hasI18n } from '@staticcms/core/lib/i18n';
import classNames from '@staticcms/core/lib/util/classNames.util'; import classNames from '@staticcms/core/lib/util/classNames.util';
@ -10,6 +11,8 @@ import {
selectEntryCollectionTitle, selectEntryCollectionTitle,
} from '@staticcms/core/lib/util/collection.util'; } from '@staticcms/core/lib/util/collection.util';
import { customPathFromSlug } from '@staticcms/core/lib/util/nested.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 MainView from '../MainView';
import EditorToolbar from './EditorToolbar'; import EditorToolbar from './EditorToolbar';
import EditorControlPane from './editor-control-pane/EditorControlPane'; import EditorControlPane from './editor-control-pane/EditorControlPane';
@ -95,6 +98,8 @@ const EditorInterface = ({
submitted, submitted,
slug, slug,
}: TranslatedProps<EditorInterfaceProps>) => { }: TranslatedProps<EditorInterfaceProps>) => {
const config = useAppSelector(selectConfig);
const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {}; const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {};
const translatedLocales = useMemo( const translatedLocales = useMemo(
() => locales?.filter(locale => locale !== defaultLocale) ?? [], () => locales?.filter(locale => locale !== defaultLocale) ?? [],
@ -151,28 +156,55 @@ const EditorInterface = ({
setSelectedLocale(locale); setSelectedLocale(locale);
}, []); }, []);
const [showPreviewToggle, previewInFrame, editorSize] = useMemo(() => { const { livePreviewUrlTemplate, showPreviewToggle, previewInFrame, editorSize } = useMemo(() => {
let preview = collection.editor?.preview ?? true; let livePreviewUrlTemplate =
let frame = collection.editor?.frame ?? true; 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; let size = collection.editor?.size ?? EDITOR_SIZE_COMPACT;
if ('files' in collection) { if (collection.editor) {
const file = getFileFromSlug(collection, entry.slug); if ('preview' in collection.editor) {
if (file?.editor?.preview !== undefined) { preview = collection.editor.preview ?? true;
preview = file.editor.preview;
} }
if (file?.editor?.frame !== undefined) { if ('frame' in collection.editor) {
frame = file.editor.frame; preview = collection.editor.frame ?? true;
}
if (file?.editor?.size !== undefined) {
size = file.editor.size;
} }
} }
return [preview, frame, size]; if ('files' in collection) {
}, [collection, entry.slug]); 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( const finalPreviewActive = useMemo(
() => showPreviewToggle && previewActive, () => showPreviewToggle && previewActive,
@ -318,6 +350,7 @@ const EditorInterface = ({
<EditorPreviewPane <EditorPreviewPane
collection={collection} collection={collection}
previewInFrame={previewInFrame} previewInFrame={previewInFrame}
livePreviewUrlTemplate={livePreviewUrlTemplate}
entry={entry} entry={entry}
fields={fields} fields={fields}
editorSize={editorSize} 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 { createPortal } from 'react-dom';
import Frame from 'react-frame-component'; import Frame from 'react-frame-component';
import { translate } from 'react-polyglot'; 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 { getPreviewStyles, getPreviewTemplate } from '@staticcms/core/lib/registry';
import classNames from '@staticcms/core/lib/util/classNames.util'; import classNames from '@staticcms/core/lib/util/classNames.util';
import { selectTemplateName } from '@staticcms/core/lib/util/collection.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 { selectConfig } from '@staticcms/core/reducers/selectors/config';
import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI'; import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI';
import { useAppSelector } from '@staticcms/core/store/hooks'; import { useAppSelector } from '@staticcms/core/store/hooks';
@ -25,6 +26,7 @@ import type {
TemplatePreviewProps, TemplatePreviewProps,
TranslatedProps, TranslatedProps,
} from '@staticcms/core/interface'; } from '@staticcms/core/interface';
import type DataUpdateEvent from '@staticcms/core/lib/util/events/DataEvent';
import type { FC } from 'react'; import type { FC } from 'react';
const FrameGlobalStyles = ` const FrameGlobalStyles = `
@ -110,12 +112,22 @@ export interface EditorPreviewPaneProps {
fields: Field[]; fields: Field[];
entry: Entry; entry: Entry;
previewInFrame: boolean; previewInFrame: boolean;
livePreviewUrlTemplate: string | undefined;
editorSize: EditorSize; editorSize: EditorSize;
showMobilePreview: boolean; showMobilePreview: boolean;
} }
const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => { 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); const config = useAppSelector(selectConfig);
@ -171,6 +183,15 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
[props, theme, widgetFor, widgetsFor], [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(() => { return useMemo(() => {
if (!element) { if (!element) {
return null; return null;
@ -196,7 +217,13 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
)} )}
> >
<ErrorBoundary config={config}> <ErrorBoundary config={config}>
{previewInFrame ? ( {livePreviewUrlTemplate ? (
<iframe
ref={livePreviewIframe}
src={`${livePreviewUrlTemplate}?useCmsData=true`}
className="w-full h-full"
/>
) : previewInFrame ? (
<Frame <Frame
key="preview-frame" key="preview-frame"
id="preview-pane" id="preview-pane"
@ -251,6 +278,7 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
editorSize, editorSize,
element, element,
initialFrameContent, initialFrameContent,
livePreviewUrlTemplate,
previewComponent, previewComponent,
previewInFrame, previewInFrame,
previewProps, previewProps,

View File

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