Three variants of a Keyboard Maestro macro for macOS OmniGraffle 7:
(KM macros feel somehow lighter and more flexible than the omniJS plugin mechanism, which is really only needed for iOS – not the platform on which I personally use OmniGraffle).
Source code: omniJS + JavaScript for Automation
(() => {
// Rob Trew 2019
// Ver 0.01
// OmniGraffle 7:
// Toggle connections between several selected
// shapes, in the order of selection,
// in one of the following patterns:
//
// - Cyclical (last selected is connected to the first)
// - Linear (chain of connections from first to last)
// - Tree (First selected connected to each of rest)
//
// The toggle has 3 stages:
// 1. Connected, with arrow directions in selection order,
// 2. arrow directions reversed,
// 3. connections cleared.
// Connection pattern for toggling:
const
connectionPattern = {
cyclic: 0,
linear: 1,
tree: 3
} ['tree']
// OMNI JS CODE ---------------------------------------
const omniJSContext = iPattern => {
const dctConnectorStyle = {
headType: 'FilledArrow',
strokeColor: Color.RGB(1, 0, 0)
};
// main :: IO ()
const main = () => {
// Uncomment the code for the connection pattern chosen
// (cyclical, linear, or tree)
const
connectionType = Boolean(iPattern) ? (
iPattern !== 1 ? (
tripleToggleTree
) : tripleToggleSeries(false)
) : tripleToggleSeries(true);
const
seln = document.windows[0].selection,
shapes = seln.solids;
return 1 < shapes.length ? (
connectionType(seln.canvas)(shapes)
) : 'Select more than one shape';
};
// OMNIGRAFFLE LINK-TOGGLING ----------------------
// tripleToggleTree :: Canvas -> [Shape] -> IO String
const tripleToggleTree = canvas => solids => {
// First item of `solids` connected *to*
// all the remaining shapes in list
// -> connected *from* all remaining shapes
// -> disconnected from all remaining shapes.
const
root = solids[0],
following = solids[1],
f = 0 < existingFromTo(root)(following).length ? (
a => b => (
unLinkedFromTo(a)(b),
linkedFromTo(canvas)(b)(a)
)
) : 0 < existingFromTo(following)(root).length ? (
flip(unLinkedFromTo)
) : linkedFromTo(canvas);
return solids.slice(1).flatMap(f(root));
};
// tripleToggleSequences :: Canvas -> Bool -> Bool ->
// [Shape] -> IO String
const tripleToggleSeries = blnCycle => canvas => solids => {
// Solids connected in series ,
// -> reversed connected in series,
// -> disconnected,
// and series closed by return to start if blnCycle is true.
const
start = solids[0],
following = solids[1],
f = 0 < existingFromTo(start)(following).length ? (
a => b => (
unLinkedFromTo(a)(b),
linkedFromTo(canvas)(b)(a)
)
) : 0 < existingFromTo(following)(start).length ? (
flip(unLinkedFromTo)
) : linkedFromTo(canvas);
return zipWith(f)(solids)(
solids.slice(1).concat(
blnCycle ? [start] : []
)
).join(', ');
};
// existingFromTo :: Solid -> Solid -> [Line]
const existingFromTo = a => b => {
// An array of any connections running from a to b.
const strID = a.id;
return b.incomingLines.filter(
x => strID === x.tail.id
);
};
// linkedFromTo :: Shape -> Shape -> IO [String]
const linkedFromTo = canvas => a => b => {
// A new connecting line from shape a to shape b.
const line = canvas.connect(a, b);
return (
Object.assign(line, dctConnectorStyle),
line.setUserData('quickLink', '1'),
[a.text + ' -> ' + b.text]
);
};
// unLinkedFromTo :: Shape -> Shape -> IO [String]
const unLinkedFromTo = a => b =>
// Any connection from a to b removed.
existingFromTo(a)(b).map(
x => (
x.remove(), [a.text + ' <- x -> ' + b.text]
)
);
// GENERIC ----------------------------------------
// https://github.com/RobTrew/prelude-jxa
// flip :: (a -> b -> c) -> b -> a -> c
const flip = f =>
x => y => f(y)(x);
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f => xs => ys =>
xs.slice(
0, Math.min(xs.length, ys.length)
).map((x, i) => f(x)(ys[i]));
// MAIN -------------------------------------------
return main()
};
// JXA CODE -------------------------------------------
// omniJSWithArgs :: Function -> [...OptionalArgs] -> a
function omniJSWithArgs(f) {
return Application('OmniGraffle')
.evaluateJavascript(
`(${f})(${Array.from(arguments)
.slice(1).map(JSON.stringify)})`
);
};
// return omniJSContext();
return omniJSWithArgs(
omniJSContext,
connectionPattern
);
})();