codecombat/app/core/utils.coffee
Matt Lott c54fea929e Update campaign editor analytics
Optimize analytics.log.event user event data find() to use stream()
instead of exec(), which is better for large result sets
Fix startDay formatting bug
Per-level recent sessions to 100
2015-01-07 14:49:24 -08:00

175 lines
5.9 KiB
CoffeeScript

module.exports.clone = (obj) ->
return obj if obj is null or typeof (obj) isnt 'object'
temp = obj.constructor()
for key of obj
temp[key] = module.exports.clone(obj[key])
temp
module.exports.combineAncestralObject = (obj, propertyName) ->
combined = {}
while obj?[propertyName]
for key, value of obj[propertyName]
continue if combined[key]
combined[key] = value
if obj.__proto__
obj = obj.__proto__
else
# IE has no __proto__. TODO: does this even work? At most it doesn't crash.
obj = Object.getPrototypeOf(obj)
combined
module.exports.normalizeFunc = (func_thing, object) ->
# func could be a string to a function in this class
# or a function in its own right
object ?= {}
if _.isString(func_thing)
func = object[func_thing]
if not func
console.error "Could not find method #{func_thing} in object", object
return => null # always return a func, or Mediator will go boom
func_thing = func
return func_thing
module.exports.hexToHSL = (hex) ->
rgbToHsl(hexToR(hex), hexToG(hex), hexToB(hex))
hexToR = (h) -> parseInt (cutHex(h)).substring(0, 2), 16
hexToG = (h) -> parseInt (cutHex(h)).substring(2, 4), 16
hexToB = (h) -> parseInt (cutHex(h)).substring(4, 6), 16
cutHex = (h) -> (if (h.charAt(0) is '#') then h.substring(1, 7) else h)
module.exports.hslToHex = (hsl) ->
'#' + (toHex(n) for n in hslToRgb(hsl...)).join('')
toHex = (n) ->
h = Math.floor(n).toString(16)
h = '0'+h if h.length is 1
h
module.exports.i18n = (say, target, language=me.get('preferredLanguage', true), fallback='en') ->
generalResult = null
fallbackResult = null
fallforwardResult = null # If a general language isn't available, the first specific one will do
matches = (/\w+/gi).exec(language)
generalName = matches[0] if matches
for localeName, locale of say.i18n
continue if localeName is '-'
if target of locale
result = locale[target]
else continue
return result if localeName is language
generalResult = result if localeName is generalName
fallbackResult = result if localeName is fallback
fallforwardResult = result if localeName.indexOf(language) is 0 and not fallforwardResult?
return generalResult if generalResult?
return fallforwardResult if fallforwardResult?
return fallbackResult if fallbackResult?
return say[target] if target of say
null
module.exports.getByPath = (target, path) ->
throw new Error 'Expected an object to match a query against, instead got null' unless target
pieces = path.split('.')
obj = target
for piece in pieces
return undefined unless piece of obj
obj = obj[piece]
obj
module.exports.isID = (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
module.exports.round = _.curry (digits, n) ->
n = +n.toFixed(digits)
positify = (func) -> (params) -> (x) -> if x > 0 then func(params)(x) else 0
# f(x) = ax + b
createLinearFunc = (params) ->
(x) -> (params.a or 1) * x + (params.b or 0)
# f(x) = ax² + bx + c
createQuadraticFunc = (params) ->
(x) -> (params.a or 1) * x * x + (params.b or 1) * x + (params.c or 0)
# f(x) = a log(b (x + c)) + d
createLogFunc = (params) ->
(x) -> if x > 0 then (params.a or 1) * Math.log((params.b or 1) * (x + (params.c or 0))) + (params.d or 0) else 0
module.exports.functionCreators =
linear: positify(createLinearFunc)
quadratic: positify(createQuadraticFunc)
logarithmic: positify(createLogFunc)
# Call done with true to satisfy the 'until' goal and stop repeating func
module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) ->
waitSoFar = 0
(done = (success) ->
if (waitSoFar += wait) <= totalWait and not success
_.delay (-> func done), wait) false
module.exports.grayscale = (imageData) ->
d = imageData.data
for i in [0..d.length] by 4
r = d[i]
g = d[i+1]
b = d[i+2]
v = 0.2126*r + 0.7152*g + 0.0722*b
d[i] = d[i+1] = d[i+2] = v
imageData
# Deep compares l with r, with the exception that undefined values are considered equal to missing values
# Very practical for comparing Mongoose documents where undefined is not allowed, instead fields get deleted
module.exports.kindaEqual = compare = (l, r) ->
if _.isObject(l) and _.isObject(r)
for key in _.union Object.keys(l), Object.keys(r)
return false unless compare l[key], r[key]
return true
else if l is r
return true
else
return false
# Return UTC string "YYYY-MM-DD" for today + offset
module.exports.getUTCDay = (offset=0) ->
# TODO: Move to utility
day = new Date()
day.setDate(day.getUTCDate() + offset)
partYear = day.getUTCFullYear()
partMonth = (day.getUTCMonth() + 1)
partMonth = "0" + partMonth if partMonth < 10
partDay = day.getUTCDate()
partDay = "0" + partDay if partDay < 10
"#{partYear}-#{partMonth}-#{partDay}"
# Fast, basic way to replace text in an element when you don't need much.
# http://stackoverflow.com/a/4962398/540620
if document?
dummy = document.createElement 'div'
dummy.innerHTML = 'text'
TEXT = if dummy.textContent is 'text' then 'textContent' else 'innerText'
module.exports.replaceText = (elems, text) ->
elem[TEXT] = text for elem in elems
null
# Add a stylesheet rule
# http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript/26230472#26230472
# Don't use wantonly, or we'll have to implement a simple mechanism for clearing out old rules.
if document?
module.exports.injectCSS = ((doc) ->
# wrapper for all injected styles and temp el to create them
wrap = doc.createElement("div")
temp = doc.createElement("div")
# rules like "a {color: red}" etc.
return (cssRules) ->
# append wrapper to the body on the first call
unless wrap.id
wrap.id = "injected-css"
wrap.style.display = "none"
doc.body.appendChild wrap
# <br> for IE: http://goo.gl/vLY4x7
temp.innerHTML = "<br><style>" + cssRules + "</style>"
wrap.appendChild temp.children[1]
return
)(document)