Plug-In: Add Static Map
Requires OmniFocus 4
DISCLAIMER: Mention of third-party websites and products is for informational purposes only and constitutes neither an endorsement nor a recommendation. OMNI-AUTOMATION.COM assumes no responsibility with regard to the selection, performance or use of information or products found at third-party websites. OMNI-AUTOMATION.COM provides this only as a convenience to our users. OMNI-AUTOMATION.COM has not tested the information found on these sites and makes no representations regarding its accuracy or reliability. There are risks inherent in the use of any information or products found on the Internet, and OMNI-AUTOMATION.COM assumes no responsibility in this regard. Please understand that a third-party site is independent from OMNI-AUTOMATION.COM and that OMNI-AUTOMATION.COM has no control over the content on that website. Please contact the vendor for additional information.
This plug-in uses the online Mapbox service to generate and add a static map image using the first map URL stored in the notes of the selected project or task.
When the plug-in is launched for the first time, you will be prompted to enter your Mapbox customer API key. This API key is securely stored in the system keychain, and will not be displayed during the execution of plug-in which uses HTTPS to encrypt the Mapbox URL during transmission.
To reset the stored API key, hold down the Control key when launching the plug-in.
TIP: The Shortcut workflow shown in the following video run on an Watch, can be installed with this link.
Video 1: Text Objects, Style Attributes, and Inline Attachments |
Example: Adding static map to a note |
|
Return to: OmniFocus Plug-In Collection
Add Static Map for URL
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.add-mapbox-image",
"version": "1.1",
"description": "This plug-in uses the online Mapbox service (www.mapbox.com) to generate and add a static map image using the first map URL stored in the notes of the selected project or task.",
"label": "Add Map from URL",
"shortLabel": "Add Map",
"paletteLabel": "Add Map",
"image": "mappin.and.ellipse"
}*/
(() => {
var preferences = new Preferences()
var credentials = new Credentials()
var serviceTitle = "Mapbox"
var shouldLog = false
const requestCredentials = async () => {
try {
OSname = app.platformName
// CREATE FORM FOR GATHERING ACCOUNT API KEY
credentialsForm = new Form()
// CREATE TEXT FIELD
keyInputField = new Form.Field.String(
"APIKey",
null,
null
)
if(OSname !== "macOS"){
keyInputField.autocapitalizationType = TextAutocapitalizationType.None
}
credentialsForm.addField(keyInputField)
// VALIDATE THE USER INPUT
credentialsForm.validate = formObject => {
APIKey = formObject.values["APIKey"]
return (APIKey && APIKey.length > 0) ? true:false
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter Mapbox API Key:"
formObject = await credentialsForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
APIKey = formObject.values["APIKey"]
// STORE THE VALUES
credentials.write(serviceTitle, "Mapbox API Key", APIKey)
// RETRIEVE CREDENTIALS OBJECT
alertMsg = "The API key has been stored in the system keychain."
alertTitle = "Confirmation"
new Alert(alertTitle, alertMsg).show()
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async mapboxURL => {
try {
if(shouldLog){console.log("FETCH", mapboxURL)}
item = document.windows[0].selection.databaseObjects[0]
noteObj = item.noteText
nStyle = noteObj.style
targetURLString = mapboxURL
request = URL.FetchRequest.fromString(targetURLString)
request.headers = {"Content-Type":"application/json"}
request.method = 'GET'
response = await request.fetch()
responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
wrapper = FileWrapper.withContents("static-map.png", response.bodyData)
attachmentObj = Text.makeFileAttachment(wrapper, noteObj.style)
if(noteObj.range.isEmpty){
noteObj.insert(noteObj.start, attachmentObj)
} else {
newLineObj = new Text('\n', noteObj.style)
noteObj.append(newLineObj, noteObj.style)
noteObj.append(newLineObj, noteObj.style)
noteObj.append(attachmentObj, noteObj.style)
}
node = document.windows[0].content.selectedNodes[0]
node.expandNote(false)
} else {
throw {
name: "Server Response Code",
message: `The server responded with code: ${responseCode}`
}
}
}
catch(err){
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// HOLD DOWN CONTROL KEY TO RESET VALUES
if (app.controlKeyDown){
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
throw {
name: "Missing Credentials",
message: "There is no stored Mapbox API key to remove."
}
} else {
alertMessage = "Remove the stored Mapbox API key?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
if(shouldLog){console.log(`Removing Service “${serviceTitle}”`)}
credentials.remove(serviceTitle)
if(shouldLog){console.log(`Service “${serviceTitle}” Removed`)}
}
})
}
} else {
credentialsObj = credentials.read(serviceTitle)
if(shouldLog){console.log("# CREDENTIALS READ")}
if (credentialsObj){
APIToken = credentialsObj["password"]
// LOCATE FIRST MAP URL IN NOTE
item = selection.databaseObjects[0]
noteObj = item.noteText
runRanges = noteObj.ranges(TextComponent.AttributeRuns)
mapLinkStr = null
for (rangeIndx in runRanges){
aRange = runRanges[rangeIndx]
runStyle = noteObj.styleForRange(aRange)
linkStr = runStyle.get(Style.Attribute.Link).string
if(linkStr.length > 0 && linkStr.includes("&ll=")){
if(shouldLog){console.log("Map URL", linkStr)}
mapLinkStr = linkStr
break
}
}
if(!mapLinkStr){
throw {
name: "Missing URL",
message: "The note contains no map links."
}
}
// ETRACT THE COORDS FROM THE MAP LINK
urlComps = URL.Components.fromString(mapLinkStr)
queryItems = urlComps.queryItems
coordsStr = null
for (itemIndex in queryItems){
queryItem = queryItems[itemIndex]
if(queryItem.name === "ll"){
coordsStr = queryItem.value
if(shouldLog){console.log("Latitude and Longitude", coordsStr)}
break
}
}
coords = coordsStr.split(",")
aLatitude = coords[0]
if(shouldLog){console.log("Latitude", aLatitude)}
aLongitude = coords[1]
if(shouldLog){console.log("Longitude", aLongitude)}
// CONSTRUCT THE USER FORM
mapStyleIndex = preferences.readNumber("mapStyleIndex")
if(!mapStyleIndex){
mapStyleIndex = 1
}
if(shouldLog){console.log("mapStyleIndex", mapStyleIndex)}
mapStyles = ["Mapbox Light", "Mapbox Dark", "Mapbox Streets", "Mapbox Outdoors", "Mapbox Satellite", "Mapbox Satellite Streets"]
mapURLStyleValues = ["light-v11", "dark-v11", "streets-v12", "outdoors-v12", "satellite-v9", "satellite-streets-v12"]
mapStylesIndexes = [0,1,2,3,4,5]
mapStyleMenu = new Form.Field.Option(
"mapStyleIndex",
"Style",
mapStylesIndexes,
mapStyles,
mapStyleIndex
)
mapSizeIndex = preferences.readNumber("mapSizeIndex")
if(!mapSizeIndex){
mapSizeIndex = 0
}
if(shouldLog){console.log("mapSizeIndex", mapSizeIndex)}
mapSizes = ["640x360", "360x640", "360x360", "480x480", "640x640"]
mapDisplaySizes = ["640 x 360", "360 x 640", "360 x 360", "480 x 480", "640 x 640"]
mapSizesIndexes = [0,1,2,3,4]
mapSizeMenu = new Form.Field.Option(
"mapSizeIndex",
"Size",
mapSizesIndexes,
mapDisplaySizes,
mapSizeIndex
)
mapZoomIndex = preferences.readNumber("mapZoomIndex")
if(!mapZoomIndex){
mapZoomIndex = 2
}
if(shouldLog){console.log("mapZoomIndex", mapZoomIndex)}
mapZoomFactors = ["13", "14", "15", "16", "17"]
mapZoomIndexes = [0,1,2,3,4]
mapZoomMenu = new Form.Field.Option(
"mapZoomIndex",
"Zoom",
mapZoomIndexes,
mapZoomFactors,
mapZoomIndex
)
shouldUseHighRes = preferences.readBoolean("shouldUseHighRes")
if(!shouldUseHighRes){
shouldUseHighRes = false
}
if(shouldLog){console.log("shouldUseHighRes", shouldUseHighRes)}
highResCheckbox = new Form.Field.Checkbox(
"shouldUseHighRes",
"High-Resolution (@2x)",
shouldUseHighRes
)
shouldUsePin = preferences.readBoolean("shouldUsePin")
if(!shouldUsePin){
shouldUsePin = true
}
if(shouldLog){console.log("shouldUsePin", shouldUsePin)}
pinStatusCheckbox = new Form.Field.Checkbox(
"shouldUsePin",
"Include Location Pin",
shouldUsePin
)
pinColorIndex = preferences.readNumber("pinColorIndex")
if(!pinColorIndex){
pinColorIndex = 0
}
if(shouldLog){console.log("mapZoomIndex", mapZoomIndex)}
pinColorTitles = ["Red", "Green", "Blue", "Black", "White", "Gray", "Dark Gray", "Light Gray"]
pinColorValues = ["f00", "0f0", "00f", "000", "fff", "808080", "a9a9a9", "d3d3d3"]
pinColorIndexes = [0, 1, 2, 3, 4, 5, 6, 7]
pinColorMenu = new Form.Field.Option(
"pinColorIndex",
"Pin Color",
pinColorIndexes,
pinColorTitles,
pinColorIndex
)
inputForm = new Form()
inputForm.addField(mapStyleMenu)
inputForm.addField(mapSizeMenu)
inputForm.addField(mapZoomMenu)
inputForm.addField(pinStatusCheckbox)
inputForm.addField(pinColorMenu)
inputForm.addField(highResCheckbox)
inputForm.validate = function(formObject){
return true
}
formPrompt = "Select Mapbox map parameters:"
buttonTitle = "Continue"
// PRESENT FORM AND WAIT FOR RESULTS
formObject = await inputForm.show(formPrompt, buttonTitle)
mapStyleIndex = formObject.values['mapStyleIndex']
if(shouldLog){console.log('mapStyleIndex', mapStyleIndex)}
mapStyleValue = mapURLStyleValues[mapStyleIndex]
if(shouldLog){console.log('mapStyleValue', mapStyleValue)}
mapSizeIndex = formObject.values['mapSizeIndex']
if(shouldLog){console.log('mapSizeIndex', mapSizeIndex)}
mapSizeValue = mapSizes[mapSizeIndex]
if(shouldLog){console.log('mapSizeValue', mapSizeValue)}
mapZoomIndex = formObject.values['mapZoomIndex']
if(shouldLog){console.log('mapZoomIndex', mapZoomIndex)}
mapZoomValue = mapZoomFactors[mapZoomIndex]
if(shouldLog){console.log('mapZoomValue', mapZoomValue)}
shouldUsePin = formObject.values['shouldUsePin']
if(shouldLog){console.log('shouldUsePin', shouldUsePin)}
pinColorIndex = formObject.values['pinColorIndex']
if(shouldLog){console.log('pinColorIndex', pinColorIndex)}
pinColorValue = pinColorValues[pinColorIndex]
if(shouldLog){console.log('pinColorValue', pinColorValue)}
shouldUseHighRes = formObject.values['shouldUseHighRes']
if(shouldLog){console.log('shouldUseHighRes', shouldUseHighRes)}
resolutionValue = (shouldUseHighRes) ? "@2x":""
// CONSTRUCT MAPBOX URL
if(shouldUsePin){
var urlStr = `https://api.mapbox.com/styles/v1/mapbox/
${mapStyleValue}/ static/ pin-l+${pinColorValue}(${aLongitude},${aLatitude})/ ${aLongitude},${aLatitude},${mapZoomValue}/ ${mapSizeValue}${resolutionValue}?access_token=${APIToken}` } else {
var urlStr = `https://api.mapbox.com/styles/v1/mapbox/
${mapStyleValue}/ static/ ${aLongitude},${aLatitude},${mapZoomValue}/ ${mapSizeValue}${resolutionValue}?access_token=${APIToken}` }
if(shouldLog){console.log("Mapbox URL", urlStr)}
// STORE THE VALUES
preferences.write("mapStyleIndex", mapStyleIndex)
preferences.write("mapSizeIndex", mapSizeIndex)
preferences.write("mapZoomIndex", mapZoomIndex)
preferences.write("pinColorIndex", pinColorIndex)
preferences.write("shouldUsePin", shouldUsePin)
preferences.write("shouldUseHighRes", shouldUseHighRes)
if(shouldLog){console.log("# FETCHING DATA")}
fetchData(urlStr)
} else {
if(shouldLog){console.log("# REQUESTING CREDENTIALS")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
singleItemIsSelected = (
selection.databaseObjects.length === 1 &&
selection.projects.length === 1 ||
selection.tasks.length === 1
)
return singleItemIsSelected
};
return action;
})();