Plugin UI
A plugin can show a floating panel with your own HTML. Users can drag it by its title bar and resize it from the corner; closing it stops the plugin.
Setup
Add "ui": "ui.html" to the manifest, then open the panel from main.js:
await popcraft.showUI({ width: 280, height: 200, title: 'Rename layers' })ui.html can be a full document or a fragment. Put styles and scripts inline — the package has no web server, so
relative <script src> / <link href> files aren't loaded. Images and fonts must be inline data: URLs unless
the plugin has the network permission.
Messaging
The panel and the main script can't call each other directly; they exchange messages.
In ui.html a popcraft object is available:
| API | |
|---|---|
popcraft.postMessage(message) | Send a message to the main script |
popcraft.onmessage = (message) => {} | Receive messages from the main script |
popcraft.close() | Close the panel (the plugin keeps running) |
Messages also arrive as window message events with the payload in event.data.pluginMessage, so code written
for that convention works too.
In main.js: popcraft.ui.postMessage(message) and popcraft.ui.onmessage = (message) => {}.
The panel's API calls are limited to messaging: to read or change the file, send a message to the main script and let it call the API.
Example: rename layers
Description
A brainstorm board with its eight sticky notes selected and Rename Layers installed. Run opens the plugin’s own panel beside the canvas, a sandboxed page that reads the selection. The pattern is set to “Idea #” and Rename renames all eight layers, which the layers panel shows.
main.js
(async () => {
await popcraft.showUI({ width: 280, height: 190, title: 'Rename layers' })
const lastPattern = (await popcraft.storage.get('pattern')) || 'Layer #'
const sendSelection = async () => {
const selection = await popcraft.getSelection()
popcraft.ui.postMessage({ type: 'selection', count: selection.length, pattern: lastPattern })
}
popcraft.on('selectionchange', sendSelection)
popcraft.ui.onmessage = async (msg) => {
if (msg.type === 'ready') return sendSelection()
if (msg.type === 'cancel') return popcraft.closePlugin()
if (msg.type === 'rename') {
const selection = await popcraft.getSelection()
let i = Number(msg.start) || 1
for (const node of selection) {
await popcraft.updateNode(node.id, { name: String(msg.pattern).replace(/#/g, String(i++)) })
}
await popcraft.storage.set('pattern', msg.pattern)
await popcraft.notify(`Renamed ${selection.length} layer(s)`)
}
}
})()ui.html
<!doctype html>
<html>
<head>
<style>
body { font: 12px system-ui, sans-serif; margin: 12px; color: #222; }
label { display: block; margin-bottom: 8px; }
input { width: 100%; box-sizing: border-box; padding: 6px; border: 1px solid #ccc; border-radius: 6px; margin-top: 4px; }
.row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 10px; }
button { padding: 6px 12px; border-radius: 6px; border: 1px solid #ccc; background: #fff; }
button.primary { background: #0c8ce9; color: #fff; border-color: #0c8ce9; }
</style>
</head>
<body>
<label>Pattern <input id="pattern" value="Layer #"></label>
<label>Start at <input id="start" type="number" value="1"></label>
<div id="count">No selection</div>
<div class="row">
<button id="cancel">Close</button>
<button id="rename" class="primary">Rename</button>
</div>
<script>
popcraft.onmessage = (msg) => {
if (msg.type === 'selection') {
document.getElementById('count').textContent = msg.count ? `${msg.count} layer(s) selected` : 'No selection'
}
}
document.getElementById('rename').onclick = () => popcraft.postMessage({
type: 'rename',
pattern: document.getElementById('pattern').value,
start: document.getElementById('start').value,
})
document.getElementById('cancel').onclick = () => popcraft.postMessage({ type: 'cancel' })
popcraft.postMessage({ type: 'ready' })
</script>
</body>
</html>Tips
- Send a
readymessage from the panel and reply with initial data — the panel may load before or after your main script sets its handler (messages are queued either way, but this keeps the flow explicit). - The panel background is white by default; set your own colors to match light or dark designs.
- Keep heavy work in
main.jsand send results to the panel.