From b4c4055b6c3691bad51fce9b58ff75cc1ec54220 Mon Sep 17 00:00:00 2001 From: devAyushDubey Date: Wed, 8 May 2024 15:35:56 +0530 Subject: [PATCH 1/2] Introduced EmptyBlocks for handling empty block entries --- src/liveEditor/components/emptyBlock.tsx | 43 +++++++++++++++++++ .../generators/generateEmptyBlock.tsx | 37 ++++++++++++++++ src/liveEditor/index.ts | 36 +++++++++++++++- src/liveEditor/listeners/mouseClick.ts | 25 ++++++----- src/styles.css | 34 +++++++++++++++ 5 files changed, 163 insertions(+), 12 deletions(-) create mode 100644 src/liveEditor/components/emptyBlock.tsx create mode 100644 src/liveEditor/generators/generateEmptyBlock.tsx diff --git a/src/liveEditor/components/emptyBlock.tsx b/src/liveEditor/components/emptyBlock.tsx new file mode 100644 index 0000000..9def01f --- /dev/null +++ b/src/liveEditor/components/emptyBlock.tsx @@ -0,0 +1,43 @@ +import { CslpData } from "../../cslp/types/cslp.types"; +import { VisualEditorCslpEventDetails } from "../types/liveEditor.types"; +import liveEditorPostMessage from "../utils/liveEditorPostMessage"; +import { LiveEditorPostMessageEvents } from "../utils/types/postMessage.types"; + +interface EmptyBlockProps { + details: { + editableElement: Element; + cslpData: string; + fieldMetadata: CslpData; + } +} + +export function EmptyBlock(props: EmptyBlockProps) { + + const { details } = props + + const blockParentName = details.fieldMetadata.fieldPath.split('.').at(-1)!.split('_').join(' ') + const capitalizedBlockParentName = blockParentName.replace(/\b[a-z]/g, (letter: string) => { + return letter.toUpperCase(); + }); + + function sendAddInstanceEvent() { + liveEditorPostMessage?.send( + LiveEditorPostMessageEvents.ADD_INSTANCE, + { + fieldMetadata: details.fieldMetadata, + index: 0, + } + ); + } + + return ( +
+
+ There are no {blockParentName} to show in this section. +
+ +
); +} \ No newline at end of file diff --git a/src/liveEditor/generators/generateEmptyBlock.tsx b/src/liveEditor/generators/generateEmptyBlock.tsx new file mode 100644 index 0000000..7e22402 --- /dev/null +++ b/src/liveEditor/generators/generateEmptyBlock.tsx @@ -0,0 +1,37 @@ +import { hydrate } from "preact"; +import { EmptyBlock } from "../components/emptyBlock"; +import { extractDetailsFromCslp } from "../../cslp"; + + +export function generateEmptyBlocks( + emptyBlockParents: Element[] | [] +): void { + + emptyBlockParents?.forEach((emptyBlockParent) => { + + const cslpData = emptyBlockParent.getAttribute("data-cslp"); + if (!cslpData) { + return; + } + const fieldMetadata = extractDetailsFromCslp(cslpData); + + hydrate(, emptyBlockParent); + }); +} + +export function removeEmptyBlocks( + emptyBlockParents: Element[] | [] +): void { + emptyBlockParents?.forEach((emptyBlockParent) => { + + const emptyBlock = emptyBlockParent.querySelector(".visual-editor__empty-block"); + + if (emptyBlock) { + emptyBlock.remove(); + } + }) +} \ No newline at end of file diff --git a/src/liveEditor/index.ts b/src/liveEditor/index.ts index b4ba213..8cb847a 100644 --- a/src/liveEditor/index.ts +++ b/src/liveEditor/index.ts @@ -20,10 +20,13 @@ import { LiveEditorPostMessageEvents } from "./utils/types/postMessage.types"; import initUI from "./components"; import { addEventListeners, removeEventListeners } from "./listeners"; +import { generateEmptyBlocks, removeEmptyBlocks } from "./generators/generateEmptyBlock"; +import { debounce, isEqual } from "lodash-es"; interface VisualEditorGlobalStateImpl { previousSelectedEditableDOM: HTMLElement | Element | null; previousHoveredTargetDOM: Element | null; + previousEmptyBlockParents: Element[] | []; } export class VisualEditor { @@ -36,6 +39,7 @@ export class VisualEditor { signal({ previousSelectedEditableDOM: null, previousHoveredTargetDOM: null, + previousEmptyBlockParents: [] }); private resizeObserver = new ResizeObserver(([entry]) => { @@ -61,6 +65,30 @@ export class VisualEditor { ); }); + private mutationObserver = new MutationObserver(debounce(() => { + + const emptyBlockParents = Array.from(document.querySelectorAll( + ".visual-editor__empty-block-parent" + )); + + const previousEmptyBlockParents = VisualEditor.VisualEditorGlobalState.value.previousEmptyBlockParents as Element[]; + + if(!isEqual(emptyBlockParents, previousEmptyBlockParents)) { + + const noMoreEmptyBlockParent = previousEmptyBlockParents.filter(x => !emptyBlockParents.includes(x)); + const newEmptyBlockParent = emptyBlockParents.filter(x => !previousEmptyBlockParents.includes(x)); + + removeEmptyBlocks(noMoreEmptyBlockParent); + generateEmptyBlocks(newEmptyBlockParent); + + VisualEditor.VisualEditorGlobalState.value = { + ...VisualEditor.VisualEditorGlobalState.value, + previousEmptyBlockParents: emptyBlockParents + }; + } + }, 100, { trailing: true })); + + constructor() { initUI({ resizeObserver: this.resizeObserver, @@ -109,11 +137,16 @@ export class VisualEditor { customCursor: this.customCursor, }); + this.mutationObserver.observe(document.body , { + childList: true, + subtree: true, + }); + liveEditorPostMessage?.on( LiveEditorPostMessageEvents.GET_ENTRY_UID_IN_CURRENT_PAGE, getEntryUidFromCurrentPage ); - + // These events are used to sync the data when we made some changes in the entry without invoking live preview module. useHistoryPostMessageEvent(); useOnEntryUpdatePostMessageEvent(); @@ -138,6 +171,7 @@ export class VisualEditor { customCursor: this.customCursor, }); this.resizeObserver.disconnect(); + this.mutationObserver.disconnect(); if (this.visualEditorContainer) { window.document.body.removeChild(this.visualEditorContainer); diff --git a/src/liveEditor/listeners/mouseClick.ts b/src/liveEditor/listeners/mouseClick.ts index 2fe50d3..3250ca0 100644 --- a/src/liveEditor/listeners/mouseClick.ts +++ b/src/liveEditor/listeners/mouseClick.ts @@ -87,17 +87,20 @@ async function handleEditorInteraction( }); } - addOverlay({ - overlayWrapper: params.overlayWrapper, - resizeObserver: params.resizeObserver, - editableElement: editableElement, - }); - - addFocusedToolbar({ - eventDetails: eventDetails, - focusedToolbar: params.focusedToolbar, - }); - + if(!editableElement.classList.contains('visual-editor__empty-block-parent') + && !editableElement.classList.contains('visual-editor__empty-block')){ + addOverlay({ + overlayWrapper: params.overlayWrapper, + resizeObserver: params.resizeObserver, + editableElement: editableElement, + }); + + addFocusedToolbar({ + eventDetails: eventDetails, + focusedToolbar: params.focusedToolbar, + }); + } + liveEditorPostMessage?.send(LiveEditorPostMessageEvents.FOCUS_FIELD, { DOMEditStack: getDOMEditStack(editableElement), }); diff --git a/src/styles.css b/src/styles.css index 06e01e0..a090071 100644 --- a/src/styles.css +++ b/src/styles.css @@ -416,3 +416,37 @@ div.multiple div.cslp-tooltip-child:hover:before { display: none; } + +.visual-editor__empty-block { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 1rem; + min-height: 100px; + } + + .visual-editor__empty-block-title { + font-size: 0.95rem; + font-family: Inter; + font-weight: 400; + line-height: 100%; + color: #647696; + } + + .visual-editor__empty-block-add-button { + height: 32px; + border-radius: 4px; + background: #F9F8FF; + border-color: #6C5CE7; + border-width: 1px; + padding: 8px 16px 8px 16px; + font-size: 0.9rem; + font-family: Inter; + font-weight: 600; + color: #6C5CE7; + padding-block: 0px; + letter-spacing: 0.01rem; + } From a3b0b22254c293637cc9b016132a88b6c0b18f84 Mon Sep 17 00:00:00 2001 From: devAyushDubey Date: Thu, 9 May 2024 12:31:33 +0530 Subject: [PATCH 2/2] Get emptyBlockParent from fieldSchema --- src/liveEditor/components/emptyBlock.tsx | 16 ++++++---------- .../generators/generateEmptyBlock.tsx | 18 ++++++++++++------ src/liveEditor/index.ts | 4 ++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/liveEditor/components/emptyBlock.tsx b/src/liveEditor/components/emptyBlock.tsx index 9def01f..4cd5a2b 100644 --- a/src/liveEditor/components/emptyBlock.tsx +++ b/src/liveEditor/components/emptyBlock.tsx @@ -1,24 +1,20 @@ import { CslpData } from "../../cslp/types/cslp.types"; -import { VisualEditorCslpEventDetails } from "../types/liveEditor.types"; import liveEditorPostMessage from "../utils/liveEditorPostMessage"; +import { ISchemaFieldMap } from "../utils/types/index.types"; import { LiveEditorPostMessageEvents } from "../utils/types/postMessage.types"; interface EmptyBlockProps { details: { - editableElement: Element; - cslpData: string; fieldMetadata: CslpData; + fieldSchema: ISchemaFieldMap; } } export function EmptyBlock(props: EmptyBlockProps) { - + const { details } = props - const blockParentName = details.fieldMetadata.fieldPath.split('.').at(-1)!.split('_').join(' ') - const capitalizedBlockParentName = blockParentName.replace(/\b[a-z]/g, (letter: string) => { - return letter.toUpperCase(); - }); + const blockParentName = details.fieldSchema.display_name; function sendAddInstanceEvent() { liveEditorPostMessage?.send( @@ -33,11 +29,11 @@ export function EmptyBlock(props: EmptyBlockProps) { return (
- There are no {blockParentName} to show in this section. + There are no {blockParentName.toLowerCase()} to show in this section.
); } \ No newline at end of file diff --git a/src/liveEditor/generators/generateEmptyBlock.tsx b/src/liveEditor/generators/generateEmptyBlock.tsx index 7e22402..5164c4d 100644 --- a/src/liveEditor/generators/generateEmptyBlock.tsx +++ b/src/liveEditor/generators/generateEmptyBlock.tsx @@ -1,13 +1,14 @@ import { hydrate } from "preact"; import { EmptyBlock } from "../components/emptyBlock"; import { extractDetailsFromCslp } from "../../cslp"; +import { FieldSchemaMap } from "../utils/fieldSchemaMap"; -export function generateEmptyBlocks( +export async function generateEmptyBlocks( emptyBlockParents: Element[] | [] -): void { +): Promise { - emptyBlockParents?.forEach((emptyBlockParent) => { + for (const emptyBlockParent of emptyBlockParents) { const cslpData = emptyBlockParent.getAttribute("data-cslp"); if (!cslpData) { @@ -15,12 +16,17 @@ export function generateEmptyBlocks( } const fieldMetadata = extractDetailsFromCslp(cslpData); + const fieldSchema = await FieldSchemaMap.getFieldSchema( + fieldMetadata.content_type_uid, + fieldMetadata.fieldPath + ); + hydrate(, emptyBlockParent); - }); + + } } export function removeEmptyBlocks( diff --git a/src/liveEditor/index.ts b/src/liveEditor/index.ts index 8cb847a..c5ef204 100644 --- a/src/liveEditor/index.ts +++ b/src/liveEditor/index.ts @@ -65,7 +65,7 @@ export class VisualEditor { ); }); - private mutationObserver = new MutationObserver(debounce(() => { + private mutationObserver = new MutationObserver(debounce(async () => { const emptyBlockParents = Array.from(document.querySelectorAll( ".visual-editor__empty-block-parent" @@ -79,7 +79,7 @@ export class VisualEditor { const newEmptyBlockParent = emptyBlockParents.filter(x => !previousEmptyBlockParents.includes(x)); removeEmptyBlocks(noMoreEmptyBlockParent); - generateEmptyBlocks(newEmptyBlockParent); + await generateEmptyBlocks(newEmptyBlockParent); VisualEditor.VisualEditorGlobalState.value = { ...VisualEditor.VisualEditorGlobalState.value,