I seem to be able to distress the current macOS and iOS builds by using JSON-format strings in notes and userData values.
In iOS, all seems fine until I attempt to edit JSON-format user data strings through the GUI - doing this appears to freeze the app entirely for several minutes.
On macOS, the JSON-format note string is clearly shown in the mouse-over toolTip display, but the GUI text control for editing the note is unresponsive and displays nothing.
( If this can be fixed – I can see that it might be diffcult – it would certainly be helpful - at the moment I’m relying on JSON-formatted user data and notes for a couple of things, including smuggling return values out through the omniJS event-horizon on iOS. )
To reproduce this problem:
- Select all or several shapes and lines on an iOS or macOS canvas
- Load and call the omniJS function below, which places a JSON dump of the graphics in a canvas background userData item keyed as ‘omniJS’
- Try to view and edit that JSON string in the iOS or macOS GUI.
Test code behind disclosure triangle below (just a function definition, call as copyAsJSON(), e.g. from the console, after pasting and entering.
omniJS test code
// OG () -> omniJS JSON
function copyAsJSON() {
'use strict';
// GENERIC FUNCTIONS -----------------------------------------------------
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = (f, xs) => [].concat.apply([], xs.map(f));
// cons :: a -> [a] -> [a]
const cons = (x, xs) => [x].concat(xs);
// curry :: ((a, b) -> c) -> a -> b -> c
const curry = f => a => b => f(a, b);
// drop :: Int -> [a] -> [a]
// drop :: Int -> String -> String
const drop = (n, xs) => xs.slice(n);
// elem :: Eq a => a -> [a] -> Bool
const elem = (x, xs) => xs.indexOf(x) !== -1;
// filter :: (a -> Bool) -> [a] -> [a]
const filter = (f, xs) => xs.filter(f);
// foldl :: (b -> a -> b) -> b -> [a] -> b
const foldl = (f, a, xs) => xs.reduce(f, a);
// intercalate :: String -> [a] -> String
const intercalate = (s, xs) => xs.join(s);
// log :: a -> IO ()
const log = (...args) =>
console.log(
args
.map(JSON.stringify)
.join(' -> ')
);
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) => xs.map(f);
// partition :: Predicate -> List -> (Matches, nonMatches)
// partition :: (a -> Bool) -> [a] -> ([a], [a])
const partition = (p, xs) =>
xs.reduce((a, x) =>
p(x) ? [a[0].concat(x), a[1]] : [a[0], a[1].concat(x)], [
[],
[]
]);
// show :: Int -> a -> Indented String
// show :: a -> String
const show = (...x) =>
JSON.stringify.apply(
null, x.length > 1 ? [x[1], null, x[0]] : x
);
// sort :: Ord a => [a] -> [a]
const sort = xs => xs.slice()
.sort();
// toUpper :: Text -> Text
const toUpper = s => s.toUpperCase();
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// COPY AS JAVASCRIPT ----------------------------------------------------
// constructorArgs :: {TypeName : [ArgumentName]}
const constructorArgs = {
'Group': ['graphics'],
'Rect': ['x', 'y', 'width', 'height'],
'Point': ['x', 'y'],
'Size': ['width', 'height'],
'SubGraph': ['graphics'],
'Table': ['graphic']
};
// enums :: [String]
const enums = [
"autosizing", "columnAlignment", "fillType", "hopType",
"textHorizontalAlignment", "lineType", "rowAlignment",
"strokeCap", "strokeJoin", "strokePattern", "strokeType",
"textVerticalPlacement", "imageSizing"
];
// protoChain :: OG Graphic -> [(String, Prototype)]
const protoChain = g => {
const pChain = protoType => {
const k = protoType.constructor.name;
return k === 'Object' ? [] : cons(
[k, protoType], pChain(Object.getPrototypeOf(protoType))
);
};
return pChain(Object.getPrototypeOf(g));
};
// graphicDict :: Canvas -> OG Graphic -> Int -> Dictionary
const graphicDict = (cnv, g) => {
const
strType = Object.getPrototypeOf(g)
.constructor.name,
ks = sort(concatMap(([s, p]) =>
filter(k => !elem(k, ['id', 'constructor']) &&
(typeof g[k] !== 'function'),
Object.getOwnPropertyNames(p)
), protoChain(g)));
return foldl(
(a, k) => (a[k] = valueJS(cnv, g, strType, k), a), {
id: g.id
},
ks
);
};
// valueJS :: Canvas -> OG Object -> String -> String -> String
const valueJS = (cnv, o, strObjType, k) => {
const
prop = o[k],
strPropType = typeof prop;
const geo = o.geometry;
return prop === null ? null : (
strPropType === 'object' ? (() => {
const
type = Object.getPrototypeOf(prop),
strType = type.constructor.name,
mbArgs = elem(strType, Object.keys(constructorArgs)) ? ({
just: constructorArgs[strType]
}) : {
nothing: true
};
return strType === 'Array' ? arrayJS(
cnv, strObjType, k, prop
) : !mbArgs.nothing ? (
foldl((a, x) => (a.args[x] = prop[x], a), {
type: strType,
args: {}
}, mbArgs.just)
) : elem(k, enums) ? (
enumJS(type, strType, prop)
) : strType === 'Color' ? (
foldl((a, x) => (a.args[x] = prop[x], a), {
type: 'Color.RGB',
args: {}
}, ['red', 'green', 'blue'])
) : strType === 'Shape' ? (
prop.id
) : strType === 'URL' ? (
prop.toString()
) : strType === 'Layer' ? (
prop.name
) : prop;
})() : (strPropType === 'string' ? prop : prop));
};
// arrayJS :: Canvas, String -> String -> [OG Value] -> [a]
const arrayJS = (cnv, strObjType, strKey, xs) =>
xs.length > 0 ? (
() => {
const strType = Object.getPrototypeOf(xs[0])
.constructor.name;
return (
strObjType !== 'Group' || strKey !== 'graphics'
) ? (
strType === 'Point' ? (
map(p => ({
type: 'Point',
args: {
x: p.x,
y: p.y
}
}), xs)
) : map(x => typeof x === 'object' ? x.id : x, xs)
) : map(x => graphicDict(cnv, x), xs);
}
)() : '';
// enumJS :: prototype -> Constructor Name -> OG Value -> String
const enumJS = (type, strType, prop) =>
foldl((a, x) => {
const strEnum = strType + '.' + x;
return a.nothing ? (
prop === eval(strEnum) ? {
just: strEnum
} : a
) : a;
}, {
nothing: true
}, drop(2, Object.getOwnPropertyNames(type.constructor)))
.just;
// isLine :: Graphic -> Bool
const isLine = g =>
Object.getPrototypeOf(g)
.constructor.name === 'Line';
// MAIN --------------------------------------------------------------
const
oSeln = document.windows[0].selection,
cnv = oSeln.canvas,
gs = oSeln.graphics,
[lines, shapes] = partition(isLine, gs),
strJSON = show(2, {
shapes: map(x => graphicDict(cnv, x), shapes),
lines: map(x => graphicDict(cnv, x), lines)
});
// Save a return value in Canvas background user-data
return (
cnv.background.setUserData('omniJSON', strJSON),
strJSON
);
};