UNPKG

imagekit-media-library-widget

Version:
372 lines (319 loc) 13.6 kB
import { MediaLibraryWidgetOptions, MediaLibraryWidgetCallback, MediaLibraryWidgetOptionsExtended, InitialViewParameterEnum, } from './interfaces/index'; export class ImagekitMediaLibraryWidget { private IK_HOST: string = 'https://eml.imagekit.io'; private IK_FRAME_TITLE: string = 'ImageKit Embedded Media Library'; private callbackFunction: MediaLibraryWidgetCallback; private widgetHost: string; private view: string | undefined; private options: MediaLibraryWidgetOptionsExtended; private modal: HTMLDivElement | undefined; private ikFrame!: HTMLDivElement; private styleEl: HTMLStyleElement | undefined; private windowClickHandler: (event: MouseEvent) => void; private messageHandler: (event: MessageEvent) => void; private iframe: HTMLIFrameElement | undefined; private loadingOverlay: HTMLDivElement | undefined; private getDefaultOptions(): MediaLibraryWidgetOptionsExtended { return { className: "", container: "", containerDimensions: { height: '100%', width: '100%' }, dimensions: { height: '100%', width: '100%' }, style: { border: 'none' }, view: 'modal', renderOpenButton: true, }; } constructor(options: MediaLibraryWidgetOptions, callback: MediaLibraryWidgetCallback) { // Create global element references this.widgetHost = window.location.href; // Define option defaults this.options = this.getDefaultOptions(); // Create options by extending defaults with the passed in arguments if (options && typeof options === 'object') { Object.assign(this.options, options); } // Set callback function this.callbackFunction = callback && typeof callback === "function" ? callback : () => {}; this.view = this.options.view; // Initialize event handlers for later removal this.windowClickHandler = (event: MouseEvent) => { if (this.modal && event.target === this.modal) { this.close(); } }; this.messageHandler = (event: MessageEvent) => { if (event.origin !== this.IK_HOST) { return; } if (event.source !== this.iframe?.contentWindow) return; if (event.data.eventType === "CLOSE_MEDIA_LIBRARY_WIDGET" || event.data.eventType === "INSERT") { this.callbackFunction(event.data); this.close(); } }; this.registerStyles(); this.buildOut(); this.setListeners(); } private registerStyles(): void { this.styleEl = document.createElement('style'); this.styleEl.innerHTML = ` /* The Modal (background) */ .ik-media-library-widget-modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ padding-top: 2%; /* Location of the box */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ } /* Modal Content */ .ik-media-library-widget-modal-content { background-color: #fefefe; margin: auto; border: 1px solid #888; width: 96%; height: 94%; position: relative; } /* Loading overlay */ .ik-media-library-widget-loading-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.9); display: flex; align-items: center; justify-content: center; z-index: 10; } .ik-media-library-widget-loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: ik-media-library-widget-spin 1s linear infinite; } @keyframes ik-media-library-widget-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .ik-media-library-widget-loading-overlay.hidden { display: none; } `; document.head.appendChild(this.styleEl); } private buildOut(): void { let container: Element | null, docFragment: DocumentFragment, mainFrame: HTMLIFrameElement, button: HTMLButtonElement | undefined; // If container is an HTML string, find the DOM node, else use it directly. if (typeof this.options.container === "string") { container = document.querySelector(this.options.container); } else { container = this.options.container; } // Create a DocumentFragment to build with docFragment = document.createDocumentFragment(); // Create ikFrame element this.ikFrame = document.createElement("div") as HTMLDivElement & { callback: MediaLibraryWidgetCallback }; this.ikFrame.className = this.options.className || ""; // Assign an empty string as the default value this.ikFrame.style.height = this.options?.containerDimensions?.height || "100%"; this.ikFrame.style.width = this.options?.containerDimensions?.width || "100%"; mainFrame = document.createElement("iframe"); mainFrame.title = this.IK_FRAME_TITLE; mainFrame.src = this.generateInitialUrl(); mainFrame.setAttribute('sandbox', 'allow-top-navigation allow-same-origin allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads'); mainFrame.setAttribute('allow', 'clipboard-write'); mainFrame.height = this.options?.dimensions?.height || "100%"; mainFrame.width = this.options?.dimensions?.width || "100%"; mainFrame.style.border = this.options.style.border; this.iframe = mainFrame; this.ikFrame.appendChild(mainFrame); if (this.view?.toLowerCase() !== 'modal') { // Add relative positioning for loading overlay this.ikFrame.style.position = "relative"; // create loading overlay for inline view const loadingOverlay = document.createElement("div"); loadingOverlay.classList.add("ik-media-library-widget-loading-overlay", "hidden"); const spinner = document.createElement("div"); spinner.classList.add("ik-media-library-widget-loading-spinner"); loadingOverlay.appendChild(spinner); this.loadingOverlay = loadingOverlay; this.ikFrame.appendChild(loadingOverlay); // Append ikFrame to DocumentFragment docFragment.appendChild(this.ikFrame); // Append DocumentFragment to body if (container) container.appendChild(docFragment); } else { if (this.options.renderOpenButton) { // create button button = document.createElement("button"); button.innerHTML = "Open Media Library"; button.onclick = () => { if (this.modal) { this.modal.style.display = "block"; } } } // create modal const modal = document.createElement("div"); const modalContent = document.createElement("div"); modal.classList.add("ik-media-library-widget-modal"); modalContent.classList.add("ik-media-library-widget-modal-content"); // create loading overlay const loadingOverlay = document.createElement("div"); loadingOverlay.classList.add("ik-media-library-widget-loading-overlay", "hidden"); const spinner = document.createElement("div"); spinner.classList.add("ik-media-library-widget-loading-spinner"); loadingOverlay.appendChild(spinner); this.loadingOverlay = loadingOverlay; modalContent.appendChild(loadingOverlay); modalContent.appendChild(this.ikFrame); modal.appendChild(modalContent); this.modal = modal; // append button and modal to docFragment if (button && this.options.renderOpenButton) { docFragment.appendChild(button); } docFragment.appendChild(modal); // append docFragment to container if (container) container.appendChild(docFragment); } if (this.iframe) { this.setLoading(true); this.setupIframeLoadHandler(); } } private generateInitialUrl(): string { const baseUrl = `${this.IK_HOST}/media-library-widget`; const params = new URLSearchParams({ redirectTo: 'media-library-widget', isMediaLibraryWidget: 'true', widgetHost: this.widgetHost }); // Add initial view parameters if they exist if (this.options?.mlSettings?.initialView) { const key = Object.keys(this.options.mlSettings.initialView)[0]; if (Object.values(InitialViewParameterEnum).includes(key as InitialViewParameterEnum)) { params.append('mlWidgetInitialView', btoa(JSON.stringify(this.options.mlSettings.initialView))); } } // Add custom query parameters if they exist if (this.options?.mlSettings?.queryParams) { Object.entries(this.options.mlSettings.queryParams).forEach(([key, value]) => { params.append(key, String(value)); }); } // Add loginViaSSO if it exists if (this.options?.mlSettings?.loginViaSSO) { params.append('loginViaSSO', 'true'); } // Add widgetImagekitId if it exists if (this.options?.mlSettings?.widgetImagekitId) { params.append('widgetImagekitId', this.options.mlSettings.widgetImagekitId); } return `${baseUrl}?${params.toString()}`; } private setLoading(isLoading: boolean): void { if (!this.loadingOverlay) return; if (isLoading) { this.loadingOverlay.classList.remove("hidden"); if (this.iframe) { this.iframe.style.visibility = "hidden"; } } else { this.loadingOverlay.classList.add("hidden"); if (this.iframe) { this.iframe.style.visibility = "visible"; } } } private setupIframeLoadHandler() { if (this.iframe) { this.iframe.onload = () => { if (this.iframe && this.iframe.contentWindow) { this.iframe.contentWindow.postMessage(JSON.stringify({ mlSettings: this.options.mlSettings, }), this.IK_HOST); } this.setLoading(false); }; } } public open(settings?: Partial<Pick<MediaLibraryWidgetOptions, 'mlSettings'>>, callback?: MediaLibraryWidgetCallback): void { if (callback && typeof callback === "function") { this.callbackFunction = callback; } if (settings) { this.options.mlSettings = structuredClone(settings.mlSettings || {}); if (this.iframe) { this.setLoading(true); this.iframe.src = this.generateInitialUrl(); this.setupIframeLoadHandler(); } } if (this.view?.toLowerCase() === 'modal' && this.modal) { this.modal.style.display = "block"; } } private close(): void { if (this.view?.toLowerCase() === 'modal') { this.closeModal(); } } private closeModal(): void { if (this.modal) { this.modal.style.display = "none"; } } public destroy(): void { window.removeEventListener("click", this.windowClickHandler); window.removeEventListener("message", this.messageHandler); if (this.modal) { this.modal.remove(); this.modal = undefined; } else if (this.ikFrame && this.ikFrame.parentNode) { this.ikFrame.parentNode.removeChild(this.ikFrame); } if (this.styleEl) { this.styleEl.remove(); this.styleEl = undefined; } // Clear references this.iframe = undefined; this.loadingOverlay = undefined; } private setListeners(): void { window.addEventListener("click", this.windowClickHandler); window.addEventListener("message", this.messageHandler); } } declare global { interface Window { IKMediaLibraryWidget: typeof ImagekitMediaLibraryWidget; } } window.IKMediaLibraryWidget = ImagekitMediaLibraryWidget; export * from "./interfaces/index"