Colophon:
To generate a swatch of the 199 basic colours named by Tinderbox 8, with the rgb values to which they map in the current display mode and color scheme, we could run the JavaScript for Automation script below, obtaining, for example, in Light mode, with the Modern color scheme, a file like this:
namedColorsLightModeModern.tbx (164.0 KB)
(() => {
'use strict';
// Core Tinderbox color swatch for
// current color scheme and display mode.
// Rob Trew 2019
// Ver 0.4 updated to include colors '0' .. '9'
// and the modifier permutations of 'gray'.
ObjC.import('AppKit');
const main = () => {
const inner = () => {
const
// Least inflected column of color names
// (column 4 in MapofTinderboxsdefinedco.html table above)
tbxBaseColors = [
'black', 'blue', 'bright blue', 'bright green',
'bright red', 'cool gray', 'cool gray dark', 'cyan',
'gray', 'green', 'magenta', 'orange', 'poppy',
'red', 'violet', 'warm gray', 'warm gray dark',
'white', 'yellow'
],
tbxColorModifiers = [
'darkest', 'darker', 'dark',
'',
'light', 'lighter', 'lightest',
'muted', 'warmer', 'cooler'
],
tbxAllColorNames = map(fullName)(
cartesianProduct(tbxColorModifiers)(
tbxBaseColors
)
).concat(
map(str)(
enumFromTo(1)(9)
)
);
const
appTBX8 = Application('Tinderbox 8'),
docs = appTBX8.documents,
newDoc = new appTBX8.Document(),
doc = (
docs.push(newDoc),
newDoc
),
hexFromName = hexAndNoteFromColorName(appTBX8)(
doc.notes
),
intTotal = tbxAllColorNames.length,
strTotal = str(intTotal);
const strListing = (
// EFFECT
appTBX8.activate(),
// VALUE
unlines(
zipWith(
i => k => {
const strHex = hexFromName(k);
return (
showLog(
str(i) + ' of ' + strTotal,
k,
strHex
),
k + '\t' + strHex
);
}
)(
enumFromTo(1)(intTotal)
)(tbxAllColorNames)
)
);
return (
// EFFECTS
appTBX8.activate(),
menuItemClicked('Tinderbox 8')(
['View', 'Arrange', 'Cleanup…']
),
// EFFECT AND VALUE
copyText(strListing),
strListing
);
};
// COLORS -----------------------------------------
// fullName :: (String, String) -> String
const fullName = prefixNameTuple => {
const [p, k] = Array.from(prefixNameTuple);
return Boolean(p) ? (
p + ' ' + k
) : p + k;
};
// hexAndNoteFromColorName ::
// TBX App -> TBX Notes -> String -> IO String
const hexAndNoteFromColorName = tbx => notes => k => {
const
newNote = new tbx.Note({
name: k
}),
attribs = (
notes.push(newNote),
newNote.attributes
),
strHex = (
zipWith(
k => v => attribs.byName(k).value = v
)(['Color', 'Width'])([
k, 6
]),
newNote.evaluate({
with: '$Color.format()'
})
);
return (
attribs.byName('Subtitle').value = strHex,
strHex
);
};
return inner();
};
// JXA ------------------------------------------------
// copyText :: String -> IO Bool
const copyText = s => {
// String copied to general pasteboard.
const pb = $.NSPasteboard.generalPasteboard;
return (
pb.clearContents,
pb.setStringForType(
$(s),
$.NSPasteboardTypeString
),
s
);
};
// menuItemClicked :: String -> [String] -> IO Bool
const menuItemClicked = strAppName => lstMenuPath => {
const intMenuPath = lstMenuPath.length;
return intMenuPath > 1 ? (() => {
const
appProcs = Application('System Events')
.processes.where({
name: strAppName
});
return appProcs.length > 0 ? (
Application(strAppName)
.activate(),
lstMenuPath.slice(1, -1)
.reduce(
(a, x) => a.menuItems[x].menus[x],
appProcs[0].menuBars[0].menus.byName(lstMenuPath[0])
)
.menuItems[lstMenuPath[intMenuPath - 1]].click(),
true
) : false;
})() : false;
};
// GENERIC FUNCTIONS ----------------------------------
// https://github.com/RobTrew/prelude-jxa
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a => b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
// cartesianProduct :: [a] -> [b] -> [(a, b)]
const cartesianProduct = xs => ys =>
xs.flatMap(x => ys.flatMap(y => Tuple(x)(y)));
// enumFromTo :: Int -> Int -> [Int]
const enumFromTo = m => n =>
Array.from({
length: 1 + n - m
}, (_, i) => m + i);
// Returns Infinity over objects without finite length.
// This enables zip and zipWith to choose the shorter
// argument when one is non-finite, like cycle, repeat etc
// length :: [a] -> Int
const length = xs =>
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
// map :: (a -> b) -> [a] -> [b]
const map = f => xs =>
(Array.isArray(xs) ? (
xs
) : xs.split('')).map(f);
// showLog :: a -> IO ()
const showLog = (...args) =>
console.log(
args
.map(JSON.stringify)
.join(' -> ')
);
// str :: a -> String
const str = x => x.toString();
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n => xs =>
'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// Use of `take` and `length` here allows zipping with non-finite lists
// i.e. generators like cycle, repeat, iterate.
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f => xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
as = take(lng)(xs),
bs = take(lng)(ys);
return Array.from({
length: lng
}, (_, i) => f(as[i])(
bs[i]
));
};
// MAIN ---
return main();
})();