I am trying to prompt the user for a shape name and color using JXA and then pass that info along to an OmniJS script to alter the shape’s fillColor to match.
Here’s my code:
const og = Application('OmniGraffle 7')
og.includeStandardAdditions = true
const deviceColors = {
'red' : [1,0,0],
'green' : [0,1,0],
'blue' : [0,0,1]
}
const newDeviceColor = og.chooseFromList( Object.keys(deviceColors).sort() )[0]
const deviceName = og.displayDialog( 'Device Name:', {defaultAnswer: ''} ).textReturned
if ( newDeviceColor && deviceName ) {
let newColor = deviceColors[newDeviceColor]
const ogJSContext = (deviceName, newColor) => {
const newDevice = canvases[0].graphicWithName(deviceName)
newDevice.fillColor = Color.RGB( ...newColor )
}
og.evaluateJavascript(
"(" + ogJSContext(deviceName, newColor) + ")()"
)
}
It gets to this line:
const newDevice = canvases[0].graphicWithName(deviceName)
then I get: ReferenceError: Can't find variable: canvases
.
I know it’s a matter of the difference between JXA and OmniJS contexts and that JXA doesn’t know about OmniJS objects like canvases
but I can’t for the life of me figure out how to do it. I’ve tried several different approaches besides the one above and none have worked.
draft8
April 15, 2018, 9:31am
#2
Atomic arguments can, of course, be stringified, with .toString() or just concatenation,
but things get easier if we pass a JSON.stringified version of a single argument (a dictionary of key-value pairs),
So while stage one might be as simple as:
(() => {
'use strict';
const og = Application('OmniGraffle');
const ogJSContext = options => {
return options.toString();
};
return og.evaluateJavascript(
'(' + ogJSContext + ')(' + 7 + ')'
);
})();
For most purposes it works better to write things like:
(() => {
'use strict';
const og = Application('OmniGraffle');
const ogJSContext = options => {
return JSON.stringify(options.color) + '\n' +
options.deviceName;
};
return og.evaluateJavascript(
'(' + ogJSContext + ')(' + JSON.stringify({
color: [0, 0, 1],
deviceName : 'BlueBottle'
}) + ')'
);
})();
draft8
April 15, 2018, 12:14pm
#3
And fleshing that out a little in this context, one variant might be something like:
(() => {
'use strict';
// ogJSContext = { color :: (Int, Int, Int), deviceName :: String }
// -> String
const ogJSContext = options => {
const main = () => {
const
cnv = canvases[0],
strName = options.deviceName,
shp = cnv.graphicWithName(strName),
lrResult = bindLR(
shp !== null ? Right(
shp
) : Left('Shape not found by name: ' + strName),
shp => Right(
(shp.fillColor = Color.RGB(
...options.color
),
'Shape named ' + strName +
' now has fillColor:' +
(() => {
const newColor = shp.fillColor;
return JSON.stringify(
['red', 'green', 'blue'].map(
k => newColor[k]
)
);
})()
)
)
);
return JSON.stringify(lrResult.Left || lrResult.Right);
};
// GENERICS FOR OMNIJS CONTEXT ---------------------------------------
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = (m, mf) =>
m.Right !== undefined ? (
mf(m.Right)
) : m;
// OMNIJS MAIN
return main();
};
const jxaMain = () => {
const main = () => {
const
og = Application('OmniGraffle'),
sa = (og.includeStandardAdditions = true, og),
deviceColors = {
'red': [1, 0, 0],
'green': [0, 1, 0],
'blue': [0, 0, 1]
},
colors = Object.keys(deviceColors).sort(),
color = (
sa.activate(),
sa.chooseFromList(colors, {
withTitle: 'Colors',
defaultItems: colors[0]
})[0]
);
const lrResult = bindLR(
bindLR(
Boolean(color) ? Right(
color
) : Left('No color selected'),
color => {
try {
return Right({
color: deviceColors[color],
deviceName: og.displayDialog(
'Name of device:', {
defaultAnswer: '',
withTitle: 'Device'
}
).textReturned
});
} catch (e) {
return Left(e.message)
}
}),
options => Right(
og.evaluateJavascript(
'(' + ogJSContext + ')(' +
JSON.stringify(options) +
')'
)
)
);
return lrResult.Left || lrResult.Right;
};
// GENERICS FOR JXA CONTEXT ------------------------------------------
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = (m, mf) =>
m.Right !== undefined ? (
mf(m.Right)
) : m;
// showJSON :: a -> String
const showJSON = x => JSON.stringify(x, null, 2);
// showLog :: a -> IO ()
const showLog = (...args) =>
console.log(
args
.map(JSON.stringify)
.join(' -> ')
);
// JXA
return main();
};
return jxaMain();
})();
draft8
April 15, 2018, 12:49pm
#4
Incidentally, I’m not sure how many named shapes there are on your canvases, but if the number is manageable, you could, of course, get a list of shape names before running your main code, and choose from a menu of them:
(() => {
'use strict';
// ogJSContextNames :: OG () -> JSON String
const ogJSContextNames = () =>
JSON.stringify(
canvases[0].graphics
.reduce((a, x) => {
const name = x.name;
return null !== name ? (
a.concat(name)
) : a;
}, [])
);
const
og = Application('OmniGraffle'),
sa = (og.includeStandardAdditions = true, og),
names = JSON.parse(og.evaluateJavascript(
'(' + ogJSContextNames + ')()'
)).sort();
return (
sa.activate(),
sa.chooseFromList(names, {
withTitle: 'Named shapes on canvas',
defaultItems: names[0]
})
);
})();