Skip to content

Instantly share code, notes, and snippets.

@kerrishotts
Last active February 4, 2019 00:47
Show Gist options
  • Save kerrishotts/63051639d191dad49992ae3ebe900d77 to your computer and use it in GitHub Desktop.
Save kerrishotts/63051639d191dad49992ae3ebe900d77 to your computer and use it in GitHub Desktop.
XD Plugin Lab: Part Four, Step 2: File I/O
/*
* Sample plugin code for Adobe XD.
*
* Visit http://adobexdplatform.com/ for API docs and more sample code.
*/
const fs = require("uxp").storage.localFileSystem;
var {Rectangle, Color} = require("scenegraph");
// global settings object
let settings = {};
// file I/O is async, so these functions will be async as well
async function loadSettings() {
// get the plugin's data folder, where we can both
// save and load information
const folder = await fs.getDataFolder();
try {
const file = await folder.getEntry("settings.json");
settings = JSON.parse(await file.read());
console.log(`Loaded settings, ${JSON.stringify(settings)}`)
} catch (err) {
console.log("No settings; using default");
settings = { h: 20, v: 20 };
}
}
async function saveSettings() {
const folder = await fs.getDataFolder();
try {
// Have to set `overwrite: true`, or we'd get
// an error when we tried to create the file
const file = await folder.createFile("settings.json", {
overwrite: true
});
await file.write(JSON.stringify(settings));
console.log("Saved settings");
} catch (err) {
/* oops? */
console.log("Hmm. Something went wrong.");
}
}
function createRectangle(selection, hPadding, vPadding) {
var textNode = selection.items[0];
var bounds = textNode.boundsInParent;
console.log(bounds);
var shape = new Rectangle();
shape.width = bounds.width + 2 * hPadding
shape.height = bounds.height + 2 * vPadding;
shape.stroke = new Color("blue");
selection.insertionParent.addChild(shape);
shape.placeInParentCoordinates(
{x: 0, y: 0},
{x: bounds.x - hPadding, y: bounds.y - vPadding}
);
}
let dialog;
function createDialog() {
dialog = document.createElement("dialog");
// We add a checkbox that lets us save settings
dialog.innerHTML = `
<style>
form {
width: 400px;
}
.row {
display: flex;
align-items: center;
}
</style>
<form method="dialog">
<h1>Create Button Frame</h1>
<hr />
<div class="row">
<label class="row">
<span>V:</span>
<input type="text" uxp-quiet="true" id="txtV" placeholder="V padding" />
</label>
<label class="row">
<span>H:</span>
<input type="text" uxp-quiet="true" id="txtH" placeholder="H padding" />
</label>
</div>
<footer>
<label class="row">
<input type="checkbox" id="save"/>
<span>Save as default</span>
</label>
<button id="cancel" type="reset" uxp-variant="secondary">Cancel</button>
<button id="ok" type="submit" uxp-variant="cta">Apply</button>
</footer>
</form>`;
// make sure the cancel button works :-)
dialog.querySelector("#cancel").addEventListener("click", () => {
dialog.close("reasonCanceled");
})
dialog.querySelector("#txtV").value = settings.v;
dialog.querySelector("#txtH").value = settings.h;
document.appendChild(dialog);
}
async function showDialog() {
if (!dialog) {
// load our defaults first
await loadSettings();
// then create the dialog
createDialog();
}
const response = await dialog.showModal();
if (response !== 'reasonCanceled') {
// user wants to create the rectangle!
const { selection } = require("scenegraph");
// You can extract the values the user entered from the DOM
// using querySelector and friends
// NOTE: we should, of course, do validation here
// but that's out of scope for now.
const hPadding = Number(dialog.querySelector("#txtH").value);
const vPadding = Number(dialog.querySelector("#txtV").value);
// and now allow saving of our settings!
const saveAsDefault = dialog.querySelector("#save").checked;
if (saveAsDefault) {
settings = {
h: hPadding,
v: vPadding
};
await saveSettings();
}
createRectangle(selection, hPadding, vPadding);
}
}
module.exports = {
commands: {
myPluginCommand: showDialog
}
};
{
"name": "Sample Plugin: Create Button Frame",
"id": "YOUR_ID_HERE",
"version": "1.0.0",
"description": "Sample plugin code for Adobe XD. Creates a rectangle around the selected text, with fixed padding.",
"host": {
"app": "XD",
"minVersion": "13.0.0"
},
"uiEntryPoints": [
{
"type": "menu",
"label": "Create Button Frame",
"commandId": "myPluginCommand"
}
]
}

XD Plugin Lab: Part Four, Step 2: File I/O

Note: This is part of a longer series.

This demonstrates how File I/O works in XD. File I/O is all asynchronous, and so you'll need to use promises or async/await.

Sections to note in main.js:

  • Line 7: Access to the file system is via UXP's storage module.
  • Line 11: We're going to store settings globally.
  • Lines 14-26: loadSettings function. Note that it is async and inside we use await when interacting with the file system.
  • Line 17: getDataFolder returns a folder to which we have read/write access without the need for user interaction. It's specific to each plugin.
  • Line 19: getEntry returns the entry matching settings.json. If it doesn't exist, an error is thrown, which means we use the default settings in lines 23-24.
  • Line 20: file.read reads the data as a string. There are also binary modes if you need them.
  • Lines 28-42: saveSettings function. Also async.
  • Line 33-35: Instead of getting an entry, we create one with overwrite: true. This is important if the file already exists, as without specifying overwrite, an error would be thrown instead.
  • Line 36: We write the serialized settings to the file. Binary options are also available.
  • Lines 91-94: We add a "[ ] Save as default" checkbox to the UI so the user can decide when to save their settings
  • Lines 105-106: We set the default padding to what's in the user's settings -- we only do this once when the dialog is created (from line 117)
  • Line 114: Load settings -- note the await so that we don't do anything until we have the settings loaded.
  • Lines 134-141: If the checkbox is checked, save the settings to our global variable and then call saveSettings -- note, again, the await here. (Technically we could skip the await; saving could happen after we create the rectangle too.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment