UNPKG

@agility/web-studio-sdk

Version:

Standard Development Kit used to enable Web Studio features in Agility CMS

264 lines (241 loc) 8.5 kB
import { initCSSAndPreviewPanel, initComponents, applyContentItem, getGuid, } from "./" import { getAbsolutePositionFromPercentage, getDeepestElementAtCoordinates, getRelativePercentage, getSelectorIndex, getUniqueSelector, } from "./commentUtils" import { dispatchSetCommentCoordsEvent, dispatchCommentDictionaryUpdatedEvent, dispatchReadyEvent, dispatchScrollEvent, dispatchWindowResizeEvent, IScrollEventArgs, dispatchDecoratorMapUpdatedEvent, } from "./frameEvents" import { generateDecoratorMap } from "./generateDecoratorMap" interface initializePreviewArgs { setIsInitialized: (state: boolean) => void } export interface IUpdatedCommentDictionary { [threadID: string]: { uniqueSelector?: string percentageOffsetX?: number percentageOffsetY?: number x?: number y?: number elementIndex?: number } } // throttle function to limit the number of times a function can be called const throttle = <T extends (...args: any[]) => void>( func: T, limit: number ) => { let lastFunc: ReturnType<typeof setTimeout> | undefined let lastRan: number | undefined return function (this: unknown, ...args: Parameters<T>) { const context = this // if we haven't run the function yet, run it and set the lastRan time if (!lastRan) { func.apply(context, args) lastRan = Date.now() } else { // if we have run the function, clear the lastFunc timeout and set a new one clearTimeout(lastFunc) lastFunc = setTimeout(function () { // if the time since the last ran is greater than the limit, run the function if (Date.now() - lastRan! >= limit) { func.apply(context, args) // set the lastRan time to now lastRan = Date.now() } }, limit - (Date.now() - lastRan)) } } as T } export const initializePreview = ({ setIsInitialized, }: initializePreviewArgs) => { //ONLY proceed if we are in an iframe with a legit parent // The parent window should be the PreviewIFrame if (!window.parent || !window.parent.postMessage) return if (window.self === window.top) return setIsInitialized(true) const agilityGuid = getGuid("initialize preview") //Add a listener for resize and scroll events on our window which will fire either the `sdk-window-resize` or `sdk-window-scroll` events to the parent window we will also need to debounce these events let resizeTimeout: any window.addEventListener("resize", () => { clearTimeout(resizeTimeout) resizeTimeout = setTimeout(() => { const args = { windowHeight: window.innerHeight, windowWidth: window.innerWidth, windowScrollableHeight: document.documentElement.scrollHeight, } dispatchWindowResizeEvent(args) }, 250) }) const throttledScrollHandler = throttle(() => { const args: IScrollEventArgs = { windowScrollableHeight: document.documentElement.scrollHeight, windowHeight: window.innerHeight, windowWidth: window.innerWidth, scrollY: window.scrollY, scrollX: window.scrollX, } dispatchScrollEvent(args) }, 10) window.addEventListener("scroll", throttledScrollHandler) window.addEventListener("message", ({ data }) => { const { source, messageType, guid, arg } = data //filter out the messages if (source !== "agility-instance" || guid !== agilityGuid) return switch (messageType) { case "ready": initCSSAndPreviewPanel() //set the components initComponents() //parse the page for fields const decoratorMap = generateDecoratorMap() if (decoratorMap) { dispatchDecoratorMapUpdatedEvent({ decoratorMap }) } break case "get-comment-coords": const { originX, originY, calcFallbackX, calcFallbackY, isDragEndEvent, threadId } = arg const element = document.elementFromPoint(originX, originY) if (!element){ console.warn("No element found at the specified coordinates.") return } const deepestEle = getDeepestElementAtCoordinates( element, originX, originY ) if (!deepestEle){ console.warn("Deepest element could not be found") return } const uniqueSelector = getUniqueSelector(deepestEle) const eleIndex = getSelectorIndex(uniqueSelector, originX, originY) const percentageCoords = getRelativePercentage( deepestEle, originX, originY ) dispatchSetCommentCoordsEvent({ uniqueSelector, percentageOffsetX: percentageCoords?.percentageX, percentageOffsetY: percentageCoords?.percentageY, elementIndex: eleIndex, isDragEndEvent: !!isDragEndEvent, threadId, originX, originY, calcFallbackX, calcFallbackY }) case "update-comment-dictionary": { const { commentDictionary } = arg // we've received the comment dictionary from the parent, it will be formatted as an ICommmentDictionary. We need to then map over each entry and go through the same process as the comment-create message event and then return an updated dictionary of type IUpdatedCommentDictionary to the parent with the uniqueSelector, offsetX and offsetY added to each entry const updatedCommentDictionary: IUpdatedCommentDictionary = {} if (!commentDictionary) { console.warn("Web Studio SDK - no comments to update") return } for (const [key, value] of Object.entries( commentDictionary as IUpdatedCommentDictionary )) { const { percentageOffsetX, percentageOffsetY, uniqueSelector, elementIndex, } = value updatedCommentDictionary[key] = value if (!uniqueSelector) { console.warn( "Web Studio SDK - could not find the unique selector for this comment" ) } else { let element = null if ( elementIndex === null || elementIndex === undefined || elementIndex === -1 ) { element = document.querySelector(uniqueSelector) } else { const elements = document.querySelectorAll(uniqueSelector) element = elements[elementIndex] } if (element) { const coords = getAbsolutePositionFromPercentage( element, percentageOffsetX, percentageOffsetY ) if (coords) { updatedCommentDictionary[key] = { ...updatedCommentDictionary[key], x: coords.x, y: coords.y, } } else { console.warn( "Web Studio SDK - could not find the absolute position from percentage" ) } } } } ///* */send the updated dictionary back to the parent dispatchCommentDictionaryUpdatedEvent({ updatedCommentDictionary }) } case "content-change": { const contentItem = arg applyContentItem(contentItem) break } case "refresh": setTimeout(() => { location.replace(location.href) }, 1000) break default: console.warn( "%cWeb Studio SDK\n Unknown message type on website:", "font-weight: bold", messageType, arg ) break } }) // if we have the width, height and url of our window, and we're ready send it to the parent const windowWidth = window.innerWidth const windowHeight = window.outerHeight // check if the document has loaded with any of the data-agility attributes const hasPageDecorators = document.querySelector("[data-agility-page]") const hasComponentDecorators = document.querySelector( "[data-agility-component]" ) const hasFieldDecorators = document.querySelector("[data-agility-field]") dispatchReadyEvent({ windowWidth, windowHeight, hasPageDecorators: !!hasPageDecorators, hasComponentDecorators: !!hasComponentDecorators, hasFieldDecorators: !!hasFieldDecorators, windowScrollableHeight: document.documentElement.scrollHeight, }) }