imagekit-media-library-widget
Version:
Javascript plugin for using Imagekit Media Library Widget
372 lines (319 loc) • 13.6 kB
text/typescript
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;
}
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"