iFrames
The Extension API allows you to display custom UI in a sandboxed iframe. The UI displayed in these iframes can communicate with your extension code via asynchronous message passing. You can define custom UI by extending the Modal or Panel class.
Modal Example
To display a simple modal, extend the Modal class, passing configuration to the super constructor:
import {EditorClient, Menu, MenuType, Modal} from 'lucid-extension-sdk';
class HelloWorldModal extends Modal {
constructor(client: EditorClient) {
super(client, {
title: 'Hello world',
width: 400,
height: 300,
content: 'Hello from a modal',
});
}
}
const client = new EditorClient();
const menu = new Menu(client);
client.registerAction('hello', () => {
const modal = new HelloWorldModal(client);
modal.show();
});
menu.addDropdownMenuItem({
label: 'Say Hello',
action: 'hello',
});
Panel Example
To add a simple panel to the right dock, extend the Panel class, passing configuration to the super constructor. Note that in this case, the HelloWorldPanel
is constructed during the initial execution of the extension code, since it will live for the duration of the editor session, unlike the HelloWorldModal
above that is used and then discarded when it is closed.
The iconUrl
specified here can be any image, but it will be displayed as 24x24 CSS pixels. Consider using a base64-encoded image URL for this icon to avoid any image loading delay:
import {EditorClient, Panel, PanelLocation} from 'lucid-extension-sdk';
class HelloWorldPanel extends Panel {
constructor(client: EditorClient) {
super(client, {
title: 'Hello world',
iconUrl: 'https://cdn-cashy-static-assets.lucidchart.com/marketing/images/LucidSoftwareFavicon.png',
location: PanelLocation.RightDock,
url: 'hello.html',
});
}
}
const client = new EditorClient();
const panel = new HelloWorldPanel(client);
Persistent panels
Lucid may destroy the panel iframe and recreate it when needed for optimal application performance. Therefore, if you are developing an extension such as a video conference tool where your code needs to run continuously in the background, you can prevent this behavior by using the persist: true
option in conjunction with location: PanelLocation.RightDock
:
class PersistentPanel extends Panel {
constructor(client: EditorClient) {
super(client, {
title: 'Hello world',
content: 'Hello from a panel',
iconUrl: 'https://cdn-cashy-static-assets.lucidchart.com/marketing/images/LucidSoftwareFavicon.png',
location: PanelLocation.RightDock,
persist: true,
});
}
}
Specifying content
Besides just specifying the content
string, the best way to package HTML content into your extension is by providing a url
string in either a Modal
or Panel
constructor.
If you don't already have a folder named public
at the root of your extension package, you'll need to create one:
> my-package
> editorextensions
└── ...
> shapelibraries
└── ...
> dataconnectors
└── ...
> public
└── img
└── ...
└── index.html
└── .gitignore
└── manifest.json
This allows you to reference the HTML file relative to the public
directory:
import {EditorClient, Modal} from 'lucid-extension-sdk';
export class ImportModal extends Modal {
constructor(client: EditorClient) {
super(client, {
title: 'Import a thing',
width: 600,
height: 400,
url: 'index.html',
});
}
}
To access static resources in the public/img
folder, you can use relative URLs in your HTML such as <img src="img/example.png">
.
The iframe is sandboxed, but allows the following:
This means your content may include scripts, external images, and so forth. Each extension is loaded on a unique
origin, so the allow-same-origin
permission means you get access to browser APIs like localStorage and
IndexedDB.
External content
Although you can, you do not need to bundle the entire UI application into the content
of a Modal
or Panel
.
Your HTML content can refer to JavaScript and CSS resources that you host elsewhere. These resources can also be derived from a framework, such as Angular or React.
If you wish to serve the entire iframe from an external URL, you just need to pass in the external URL as url
in the Modal
or Panel
constructor:
export class ContentFromElsewhereModal extends Modal {
constructor(client: EditorClient) {
super(client, {
title: 'Loading up something else',
width: 600,
height: 400,
url: 'https://www.example.com',
});
}
}
Specifying content using a content string (Legacy)
The reason why this approach is not favored is that it needs to inline all static resources using data URLs. Consequently, the bundle size becomes excessively large, leading to potential issues with slow parsing and execution.
The content
string passed to either a Modal
or Panel
constructor is set as the full content of the iframe.
That means it will typically include an <html>
tag.
To package HTML content into your extension as a content string, you can use a TypeScript import
as a string. By default, a new editor extension includes a resources
directory with the following resource.d.ts
:
declare module '*.html' {
const content: string;
export default content;
}
This indicates that any .html
file in that directory will be treated as a string when imported. The tsconfig.json
file references that file as well so the compiler knows to treat those files correctly:
{
// ...
"files":[
"resources/resource.d.ts",
],
}
This allows you to import the whole HTML file and use it as the content string like this:
import {EditorClient, Modal} from 'lucid-extension-sdk';
import importHtml from '../resources/import.html';
export class ImportModal extends Modal {
constructor(client: EditorClient) {
super(client, {
title: 'Import a thing',
width: 600,
height: 400,
content: importHtml,
});
}
}
Communicating with iframes
You can communicate with your iframe via message passing. To send a message to your iframe code, call sendMessage on your Modal
or Panel
subclass. You can pass any JSON-serializable value, and that value will be sent to your iframe with Window.postMessage. You can listen for these messages using the normal browser API, with your message being stored in the event object's .data
:
window.addEventListener('message', (e) => {
console.log(e.data);
});
To send a message from your iframe code to your Modal
or Panel
, call parent.postMessage
like this:
parent.postMessage({
a: 'hello',
b: 'world',
title: 'My Page'
}, '*');
The previous examples use generic javascript. If you decide to use a framework (Angular, React, etc.) for your content, how you pass or listen to messages might differ. Regardless, the code written in your
Modal
orPanel
subclass does not need to change.
Any message you pass into parent.postMessage
will be received in your Modal
's or Panel
's implementation of messageFromFrame. From here, you can call any method in the extension-sdk or from your own code. For example, the following code has the message logged to the console, sets the page title, and then closes the modal or panel:
protected messageFromFrame(message: JsonSerializable): void {
console.log(message['a']);
console.log(message['b']);
this.page.setTitle(message['title'])
this.hide();
}
Lucid styles
You can utilize similar styles of elements that appear in Lucid products in your custom UI by linking the stylesheet at https://lucid.app/public-styles.css, which provides the most up to date styles.
Currently you can style buttons and text fields with Lucid's style patterns:
<html>
<head>
<link type="text/css" rel="stylesheet" href="https://lucid.app/public-styles.css">
</head>
<body>
<button class="lucid-styling primary">primary</button>
<button class="lucid-styling secondary">secondary</button>
<button class="lucid-styling tertiary">tertiary</button>
<input
type="text"
class="lucid-styling"
>
</body>
</html>
Updated 8 months ago