feat: live preview iframe, pass data update event
This commit is contained in:
parent
2834f3535b
commit
ba27a456da
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user