UNPKG

three-codeeditor

Version:

codeeditor for three.js

1,449 lines (1,263 loc) 244 kB
/*global window, process, global*/ ;(function(Global) { var globalInterfaceSpec = [ {action: "installMethods", target: "Array", sources: ["arr"], methods: ["from","genN","range","withN"]}, {action: "installMethods", target: "Array.prototype", sources: ["arr"], methods: ["all","any","batchify","clear","clone","collect","compact","delimWith","detect","doAndContinue","each","equals","filterByKey","findAll","first","flatten","forEachShowingProgress","grep","groupBy","groupByKey","histogram","include","inject","intersect","invoke","last","mapAsync", "mapAsyncSeries", "mask","max","min","mutableCompact","nestedDelay","partition","pluck","pushAll","pushAllAt","pushAt","pushIfNotIncluded","reMatches","reject","rejectByKey","remove","removeAt","replaceAt","rotate","shuffle","size","sortBy","sortByKey","sum","swap","toArray","toTuples","union","uniq","uniqBy","without","withoutAll","zip"], alias: [["select", "filter"],["find","detect"]]}, {action: "installMethods", target: "Date", sources: ["date"], methods: [/*"parse"*/]}, {action: "installMethods", target: "Date.prototype", sources: ["date"], methods: ["equals","format","relativeTo"]}, {action: "installMethods", target: "Function", sources: ["fun"], methods: ["fromString"]}, {action: "installMethods", target: "Function.prototype", sources: ["fun"], methods: [/*"addProperties",*/"addToObject","argumentNames","asScript","asScriptOf","binds","curry","delay","functionNames","localFunctionNames","getOriginal","getVarMapping","logCalls","logCompletion","logErrors","qualifiedMethodName","setProperty","traceCalls","wrap"]}, {action: "installMethods", target: "Number", sources: ["num"], methods: []}, {action: "installMethods", target: "Number.prototype", sources: ["num"], methods: ["detent","randomSmallerInteger","roundTo","toDegrees","toRadians"]}, {action: "installMethods", target: "Object", sources: ["obj"], methods: ["addScript","clone","deepCopy","extend","inherit","isArray","isBoolean","isElement","isEmpty","isFunction","isNumber","isObject","isRegExp","isString","isUndefined","merge","mergePropertyInHierarchy","values","valuesInPropertyHierarchy"]}, {action: "installMethods", target: "Object.prototype", sources: ["obj"], methods: []}, {action: "installMethods", target: "String.prototype", sources: ["string"], methods: ["camelize","capitalize","digitValue","empty","endsWith","hashCode","include","pad","regExpEscape","startsWith","startsWithVowel","succ","times","toArray","toQueryParams","truncate"]}, {action: "installMethods", target: "Function.prototype", sources: ["class"], methods: ["create","addMethods","isSubclassOf","superclasses","categoryNameFor","remove"], alias: [["subclass", "create"]]}, {action: "installObject", target: "Numbers", source: "num", methods: ["average","between","convertLength","humanReadableByteSize","median","normalRandom","parseLength","random","sort"]}, {action: "installObject", target: "Properties", source: "properties", methods: ["all","allOwnPropertiesOrFunctions","allProperties","any","forEachOwn","hash","nameFor","own","ownValues","values"]}, {action: "installObject", target: "Strings", source: "string", methods: ["camelCaseString","createDataURI","diff","format","formatFromArray","indent","lineIndexComputer","lines","md5","newUUID","nonEmptyLines","pad","paragraphs","peekLeft","peekRight","print","printNested","printTable","printTree","quote","reMatches","removeSurroundingWhitespaces","stringMatch","tableize","tokens","unescapeCharacterEntities","withDecimalPrecision"]}, {action: "installObject", target: "Objects", source: "obj", methods: ["asObject", "equals","inspect","isMutableType","safeToString","shortPrintStringOf","typeStringOf"]}, {action: "installObject", target: "Functions", source: "fun", methods: ["all","compose","composeAsync","createQueue","debounce","debounceNamed","either","extractBody","flip","notYetImplemented","once","own","throttle","throttleNamed","timeToRun","timeToRunN","waitFor","workerWithCallbackQueue","wrapperChain"]}, {action: "installObject", target: "Grid", source: "grid"}, {action: "installObject", target: "Interval", source: "interval"}, {action: "installObject", target: "lively.ArrayProjection", source: "arrayProjection"}, {action: "installObject", target: "lively.Closure", source: "Closure"}, {action: "installObject", target: "lively.Grouping", source: "Group"}, {action: "installObject", target: "lively.PropertyPath", source: "Path"}, {action: "installObject", target: "lively.Worker", source: "worker"}, {action: "installObject", target: "lively.Class", source: "classHelper"} ]; var isNode = typeof process !== 'undefined' && process.versions && process.versions.node; var livelyLang = createLivelyLangObject(); if (isNode) module.exports = livelyLang; else { livelyLang._prevLivelyGlobal = Global.lively; if (!Global.lively) Global.lively = {}; if (!Global.lively.lang) Global.lively.lang = livelyLang; else { for (var name in livelyLang) Global.lively.lang[name] = livelyLang[name]; } } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- function createLivelyLangObject() { return { chain: chain, noConflict: noConflict, installGlobals: installGlobals, uninstallGlobals: uninstallGlobals, globalInterfaceSpec: globalInterfaceSpec, deprecatedLivelyPatches: deprecatedLivelyPatches }; } function chain(object) { if (!object) return object; var chained; if (Array.isArray(object)) return createChain(livelyLang.arr, object); if (object.constructor.name === "Date") return createChain(livelyLang.date, object); switch (typeof object) { case 'string': return createChain(livelyLang.string, object); case 'object': return createChain(livelyLang.obj, object); case 'function': return createChain(livelyLang.fun, object); case 'number': return createChain(livelyLang.num, object); } throw new Error("Chain for object " + object + " (" + object.constructor.name + ") no supported"); } function createChain(interfaceObj, obj) { return Object.keys(interfaceObj).reduce(function(chained, methodName) { chained[methodName] = function(/*args*/) { var args = Array.prototype.slice.call(arguments), result = interfaceObj[methodName].apply(null, [obj].concat(args)); return chain(result); } return chained; }, {value: function() { return obj; }}); } function noConflict() { if (!isNode) { var keepLivelyNS = livelyLang._prevLivelyGlobal; if (!keepLivelyNS) delete Global.lively else delete Global.lively.lang } return livelyLang; } function installGlobals() { globalInterfaceSpec.forEach(function(ea) { if (ea.action === "installMethods") { var targetPath = livelyLang.Path(ea.target); if (!targetPath.isIn(Global)) targetPath.set(Global, {}, true); var sourcePath = livelyLang.Path(ea.sources[0]); ea.methods.forEach(function(name) { installProperty( sourcePath.concat([name]), targetPath.concat([name])); }); if (ea.alias) ea.alias.forEach(function(mapping) { installProperty( sourcePath.concat([mapping[1]]), targetPath.concat([mapping[0]])); }); } else if (ea.action === "installObject") { var targetPath = livelyLang.Path(ea.target); var source = livelyLang.Path(ea.source).get(livelyLang); targetPath.set(Global, source, true); } else throw new Error("Cannot deal with global setup action: " + ea.action); }); } function installProperty(sourcePath, targetPath) { if (!sourcePath.isIn(livelyLang)) { var err = new Error("property not provided by lively.lang: " + sourcePath); console.error(err.stack || err); throw err; } var prop = sourcePath.get(livelyLang); if (typeof prop === "function" && targetPath.slice(-2, -1).toString() === "prototype") { var origFunc = prop; prop = function(/*this and args*/) { var args = Array.prototype.slice.call(arguments); args.unshift(this); return origFunc.apply(null, args); }; prop.toString = function() { return origFunc.toString(); }; } targetPath.set(Global, prop, true); } function uninstallGlobals() { globalInterfaceSpec.forEach(function(ea) { if (ea.action === "installMethods") { var p = livelyLang.Path(ea.target) var target = p.get(Global); if (!target) return; ea.methods.forEach(function(name) { delete target[name]; }); if (ea.alias) ea.alias.forEach(function(mapping) { delete target[mapping[0]]; }); } else if (ea.action === "installObject") { var p = livelyLang.Path(ea.target); p.del(Global); } else throw new Error("Cannot deal with global setup action: " + ea.action); }) } function deprecatedLivelyPatches() { livelyLang.installGlobals(); Global.$A = Array.from; // We need to redefine Function.evalJS here b/c the original definition is // in a JS 'use strict' block. However, not all function sources we pass in // #evalJS from Lively adhere to the strictness rules. To allow those // functions for now we define the creator again outside of a strictness block. Function.evalJS = livelyLang.fun.evalJS = function(src) { return eval(src); } livelyLang.Path.type = livelyLang.PropertyPath; livelyLang.Path.prototype.serializeExpr = function () { // ignore-in-doc return 'lively.PropertyPath(' + livelyLang.obj.inspect(this.parts()) + ')'; } livelyLang.Closure.type = "lively.Closure"; livelyLang.fun.methodChain = livelyLang.fun.wrapperChain; if (typeof JSON !== "undefined") JSON.prettyPrint = function(jso) { return JSON.stringify(jso, null, 2); }; Global.NativeArrayFunctions = livelyLang.arrNative; } })(typeof window !== "undefined" ? window : global); ;/*global process, require*/ /* * A simple node.js-like cross-platform event emitter implementation. */ ;(function(exports) { "use strict"; var isNode = typeof process !== 'undefined' && process.versions && process.versions.node; // A simple node.js-like cross-platform event emitter implementation that can // be used as a mixin. Emitters support the methods: `on(eventName, handlerFunc)`, // `once(eventName, handlerFunc)`, `emit(eventName, eventData)`, // `removeListener(eventName, handlerFunc)`, `removeAllListeners(eventName)` // Example: // var emitter = events.makeEmitter({}); // var log = []; // emitter.on("test", function() { log.push("listener1"); }); // emitter.once("test", function() { log.push("listener2"); }); // emitter.emit("test"); // emitter.emit("test"); // log // => ["listener1","listener2","listener1"] // emitter.removeAllListeners("test"); // emitter.emit("test"); // log // => is still ["listener1","listener2","listener1"] var events = exports.events = { makeEmitter: isNode ? function(obj) { if (obj.on && obj.removeListener) return obj; var events = require("events"); require("util")._extend(obj, events.EventEmitter.prototype); events.EventEmitter.call(obj); return obj; } : function(obj) { if (obj.on && obj.removeListener) return obj; obj.listeners = {}; obj.on = function(type, handler) { if (!handler) return; if (!obj.listeners[type]) obj.listeners[type] = []; obj.listeners[type].push(handler); } obj.once = function(type, handler) { if (!handler) return; function onceHandler(/*ignore-in-docs args*/) { obj.removeListener(type, onceHandler); handler.apply(this, arguments); } obj.on(type, onceHandler); } obj.removeListener = function(type, handler) { if (!obj.listeners[type]) return; obj.listeners[type] = obj.listeners[type].filter(function(h) { return h !== handler; }); } obj.removeAllListeners = function(type) { if (!obj.listeners[type]) return; obj.listeners[type] = []; } obj.emit = function(/*type and args*/) { var args = Array.prototype.slice.call(arguments); var type = args.shift(); var handlers = obj.listeners[type]; if (!handlers || !handlers.length) return; handlers.forEach(function(handler) { try { handler.apply(null, args) } catch (e) { console.error("Error in event handler: %s", e.stack || String(e)); } }); } return obj; } }; })(typeof lively !== 'undefined' && lively.lang ? lively.lang : require('./base')); ;/*global*/ /* * Utility functions that help to inspect, enumerate, and create JS objects */ ;(function(exports) { "use strict"; // -=-=-=-=-=-=-=-=- // internal helper // -=-=-=-=-=-=-=-=- // serveral methods in lib/object.js are inspired or derived from // Prototype JavaScript framework, version 1.6.0_rc1 // (c) 2005-2007 Sam Stephenson // Prototype is freely distributable under the terms of an MIT-style license. // For details, see the Prototype web site: http://www.prototypejs.org/ function print(object) { if (object && obj.isArray(object)) { return '[' + object.map(print) + ']'; } if (typeof object !== "string") { return String(object); } var result = String(object); result = result.replace(/\n/g, '\\n\\\n'); result = result.replace(/(")/g, '\\$1'); result = '\"' + result + '\"'; return result; } function argumentNames(func) { if (func.superclass) return []; var names = func.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]. replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, ''). replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } function indent(str, indentString, depth) { if (!depth || depth <= 0) return str; while (depth > 0) { depth--; str = indentString + str; } return str; } // show-in-doc var obj = exports.obj = { // -=-=-=-=- // testing // -=-=-=-=- isArray: function(obj) { /*show-in-doc*/ return obj && Array.isArray(obj); }, isElement: function(object) { /*show-in-doc*/ return object && object.nodeType == 1; }, isFunction: function(object) { /*show-in-doc*/ return object instanceof Function; }, isBoolean: function(object) { /*show-in-doc*/ return typeof object == "boolean"; }, isString: function(object) { /*show-in-doc*/ return typeof object == "string"; }, isNumber: function(object) { /*show-in-doc*/ return typeof object == "number"; }, isUndefined: function(object) { /*show-in-doc*/ return typeof object == "undefined"; }, isRegExp: function(object) { /*show-in-doc*/ return object instanceof RegExp; }, isObject: function(object) { /*show-in-doc*/ return typeof object == "object"; }, isEmpty: function(object) { /*show-in-doc*/ for (var key in object) if (object.hasOwnProperty(key)) return false; return true; }, equals: function(a, b) { // Is object `a` structurally equivalent to object `b`? Deep comparison. if (!a && !b) return true; if (!a || !b) return false; switch (a.constructor) { case String: case Date: case Boolean: case Number: return a == b; }; if (typeof a.isEqualNode === "function") return a.isEqualNode(b); if (typeof a.equals === "function") return a.equals(b); return cmp(a, b) && cmp(b, a); // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- function cmp(left, right) { for (var name in left) { if (typeof left[name] === "function") continue; if (!obj.equals(left[name], right[name])) return false; } return true; } }, // -=-=-=-=-=- // accessing // -=-=-=-=-=- keys: Object.keys || function(object) { // like Object.keys var keys = []; for (var property in object) keys.push(property); return keys; }, values: function(object) { // Example: // var obj1 = {x: 22}, obj2 = {x: 23, y: {z: 3}}; // obj2.__proto__ = obj1; // obj.values(obj1) // => [22] // obj.values(obj2) // => [23,{z: 3}] return object ? Object.keys(object).map(function(k) { return object[k]; }) : []; }, addScript: function (object, funcOrString, optName, optMapping) { var func = exports.fun.fromString(funcOrString); return exports.fun.asScriptOf(func, object, optName, optMapping); }, // -=-=-=-=- // mutation // -=-=-=-=- extend: function(destination, source) { // Add all properties of `source` to `destination`. // Example: // var dest = {x: 22}, src = {x: 23, y: 24} // obj.extend(dest, src); // dest // => {x: 23,y: 24} var currentCategoryNames = null; for (var i = 1; i < arguments.length; i++) { if (typeof arguments[i] == "string") { var catName = arguments[i]; if (!destination.categories) destination.categories = {}; if (!destination.categories[catName]) destination.categories[catName] = []; currentCategoryNames = destination.categories[catName]; continue; } var source = arguments[i]; for (var property in source) { var getter = source.__lookupGetter__(property), setter = source.__lookupSetter__(property); if (getter) destination.__defineGetter__(property, getter); if (setter) destination.__defineSetter__(property, setter); if (getter || setter) continue; var sourceObj = source[property]; destination[property] = sourceObj; if (currentCategoryNames) currentCategoryNames.push(property); if (typeof sourceObj === "function") { if ((!sourceObj.name || (sourceObj.name.length == 0)) && !sourceObj.displayName) sourceObj.displayName = property; // remember the module that contains the definition if (typeof lively !== 'undefined' && lively.Module && lively.Module.current) sourceObj.sourceModule = lively.Module.current(); } } } return destination; }, // -=-=-=-=- // clone // -=-=-=-=- clone: function(object) { // Shallow copy return Array.isArray(object) ? Array.prototype.slice.call(object) : exports.obj.extend({}, object); }, extract: function(properties, object, mapFunc) { return properties.reduce(function(extracted, name) { if (object.hasOwnProperty(name)) { var val = mapFunc ? mapFunc(name, object[name]) : object[name]; extracted[name] = val; } return extracted; }, {}); }, // -=-=-=-=-=- // inspection // -=-=-=-=-=- inspect: function inspect(object, options, depth) { // Prints a human-readable representation of `obj`. The printed // representation will be syntactically correct JavaScript but will not // necessarily evaluate to a structurally identical object. `inspect` is // meant to be used while interactivively exploring JavaScript programs and // state. // // `options` can be {printFunctionSource: BOOLEAN, escapeKeys: BOOLEAN, maxDepth: NUMBER} options = options || {}; depth = depth || 0; if (!object) return print(object); // print function if (typeof object === 'function') { return options.printFunctionSource ? String(object) : 'function' + (object.name ? ' ' + object.name : '') + '(' + argumentNames(object).join(',') + ') {/*...*/}'; } // print "primitive" switch (object.constructor) { case String: case Boolean: case RegExp: case Number: return print(object); }; if (typeof object.serializeExpr === 'function') return object.serializeExpr(); var isArray = object && Array.isArray(object), openBr = isArray ? '[' : '{', closeBr = isArray ? ']' : '}'; if (options.maxDepth && depth >= options.maxDepth) return openBr + '/*...*/' + closeBr; var printedProps = []; if (isArray) { printedProps = object.map(function(ea) { return inspect(ea, options, depth); }); } else { printedProps = Object.keys(object) .sort(function(a, b) { var aIsFunc = typeof object[a] === 'function', bIsFunc = typeof object[b] === 'function'; if (aIsFunc === bIsFunc) { if (a < b) return -1; if (a > b) return 1; return 0; } return aIsFunc ? 1 : -1; }) .map(function(key, i) { if (isArray) inspect(object[key], options, depth + 1); var printedVal = inspect(object[key], options, depth + 1); return options.escapeKeys ? Strings.print(key) : key + ": " + printedVal; }); } if (printedProps.length === 0) { return openBr + closeBr; } var printedPropsJoined = printedProps.join(','), useNewLines = !isArray && (!options.minLengthForNewLine || printedPropsJoined.length >= options.minLengthForNewLine), ind = indent('', options.indent || ' ', depth), propIndent = indent('', options.indent || ' ', depth + 1), startBreak = useNewLines ? '\n' + propIndent: '', endBreak = useNewLines ? '\n' + ind : ''; if (useNewLines) printedPropsJoined = printedProps.join(',' + startBreak); return openBr + startBreak + printedPropsJoined + endBreak + closeBr; }, // -=-=-=-=- // merging // -=-=-=-=- merge: function(objs) { // `objs` can be a list of objects. The return value will be a new object, // containing all properties of all objects. If the same property exist in // multiple objects, the right-most property takes precedence. // // Like `extend` but will not mutate objects in `objs`. // if objs are arrays just concat them // if objs are real objs then merge propertdies if (arguments.length > 1) { return obj.merge(Array.prototype.slice.call(arguments)); } if (Array.isArray(objs[0])) { // test for all? return Array.prototype.concat.apply([], objs); } return objs.reduce(function(merged, ea) { for (var name in ea) if (ea.hasOwnProperty(name)) merged[name] = ea[name]; return merged; }, {}); }, // -=-=-=-=-=-=- // inheritance // -=-=-=-=-=-=- inherit: function(obj) { return Object.create(obj); }, valuesInPropertyHierarchy: function(obj, name) { // Lookup all properties named name in the proto hierarchy of obj. // Example: // var a = {foo: 3}, b = Object.create(a), c = Object.create(b); // c.foo = 4; // obj.valuesInPropertyHierarchy(c, "foo") // => [3,4] var result = [], lookupObj = obj; while (lookupObj) { if (lookupObj.hasOwnProperty(name)) result.unshift(lookupObj[name]) lookupObj = Object.getPrototypeOf(lookupObj); } return result; }, mergePropertyInHierarchy: function(obj, propName) { // like `merge` but automatically gets all definitions of the value in the // prototype chain and merges those. // Example: // var o1 = {x: {foo: 23}}, o2 = {x: {foo: 24, bar: 15}}, o3 = {x: {baz: "zork"}}; // o2.__proto__ = o1; o3.__proto__ = o2; // obj.mergePropertyInHierarchy(o3, "x"); // // => {bar: 15, baz: "zork",foo: 24} return this.merge(this.valuesInPropertyHierarchy(obj, propName)); }, deepCopy: function (object) { // Recursively traverses `object` and its properties to create a copy. if (!object || typeof object !== "object") return object; var result = Array.isArray(object) ? Array(object.length) : {}; for (var key in object) { if (object.hasOwnProperty(key)) result[key] = obj.deepCopy(object[key]); } return result; }, // -=-=-=-=-=-=-=-=- // stringification // -=-=-=-=-=-=-=-=- typeStringOf: function(obj) { // ignore-in-doc if (obj === null) return "null"; if (typeof obj === "undefined") return "undefined"; return obj.constructor.name; }, shortPrintStringOf: function(obj) { // ignore-in-doc // primitive values if (!this.isMutableType(obj)) return this.safeToString(obj); // constructed objects if (obj.constructor.name !== 'Object' && !Array.isArray(obj)) { if(obj.constructor.name) return obj.constructor.name ? obj.constructor.name : Object.prototype.toString.call(obj).split(" ")[1].split("]")[0]; } // arrays or plain objects var typeString = ""; function displayTypeAndLength(obj, collectionType, firstBracket, secondBracket) { if (obj.constructor.name === collectionType) { typeString += firstBracket; if (obj.length || Object.keys(obj).length) typeString += "..."; typeString += secondBracket; } } displayTypeAndLength(obj, "Object", "{", "}"); displayTypeAndLength(obj, "Array", "[", "]"); return typeString; }, isMutableType: function(obj) { // Is `obj` a value or mutable type? var immutableTypes = ["null", "undefined", "Boolean", "Number", "String"]; return immutableTypes.indexOf(this.typeStringOf(obj)) === -1; }, safeToString: function(obj) { // Like `toString` but catches errors. try { return (obj ? obj.toString() : String(obj)).replace('\n',''); } catch (e) { return '<error printing object>'; } }, asObject: function(obj) { switch (typeof obj) { case 'string': return new String(obj); case 'boolean': return new Boolean(obj); case 'number': return new Number(obj); default: return obj; } } }; // ignore-in-doc // -=-=-=-=-=- // properties // -=-=-=-=-=- var properties = exports.properties = { all: function(object, predicate) { // ignore-in-doc var a = []; for (var name in object) { if ((object.__lookupGetter__(name) || typeof object[name] !== 'function') && (predicate ? predicate(name, object) : true)) a.push(name); } return a; }, allOwnPropertiesOrFunctions: function(obj, predicate) { // ignore-in-doc return Object.getOwnPropertyNames(obj).reduce(function(result, name) { if (predicate ? predicate(obj, name) : true) result.push(name); return result; }, []); }, own: function(object) { // ignore-in-doc var a = []; for (var name in object) { if (object.hasOwnProperty(name) && (object.__lookupGetter__(name) || object[name] !== 'function')) a.push(name); } return a; }, forEachOwn: function(object, func, context) { // ignore-in-doc var result = []; for (var name in object) { if (!object.hasOwnProperty(name)) continue; var value = object[name]; if (value !== 'function') { result.push(func.call(context || this, name, value)); } } return result; }, nameFor: function(object, value) { // ignore-in-doc for (var name in object) { if (object[name] === value) return name; } return undefined; }, values: function(obj) { // ignore-in-doc var values = []; for (var name in obj) values.push(obj[name]); return values; }, ownValues: function(obj) { // ignore-in-doc var values = []; for (var name in obj) { if (obj.hasOwnProperty(name)) values.push(obj[name]); } return values; }, any: function(obj, predicate) { // ignore-in-doc for (var name in obj) { if (predicate(obj, name)) return true; } return false; }, allProperties: function(obj, predicate) { // ignore-in-doc var result = []; for (var name in obj) { if (predicate ? predicate(obj, name) : true) result.push(name); } return result; }, hash: function(obj) { // ignore-in-doc // Using the property names of `obj` to generate a hash value. return Object.keys(obj).sort().join('').hashCode(); } }; // -=-=-=-=-=-=-=-=-=-=-=-=-=- // js object path accessor // -=-=-=-=-=-=-=-=-=-=-=-=-=- // A `Path` is an objectified chain of property names (kind of a "complex" // getter and setter). Path objects can make access and writes into deeply nested // structures more convenient. `Path` provide "safe" get and set operations and // can be used for debugging by providing a hook that allows users to find out // when get/set operations happen. var Path = exports.Path = function Path(p, splitter) { if (p instanceof Path) return p; if (!(this instanceof Path)) return new Path(p, splitter); if (splitter) this.setSplitter(splitter); return this.fromPath(p); } obj.extend(Path, { superclass: Object, type: 'Path', categories: {} }); obj.extend(Path.prototype, { isPathAccessor: true, splitter: '.', fromPath: function(path) { // ignore-in-doc if (obj.isString(path) && path !== '' && path !== this.splitter) { this._parts = path.split(this.splitter); this._path = path; } else if (obj.isArray(path)) { this._parts = [].concat(path); this._path = path.join(this.splitter); } else { this._parts = []; this._path = ''; } return this; }, setSplitter: function(splitter) { // ignore-in-doc if (splitter) this.splitter = splitter; return this; }, parts: function() { /*key names as array*/ return this._parts; }, size: function() { /*show-in-doc*/ return this._parts.length; }, slice: function(n, m) { /*show-in-doc*/ return Path(this.parts().slice(n, m)); }, normalizePath: function() { // ignore-in-doc // FIXME: define normalization return this._path; }, isRoot: function(obj) { return this._parts.length === 0; }, isIn: function(obj) { // Does the Path resolve to a value when applied to `obj`? if (this.isRoot()) return true; var parent = this.get(obj, -1); return parent && parent.hasOwnProperty(this._parts[this._parts.length-1]); }, equals: function(obj) { // Example: // var p1 = Path("foo.1.bar.baz"), p2 = Path(["foo", 1, "bar", "baz"]); // // Path's can be both created via strings or pre-parsed with keys in a list. // p1.equals(p2) // => true return obj && obj.isPathAccessor && this.parts().equals(obj.parts()); }, isParentPathOf: function(otherPath) { // Example: // var p1 = Path("foo.1.bar.baz"), p2 = Path("foo.1.bar"); // p2.isParentPathOf(p1) // => true // p1.isParentPathOf(p2) // => false otherPath = otherPath && otherPath.isPathAccessor ? otherPath : Path(otherPath); var parts = this.parts(), otherParts = otherPath.parts(); for(var i = 0; i < parts.length; i ++) { if (parts[i] != otherParts[i]) return false } return true }, relativePathTo: function(otherPath) { // Example: // var p1 = Path("foo.1.bar.baz"), p2 = Path("foo.1"); // p2.relativePathTo(p1) // => Path(["bar","baz"]) // p1.relativePathTo(p2) // => undefined otherPath = Path(otherPath); return this.isParentPathOf(otherPath) ? otherPath.slice(this.size(), otherPath.size()) : undefined; }, del: function(obj) { if (this.isRoot()) return false; var parent = obj for (var i = 0; i < this._parts.length-1; i++) { var part = this._parts[i]; if (parent.hasOwnProperty(part)) { parent = parent[part]; } else return false; } return delete parent[this._parts[this._parts.length-1]]; }, set: function(obj, val, ensure) { // Deeply resolve path in `obj` and set the resulting property to `val`. If // `ensure` is true, create nested structure in between as necessary. // Example: // var o1 = {foo: {bar: {baz: 42}}}; // var path = Path("foo.bar.baz"); // path.set(o1, 43) // o1 // => {foo: {bar: {baz: 43}}} // var o2 = {foo: {}}; // path.set(o2, 43, true) // o2 // => {foo: {bar: {baz: 43}}} if (this.isRoot()) return undefined; var parent = obj for (var i = 0; i < this._parts.length-1; i++) { var part = this._parts[i]; if (parent.hasOwnProperty(part) && (typeof parent[part] === "object" || typeof parent[part] === "function")) { parent = parent[part]; } else if (ensure) { parent = parent[part] = {}; } else { return undefined; } } return parent[this._parts[this._parts.length-1]] = val; }, get: function(obj, n) { // show-in-doc var parts = n ? this._parts.slice(0, n) : this._parts; return parts.reduce(function(current, pathPart) { return current ? current[pathPart] : current; }, obj); }, concat: function(p, splitter) { // show-in-doc return Path(this.parts().concat(Path(p, splitter).parts())); }, toString: function() { return this.normalizePath(); }, serializeExpr: function() { // ignore-in-doc return 'Path(' + Objects.inspect(this.parts()) + ')'; }, watch: function(options) { // React or be notified on reads or writes to a path in a `target`. Options: // ```js // { // target: OBJECT, // uninstall: BOOLEAN, // onGet: FUNCTION, // onSet: FUNCTION, // haltWhenChanged: BOOLEAN, // verbose: BOOLEAN // } // ``` // Example: // // Quite useful for debugging to find out what call-sites change an object. // var o = {foo: {bar: 23}}; // Path("foo.bar").watch({target: o, verbose: true}); // o.foo.bar = 24; // => You should see: "[object Object].bar changed: 23 -> 24" if (!options || this.isRoot()) return; var target = options.target, parent = this.get(target, -1), propName = exports.arr.last(this.parts()), newPropName = 'propertyWatcher$' + propName, watcherIsInstalled = parent && parent.hasOwnProperty(newPropName), uninstall = options.uninstall, haltWhenChanged = options.haltWhenChanged, showStack = options.showStack, getter = parent.__lookupGetter__(propName), setter = parent.__lookupSetter__(propName); if (!target || !propName || !parent) return; if (uninstall) { if (!watcherIsInstalled) return; delete parent[propName]; parent[propName] = parent[newPropName]; delete parent[newPropName]; var msg = 'Watcher for ' + parent + '.' + propName + ' uninstalled'; show(msg); return; } if (watcherIsInstalled) { var msg = 'Watcher for ' + parent + '.' + propName + ' already installed'; show(msg); return; } if (getter || setter) { var msg = parent + '["' + propName + '"] is a getter/setter, watching not support'; console.log(msg); if (typeof show === "undefined") show(msg); return; } // observe slots, for debugging parent[newPropName] = parent[propName]; parent.__defineSetter__(propName, function(v) { var oldValue = parent[newPropName]; if (options.onSet) options.onSet(v, oldValue); var msg = parent + "." + propName + " changed: " + oldValue + " -> " + v; if (showStack) msg += '\n' + (typeof lively !== "undefined" ? lively.printStack() : console.trace()); if (options.verbose) { console.log(msg); if (typeof show !== 'undefined') show(msg); } if (haltWhenChanged) debugger; return parent[newPropName] = v; }); parent.__defineGetter__(propName, function() { if (options.onGet) options.onGet(parent[newPropName]); return parent[newPropName]; }); var msg = 'Watcher for ' + parent + '.' + propName + ' installed'; console.log(msg); if (typeof show !== 'undefined') show(msg); }, debugFunctionWrapper: function(options) { // ignore-in-doc // options = {target, [haltWhenChanged, showStack, verbose, uninstall]} var target = options.target, parent = this.get(target, -1), funcName = this.parts().last(), uninstall = options.uninstall, haltWhenChanged = options.haltWhenChanged === undefined ? true : options.haltWhenChanged, showStack = options.showStack, func = parent && funcName && parent[funcName], debuggerInstalled = func && func.isDebugFunctionWrapper; if (!target || !funcName || !func || !parent) return; if (uninstall) { if (!debuggerInstalled) return; parent[funcName] = parent[funcName].debugTargetFunction; var msg = 'Uninstalled debugFunctionWrapper for ' + parent + '.' + funcName; console.log(msg); if (typeof show !== 'undefined') show(msg); show(msg); return; } if (debuggerInstalled) { var msg = 'debugFunctionWrapper for ' + parent + '.' + funcName + ' already installed'; console.log(msg); if (typeof show !== 'undefined') show(msg); return; } var debugFunc = parent[funcName] = func.wrap(function(proceed) { var args = Array.from(arguments); if (haltWhenChanged) debugger; if (showStack) show(lively.printStack()); if (options.verbose) show(funcName + ' called'); return args.shift().apply(parent, args); }); debugFunc.isDebugFunctionWrapper = true; debugFunc.debugTargetFunction = func; var msg = 'debugFunctionWrapper for ' + parent + '.' + funcName + ' installed'; console.log(msg); if (typeof show !== 'undefined') show(msg); } }); })(typeof lively !== 'undefined' && lively.lang ? lively.lang : require('./base')); ; /* * Methods to make working with arrays more convenient and collection-like * abstractions for groups, intervals, grids. */ ;(function(exports) { "use strict"; // Pure JS implementations of native Array methods. var arrNative = exports.arrNative = { sort: function(sortFunc) { // show-in-doc if (!sortFunc) { sortFunc = function(x,y) { if (x < y) return -1; if (x > y) return 1; return 0; }; } var len = this.length, sorted = []; for (var i = 0; i < this.length; i++) { var inserted = false; for (var j = 0; j < sorted.length; j++) { if (1 === sortFunc(sorted[j], this[i])) { inserted = true; sorted[j+1] = sorted[j]; sorted[j] = this[i]; break; } } if (!inserted) sorted.push(this[i]); } return sorted; }, filter: function(iterator, context) { // show-in-doc var results = []; for (var i = 0; i < this.length; i++) { if (!this.hasOwnProperty(i)) continue; var value = this[i]; if (iterator.call(context, value, i)) results.push(value); } return results; }, forEach: function(iterator, context) { // show-in-doc for (var i = 0, len = this.length; i < len; i++) { iterator.call(context, this[i], i, this); } }, some: function(iterator, context) { // show-in-doc return this.detect(iterator, context) !== undefined; }, every: function(iterator, context) { // show-in-doc var result = true; for (var i = 0, len = this.length; i < len; i++) { result = result && !! iterator.call(context, this[i], i); if (!result) break; } return result; }, map: function(iterator, context) { // show-in-doc var results = []; this.forEach(function(value, index) { results.push(iterator.call(context, value, index)); }); return results; }, reduce: function(iterator, memo, context) { // show-in-doc var start = 0; if (!arguments.hasOwnProperty(1)) { start = 1; memo = this[0]; } for (var i = start; i < this.length; i++) memo = iterator.call(context, memo, this[i], i, this); return memo; }, reduceRight: function(iterator, memo, context) { // show-in-doc var start = this.length-1; if (!arguments.hasOwnProperty(1)) { start--; memo = this[this.length-1]; } for (var i = start; i >= 0; i--) memo = iterator.call(context, memo, this[i], i, this); return memo; } }; // variety of functions for Arrays var arr = exports.arr = { // -=-=-=-=-=-=-=- // array creations // -=-=-=-=-=-=-=- range: function(begin, end, step) { // Examples: // arr.range(0,5) // => [0,1,2,3,4,5] // arr.range(0,10,2) // => [0,2,4,6,8,10] step = step || 1 var result = []; for (var i = begin; i <= end; i += step) result.push(i); return result; }, from: function(iterable) { // Makes JS arrays out of array like objects like `arguments` or DOM `childNodes` if (!iterable) return []; if (Array.isArray(iterable)) return iterable; if (iterable.toArray) return iterable.toArray(); var length = iterable.length, results = new Array(length); while (length--) results[length] = iterable[length]; return results; }, withN: function(n, obj) { // Example: // arr.withN(3, "Hello") // => ["Hello","Hello","Hello"] var result = new Array(n); while (n > 0) result[--n] = obj; return result; }, genN: function(n, generator) { // Number -> Function -> Array // Takes a generator function that is called for each `n`. // Example: // arr.genN(3, num.random) // => [46,77,95] var result = new Array(n); while (n > 0) result[--n] = generator(n); return result; }, // -=-=-=-=- // filtering // -=-=-=-=- filter: function(array, iterator, context) { // [a] -> (a -> Boolean) -> c? -> [a] // Calls `iterator` for each element in `array` and returns a subset of it // including the elements for which `iterator` returned a truthy value. // Like `Array.prototype.filter`. return array.filter(iterator, context); }, detect: function(arr, iterator, context) { // [a] -> (a -> Boolean) -> c? -> a // returns the first occurrence of an element in `arr` for which iterator // returns a truthy value for (var value, i = 0, len = arr.length; i < len; i++) { value = arr[i]; if (iterator.call(context, value, i)) return value; } return undefined; }, filterByKey: function(arr, key) { // [a] -> String -> [a] // Example: // var objects = [{x: 3}, {y: 4}, {x:5}] // arr.filterByKey(objects, "x") // => [{x: 3},{x: 5}] return arr.filter(function(ea) { return !!ea[key]; }); }, grep: function(arr, filter, context) { // [a] -> String|RegExp -> [a] // `filter` can be a String or RegExp. Will stringify each element in // Example: // ["Hello", "World", "Lively", "User"].grep("l") // => ["Hello","World","Lively"] if (typeof filter === 'string') filter = new RegExp(filter, 'i'); return arr.filter(filter.test.bind(filter)) }, mask: function(array, mask) { // select every element in array for which array's element is truthy // Example: [1,2,3].mask([false, true, false]) => [2] return array.filter(function(_, i) { return !!mask[i]; }); }, reject: function(array, func, context) { // show-in-doc function iterator(val, i) { return !func.call(context, val, i); } return array.filter(iterator); }, rejectByKey: function(array, key) { // show-in-doc return array.filter(function(ea) { return !ea[key]; }); }, without: function(array, elem) { // non-mutating // Example: // arr.without([1,2,3,4,5,6], 3) // => [1,2,4,5,6] return array.filter(function(value) { return value !== elem; }); }, withoutAll: function(array, otherArr) { // non-mutating // Example: // arr.withoutAll([1,2,3,4,5,6], [3,4]) // => [1,2,5,6] return array.filter(function(value) { return otherArr.indexOf(value) === -1; }); }, uniq: function(array, sorted) { // non-mutating // Removes duplicates from array. return array.inject([], function(a, value, index) { if (0 === index || (sorted ? a.last() != value : !a.include(value))) a.push(value); return a; }); }, uniqBy: function(array, comparator, context) { // like `arr.uniq` but with custom equality: `comparator(a,b)` returns // BOOL. True if a and be should be regarded equal, false otherwise. var result = arr.clone(array); for (var i = 0; i < result.length; i++) { var item = array[i]; for (var j = i+1; j < result.length; j++) { if (comparator.call(context, item, result[j])) { arr.removeAt(result, j); j--; } } } return result; }, compact: function(array) { // removes falsy values // Example: // arr.compact([1,2,undefined,4,0]) // => [1,2,4] return array.filter(function(ea) { return !!ea; }); }, mutableCompact: function(array) { // fix gaps that were created with 'delete' var i = 0, j = 0, len = array.length; while (i < len) { if (array.hasOwnProperty(i)) array[j++] = array[i]; i++; } while (j++ < len) array.pop(); return array; }, // -=-=-=-=- // iteration // -=-=-=-=- forEach: function(array, iterator, context) { // [a] -> (a -> Undefined) -> c? -> Undefined // `iterator` is called on each element in `array` for side effects. Like // `Array.prototype.forEach`. return array.forEach(iterator, context); }, zip: function(/*arr, arr2, arr3*/) { // Takes any number of lists as arguments. Combines them elment-wise. // Example: // arr.zip([1,2,3], ["a", "b", "c"], ["A", "B"]) // // => [[1,"a","A"],[2,"b","B"],[3,"c",undefined]] var args = arr.from(arguments), array = args.shift(), iterator = typeof arr.last(args) === 'function' ? args.pop() : function(x) { return x; }, collections = [array].concat(args).map(arr.from); return array.map(function(value, index) { return iterator(arr.pluck(collections, index), index); }); }, flatten: function flatten(array) { // Turns a nested collection into a flat one. // Example: // arr.flatten([1, [2, [3,4,5], [6]], 7,8]) // // => [1,2,3,4,5,6,7,8] return array.reduce(function(flattened, value) { return flattened.concat(Array.isArray(value) ? flatten(value) : [value]); }, []); }, delimWith: function(array, delim) { return array.reduce(function(xs, x) { if (xs.length > 0) xs.push(delim) xs.push(x); return xs; }, []); }, // -=-=-=-=- // mapping // -=-=-=-=- map: function(array, iterator, context) { // [a] -> (a -> b) -> c? -> [b] // Applies `iterator` to each element of `array` and returns a new Array // with the results of those calls. Like `Array.prototype.some`. return array.map(iterator, context); }, invoke: function(array, method, arg1, arg2, arg3, arg4, arg5, arg6) { // Calls `method` on each element in `array`, passing all arguments. Often // a handy way to avoid verbose `map` calls. // Example: arr.invoke(["hello", "world"], "toUpperCase") // => ["HELLO","WORLD"] return array.map(function(ea) { return ea[method](arg1, arg2, arg3, arg4, arg5, arg6); }); }, pluck: function(array, property) { // Returns `property` or undefined from each element of array. For quick // `map`s and similar to `invoke`. // Example: arr.pluck(["hello", "world"], 0) // => ["h","w"] return array.map(function(ea) { return ea[property]; }); }, // -=-=-=-=- // folding // -=-=-=-=- reduce: function(array, iterator, memo, context) { // Array -> Function -> Object? -> Object? -> Object? // Applies `iterator` to each element of `array` and returns a new Array // with the results of those calls. Like `Array.prototype.some`. return array.reduce(iterator, memo, context); }, reduceRight: function(array, iterator, memo, context) { // show-in-doc return array.reduceRight(iterator, memo, context); }, // -=-=-=-=- // testing // -=-=-=-=- isArray: Array.isArray, include: function(array, object) { // Example: arr.include([1,2,3], 2) // => true return array.indexOf(object) !== -1; }, some: function(array, iterator, context) { // [a] -> (a -> Boolean) -> c? -> Boolean // Returns true if there is at least one abject in `array` for which // `iterator` returns a truthy result. Like `Array.prototype.some`. return array.some(iterator, context); }, every: function(array, iterator, context) { // [a] -> (a -> Boolean) -> c? -> Boolean // Returns true if for all abjects in `array` `iterator` returns a truthy // result. Like `Array.prototype.every`. return array.every(iterator, context); }, equals: function(array, otherArray) { // Returns true iff each element in `array` is equal (`==`) to its // corresponding element in `otherArray` var len = array.length; if (!otherArray || len !== otherArray.length) return false; for (var i = 0; i < len; i++) { if (array[i] && otherArray[i] && array[i].equals && otherArray[i].equals) { if (!array[i].equals(otherArray[i])) { return false; } else { continue; } } if (array[i] != otherArray[i]) return false; } return true; }, // -=-=-=-=- // sorting // -=-=-=-=- sort: function(array, sortFunc) { // [a] -> (a -> Number)? -> [a] // Just `Array.prototype.sort` return array.sort(sortFunc); }, sortBy: function(array, iterator, context) { // Example: // arr.sortBy(["Hello", "Lively", "User"], function(ea) { // return ea.charCodeAt(ea.length-1); }) // => ["Hello","User","Lively"] return arr.pluck( array.map(function(value, index) { return {value: value,criteria: iterator.call(context, value, index)}; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a