feat: live updates

This commit is contained in:
Daniel Lautzenheiser 2023-07-19 16:15:46 -04:00
parent 7601111424
commit 5046dc1558
10 changed files with 58 additions and 39 deletions

View File

@ -25,7 +25,7 @@
"prepublishOnly": "yarn build ",
"prepack": "cp ../../README.md ./",
"postpack": "rm ./README.md",
"serve": "webpack serve",
"serve": "webpack serve --config-name configMain",
"test": "cross-env NODE_ENV=test jest",
"test:integration": "cross-env NODE_ENV=test jest -c jest.config.integration.js",
"test:ci": "cross-env NODE_ENV=test jest --maxWorkers=2 --coverage",

View File

@ -212,7 +212,6 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
}, [collection, createBackup, entryDraft.entry, hasChanged]);
useEntryCallback({
hasChanged,
collection,
slug,
callback: () => {

View File

@ -186,7 +186,10 @@ const EditorPreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
const livePreviewIframe = useRef<HTMLIFrameElement>(null);
const passEventToIframe = useCallback((event: DataUpdateEvent) => {
if (livePreviewIframe.current) {
livePreviewIframe.current.contentWindow?.postMessage(event);
livePreviewIframe.current.contentWindow?.postMessage({
message: 'data:update',
value: { fieldPath: event.detail.fieldPath, value: event.detail.value },
});
}
}, []);

View File

@ -1 +0,0 @@
export * from './live';

View File

@ -16,7 +16,6 @@ import Registry from './lib/registry';
export * from './backends';
export * from './interface';
export * from './components';
export * from './lib';
export { default as locales } from './locales';
export * from './widgets';

View File

@ -1,23 +1,29 @@
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ValueOrNestedValue } from '@staticcms/core/interface';
import type DataUpdateEvent from '../util/events/DataEvent';
export default function useData(value: ValueOrNestedValue, path: string) {
const [data, setData] = useState(value);
const [searchParams] = useSearchParams();
const isCms = searchParams.get('useCmsData') === 'true';
const isCms = useMemo(() => {
if (!window) {
return false;
}
const searchParams = new URLSearchParams(window.location.search);
return searchParams.get('useCmsData') === 'true';
}, []);
const onDataChange = useCallback(
(event: DataUpdateEvent) => {
if (!isCms) {
(event: MessageEvent) => {
if (!isCms || event.data.message !== 'data:update') {
return;
}
if (event.detail.fieldPath === path) {
setData(event.detail.value);
const { fieldPath, value } = event.data.value;
if (fieldPath === path) {
setData(value);
}
},
[isCms, path],
@ -28,10 +34,10 @@ export default function useData(value: ValueOrNestedValue, path: string) {
return;
}
window.addEventListener('data:update', onDataChange);
window?.addEventListener('message', onDataChange);
return () => {
window.removeEventListener('data:update', onDataChange);
window?.removeEventListener('message', onDataChange);
};
}, [isCms, onDataChange]);

View File

@ -63,18 +63,12 @@ async function handleChange(
}
interface EntryCallbackProps {
hasChanged: boolean;
collection: Collection;
slug: string | undefined;
callback: () => void;
}
export default function useEntryCallback({
hasChanged,
slug,
collection,
callback,
}: EntryCallbackProps) {
export default function useEntryCallback({ slug, collection, callback }: EntryCallbackProps) {
const dispatch = useAppDispatch();
const entry = useAppSelector(selectEditingDraft);
@ -85,7 +79,7 @@ export default function useEntryCallback({
return;
}
if (hasChanged && entry) {
if (entry) {
const file = fileForEntry(collection, slug);
let updatedEntryData = entry.data;
@ -121,9 +115,9 @@ export default function useEntryCallback({
setLastEntryData(entry?.data);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [entry, hasChanged]);
}, [entry]);
const debouncedRunUpdateCheck = useDebouncedCallback(runUpdateCheck, 500);
const debouncedRunUpdateCheck = useDebouncedCallback(runUpdateCheck, 200);
useEffect(() => {
debouncedRunUpdateCheck();

View File

@ -1,6 +1,6 @@
import React from 'react';
import { useData } from '@staticcms/core/lib';
import useData from '@staticcms/core/lib/hooks/useData';
import type { ValueOrNestedValue } from '@staticcms/core/interface';
import type { FC } from 'react';
@ -12,7 +12,6 @@ export interface DataProps {
const Data: FC<DataProps> = ({ path, value }) => {
const data = useData(value, path);
return <>{data}</>;
};

View File

@ -12,8 +12,7 @@ function moduleNameToPath(libName) {
return path.resolve(__dirname, '..', '..', 'node_modules', libName);
}
module.exports = {
entry: './src/index.ts',
const config = {
mode: isProduction ? 'production' : 'development',
devtool: 'source-map',
externals: isProduction
@ -107,15 +106,6 @@ module.exports = {
STATIC_CMS_CORE_VERSION: JSON.stringify(`${pkg.version}${isProduction ? '' : '-dev'}`),
}),
].filter(Boolean),
output: {
publicPath: '',
path: path.resolve(__dirname, 'dist'),
filename: 'static-cms-core.js',
library: {
name: 'StaticCmsCore',
type: 'umd',
},
},
devServer: {
static: {
directory: './dev-test',
@ -126,3 +116,33 @@ module.exports = {
hot: true,
},
};
const configLive = Object.assign({}, config, {
name: "configLive",
entry: './src/live/index.ts',
output: {
publicPath: '',
path: path.resolve(__dirname, 'dist'),
filename: 'live.js',
library: {
name: 'StaticCmsCoreLive',
type: 'umd',
},
}
});
const configMain = Object.assign({}, config, {
name: "configMain",
entry: './src/index.ts',
output: {
publicPath: '',
path: path.resolve(__dirname, 'dist'),
filename: 'static-cms-core.js',
library: {
name: 'StaticCmsCore',
type: 'umd',
},
}
});
module.exports = [configMain, configLive]