<commit_before>// This module is neither officially-endorsed or supported by Wink
// however, Wink publishes an API and there are lots of wonderful open source tools that allow us to build on that API!

// jshint asi: true

var wink = { indicator: {} }

// data, e.g., datasources["WinKData"]["light_bulb"]["Kitchen"]

wink.indicator.value = function(data, property) {
    var value

    if (!data) return false
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return false
    if ({remotes: true, sprinklers: true }[data.object_type]) return false
    if (typeof property !== 'undefined') {
        if ((typeof data[property] === 'undefined') || (data[property] === null )) return true
        return { sensor_pods     : true
               , smoke_detectors : true
               , thermostats     : (property === 'temperature') || (property === 'humidity') || (property === 'mode')
	       , air_conditioners: (property === 'temperature')
               }[data.object_type] || (property === 'battery')
    }

    value = { buttons         : true
            , gangs           : true
            , garage_doors    : data.position > 0
            , hubs            : true
            , linked_services : true
            , light_bulbs     : data.powered
            , locks           : data.locked
            , sensor_pods     : true
            , shades          : data.position > 0
            , smoke_detectors : true
            , thermostats     : data.powered
            , air_conditioners: data.mode !== 'off'
            , unknown_devices: true
            , propane_tanks: true
            , piggy_banks: true
            , cameras: true
            }[data.object_type]
    return (typeof value !== 'undefined' ? value : data.powered)
}

wink.indicator.on_text = function(data, property) {
    return on_text(data, property)
}

var on_text = function(data, property) {
    var text, value

    if (!data) return ''
    text =  { buttons         : ''
	    , piggy_banks: 'Balance: ' + dollars(data.balance) + ' / Goal: ' + dollars(data.savings_goal)
            , garage_doors    : 'OPEN'
            , hubs            : (!data.update_needed ? 'OK' : 'UPDATE NEEDED')
            , linked_services : 'OK'
            , light_bulbs     : (data.brightness == 1.0  ? ''     : pct(data.brightness))
            , locks           : 'LOCKED'
            , sensor_pods     : 'PROPERTY?'
            , shades          : (data.position == 1.0    ? 'OPEN' : pct(data.position))
            , smoke_detectors : 'OK'
            , thermostats     : (data.cool_active ? 'COOL' : data.heat_active ? 'HEAT' : data.aux_active ? 'AUX' : data.fan_active ? 'FAN' : data.mode)
            , air_conditioners: (data.powered ? 'COOL to ' + dual_temp(data.max_set_point) : 'IDLE')
            , propane_tanks   : 'OK'
            }[data.object_type] || 'ON'
    if (typeof property === 'undefined') return text
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''
    value = data[property]
    text =  { co_detected     : (value ? 'CO DETECTED'     : '')
            , fault           : (value ? 'FAULT DETECTED'  : '')
            , liquid_detected : (value ? 'LEAK DETECTED'   : '')
            , locked          : (value ? 'LOCKED'          : 'UNLOCKED')
            , loudness        : (value ? 'LOUD'            : '')
            , noise           : (value ? 'NOISY'           : '')
            , motion          : (value ? 'MOTION'          : '')
            , occupied        : (value ? 'OCCUPIED'        : 'VACANT')
            , opened          : (value ? 'OPEN'            : 'CLOSED')
            , smoke_detected  : (value ? 'SMOKE DETECTED'  : '')
            , tamper_detected : (value ? 'TAMPER DETECTED' : '')
            , vibration       : (value ? 'VIBRATION'       : '')
            , presence        : (value ? 'HOME'		   : 'NO')
            , timeframe       : (value ? 'NIGHT'       : 'DAY')
            , rain            : (value ? 'RAIN'        : 'NO RAIN')
            , pressure        : value+' mBar'
            , luminance       : value+' cd/m2'
            , lat             : value
            , lon             : value
            , accuracy        : value+ (value<=1 ? ' meter' :' meters')
            , lastWaypoint    : value
            , lastEvent       : value
            , distanceFromHome: value
            , trigger         : geo_trigger(value)
            , lastUpdated     : timeConverter(value)
            , uvindex         : getUvIndex(value)
            , battery         : pct(value)
            , brightness      : pct(value)
            , co_severity     : pct(value)
            , humidity        : pct(value)
            , smoke_severity  : pct(value)
            , remaining       : pct(value)
            , temperature     : dual_temp(value)
            , mode	      : tstat_mode(data,value)

            }[property]
    if (text === '') text = 'OK'

    return text
}

var timeConverter = function (UNIX_timestamp){
  var a = new Date(UNIX_timestamp * 1000);
  var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  var year = a.getFullYear();
  var month = months[a.getMonth()];
  var date = a.getDate();
  var hour = a.getHours();
  var min = a.getMinutes();
  var sec = a.getSeconds();
  var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + (min<10 ? '0'+min : min);
  return time;
}

var getUvIndex = function(val){
    var value;
    value=parseInt(val)
    return value+' '+(value <=2 ? 'Minimal' : value <=4 ? 'Low' : value<=6 ? 'Moderate' : value<=9 ? 'High!' : 'Very High!');
}

var geo_trigger = function(value){
    var text;
    text='';
    text={
        p : 'ping',
        c : 'gps geofence',
        b : 'beacon geofence',
        r : 'remote request',
        u : 'manual publish',
        t : 'moving publish',
        a : 'auto update'
    }[value]
    return (text === '' ? 'auto update' : text);
}

var pct = function(value) {
    return ((value > 1.0 ? value : value * 100).toFixed(0) + '%')
}
var dual_temp = function (value) {
    return (typeof value === 'number' ? (value.toFixed(1) + 'C / ' + ((value * 1.8) + 32).toFixed(1) + 'F') : '')
}

var dollars = function (value) {
    return (typeof value === 'number' ? (value / 100).toFixed(2) : '')
}

var tstat_mode = function (data, value) {
    //    return value
    if (data.users_away) return 'Away mode'
    return (value == 'cool_only' ? 'cool to: ' + dual_temp(data.max_set_point) : value == 'heat_only' ? 'heat to: ' + dual_temp(data.min_set_point) : 'auto: ' + dual_temp(data.min_set_point) + ' - ' + dual_temp(data.max_set_point))
}

wink.indicator.off_text = function(data, property) {
    return off_text(data, property)
}

var off_text = function(data, property) {
    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return 'ERR'
    if (typeof property !== 'undefined') return ''

    return { buttons          : 'IDLE'
           , garage_doors     : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           , locks            : 'UNLOCKED'
           , shades           : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           }[data.object_type] || 'OFF'
}

freeboard.addStyle('.wink-indicator-circle', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;margin-right:10px;");
freeboard.addStyle('.wink-indicator-triangle', "border-radius:50%;width:0;height:0;border-left:15px solid transparent;border-right:15px solid transparent;margin-top:5px;float:left;margin-right:10px;");

wink.indicator.style_element = function(data, property) {
    var params = style_element(data, property)

    if (params === '') return params

    if (params.shape == 'triangle') {
        return ('

')
    }
    return ('

')
}

DynCol =function (hex, lum) {

	// validate hex string
	hex = String(hex).replace(/[^0-9a-f]/gi, '');
	if (hex.length < 6) {
		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
	}
	lum = lum || 0;

	// convert to decimal and change luminosity
	var rgb = "#", c, i;
	for (i = 0; i < 3; i++) {
		c = parseInt(hex.substr(i*2,2), 16);
		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
		rgb += ("00"+c).substr(c.length);
	}
	return rgb;
}

var style_element = function(data, property) {
    var color, value
      , black  = '#000000'
      , blue   = '#00b8f1'
      , green  = '#00ff00'
      , red    = '#ff0000'
      , shape  = 'circle'
      , white  = '#ffffff'
      , yellow = '#eed202'
      , day = '#FFBF00'
      , rain = '#81DAF5'
      , normal = '#40FF00'
      , moderate = '#FFFF00'
      , warning = '#FF8000'
      , danger = '#FF0000'

    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return { color: red, shape: 'triangle' }

    color = { binary_switches : (data.powered          ? white  : black)
	    , cameras	      : (data.capturing_video ? green : data.connection ? blue : black)
            , outlet          : (data.powered ? white : black)
            , buttons         : (!data.pressed         ? blue  : green)
            , garage_doors    : (data.position === 0.0 ? blue  : yellow)
            , hubs            : (!data.update_needed   ? blue  : yellow)
            , light_bulbs     : (data.powered          ? DynCol("#905030",data.brightness*5) : black)
            , locks           : (data.locked           ? blue  : yellow)
            , shades          : (data.position === 0.0 ? blue  : data.position === null ? yellow : green)
            , thermostats     : ({ cool_only: blue, heat_only: red }[data.mode] || green)
            , air_conditioners: (data.powered ? blue : green)
            , propane_tanks   : (data.remaining == 0 ? red : data.remaining == 1 ? blue : data.remaining > 0.66 ? green : data.remaining > 0.33 ? yellow : red)
            , piggy_banks     : '#'+data.nose_color
            }[data.object_type] || blue

    if (typeof property === 'undefined') return { color: color, shape: shape }
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''

    value = data[property]
    color = { co_detected     : value && red
            , fault           : value && red
            , liquid_detected : value && red
            , locked          : (!value) && red
            , loudness        : value && yellow
            , noise           : value && yellow
            , motion          : value && yellow
            , occupied        : value && yellow
            , opened          : value && yellow
            , presence        : value && yellow
            , smoke_detected  : value && red
            , tamper_detected : value && red
            , vibration       : value && yellow
            , rain            : value && rain
            , timeframe       : (value ? black : day)
            , uvindex         : (value <=4 ? normal : value <=7 ? moderate : value <= 9 ? warning : danger)
            , lat             : blue
            , lon             : blue
            , accuracy        : blue
            , distanceFromHome: blue
            , lastWaypoint    : blue
            , lastEvent       : (value=='enter' ? yellow : blue)
            , battery         : (value ==  1.0 ? blue : value > 0.66 ? green : value > 0.33 ? yellow : red)
            , brightness      : false
            , co_severity     : (value > 0) && red
            , humidity        : false
            , smoke_severity  : (value > 0) && red
	        , mode	      : (value=='cool_only' ? blue : red)
            , temperature     : false
            }[property] || blue
//    if ((color != blue) && (color != green)) shape = 'triangle'

    return { color: color, shape: shape }
}

<commit_msg>small fixes<commit_after>// This module is neither officially-endorsed or supported by Wink
// however, Wink publishes an API and there are lots of wonderful open source tools that allow us to build on that API!

// jshint asi: true

var wink = { indicator: {} }

// data, e.g., datasources["WinKData"]["light_bulb"]["Kitchen"]

wink.indicator.value = function(data, property) {
    var value

    if (!data) return false
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return false
    if ({remotes: true, sprinklers: true }[data.object_type]) return false
    if (typeof property !== 'undefined') {
        if ((typeof data[property] === 'undefined') || (data[property] === null )) return true
        return { sensor_pods     : true
               , smoke_detectors : true
               , thermostats     : (property === 'temperature') || (property === 'humidity') || (property === 'mode')
	       , air_conditioners: (property === 'temperature')
               }[data.object_type] || (property === 'battery')
    }

    value = { buttons         : true
            , gangs           : true
            , garage_doors    : data.position > 0
            , hubs            : true
            , linked_services : true
            , light_bulbs     : data.powered
            , locks           : data.locked
            , sensor_pods     : true
            , shades          : data.position > 0
            , smoke_detectors : true
            , thermostats     : data.powered
            , air_conditioners: data.mode !== 'off'
            , unknown_devices: true
            , propane_tanks: true
            , piggy_banks: true
            , cameras: true
            }[data.object_type]
    return (typeof value !== 'undefined' ? value : data.powered)
}

wink.indicator.on_text = function(data, property) {
    return on_text(data, property)
}

var on_text = function(data, property) {
    var text, value

    if (!data) return ''
    text =  { buttons         : ''
	    , piggy_banks: 'Balance: ' + dollars(data.balance) + ' / Goal: ' + dollars(data.savings_goal)
            , garage_doors    : 'OPEN'
            , hubs            : (!data.update_needed ? 'OK' : 'UPDATE NEEDED')
            , linked_services : 'OK'
            , light_bulbs     : (data.brightness == 1.0  ? ''     : pct(data.brightness))
            , locks           : 'LOCKED'
            , sensor_pods     : 'PROPERTY?'
            , shades          : (data.position == 1.0    ? 'OPEN' : pct(data.position))
            , smoke_detectors : 'OK'
            , thermostats     : (data.cool_active ? 'COOL' : data.heat_active ? 'HEAT' : data.aux_active ? 'AUX' : data.fan_active ? 'FAN' : data.mode)
            , air_conditioners: (data.powered ? 'COOL to ' + dual_temp(data.max_set_point) : 'IDLE')
            , propane_tanks   : 'OK'
            }[data.object_type] || 'ON'
    if (typeof property === 'undefined') return text
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''
    value = data[property]
    text =  { co_detected     : (value ? 'CO DETECTED'     : '')
            , fault           : (value ? 'FAULT DETECTED'  : '')
            , liquid_detected : (value ? 'LEAK DETECTED'   : '')
            , locked          : (value ? 'LOCKED'          : 'UNLOCKED')
            , loudness        : (value ? 'LOUD'            : '')
            , noise           : (value ? 'NOISY'           : '')
            , motion          : (value ? 'MOTION'          : '')
            , occupied        : (value ? 'OCCUPIED'        : 'VACANT')
            , opened          : (value ? 'OPEN'            : 'CLOSED')
            , smoke_detected  : (value ? 'SMOKE DETECTED'  : '')
            , tamper_detected : (value ? 'TAMPER DETECTED' : '')
            , vibration       : (value ? 'VIBRATION'       : '')
            , presence        : (value ? 'HOME'		   : 'NO')
            , timeframe       : (value ? 'NIGHT'       : 'DAY')
            , rain            : (value ? 'RAIN'        : 'NO RAIN')
            , pressure        : value+' mBar'
            , luminance       : value+' cd/m2'
            , lat             : value
            , lon             : value
            , accuracy        : value+ (value<=1 ? ' meter' :' meters')
            , lastWaypoint    : value
            , lastEvent       : value
            , distanceFromHome: value
            , trigger         : geo_trigger(value)
            , lastUpdated     : timeConverter(value)
            , uvindex         : getUvIndex(value)
            , battery         : pct(value)
            , brightness      : pct(value)
            , co_severity     : pct(value)
            , humidity        : pct(value)
            , smoke_severity  : pct(value)
            , remaining       : pct(value)
            , temperature     : dual_temp(value)
            , mode	      : tstat_mode(data,value)

            }[property]
    if (text === '') text = 'OK'

    return text
}

var timeConverter = function (UNIX_timestamp){
  var a = new Date(UNIX_timestamp * 1000);
  var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  var year = a.getFullYear();
  var month = months[a.getMonth()];
  var date = a.getDate();
  var hour = a.getHours();
  var min = a.getMinutes();
  var sec = a.getSeconds();
  var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + (min<10 ? '0'+min : min);
  return time;
}

var getUvIndex = function(val){
    var value;
    value=parseInt(val);
    return value+' '+(value <=2 ? 'Minimal' : value <=4 ? 'Low' : value<=6 ? 'Moderate' : value<=9 ? 'High!' : 'Very High!');
}

var geo_trigger = function(value){
    var text;
    text='';
    text={
        p : 'ping',
        c : 'gps geofence',
        b : 'beacon geofence',
        r : 'remote request',
        u : 'manual publish',
        t : 'moving publish',
        a : 'auto update',
        none: 'auto update'
    }[value]
    return (text === '' ? 'auto update' : text);
}

var pct = function(value) {
    return ((value > 1.0 ? value : value * 100).toFixed(0) + '%')
}
var dual_temp = function (value) {
    return (typeof value === 'number' ? (value.toFixed(1) + 'C / ' + ((value * 1.8) + 32).toFixed(1) + 'F') : '')
}

var dollars = function (value) {
    return (typeof value === 'number' ? (value / 100).toFixed(2) : '')
}

var tstat_mode = function (data, value) {
    //    return value
    if (data.users_away) return 'Away mode'
    return (value == 'cool_only' ? 'cool to: ' + dual_temp(data.max_set_point) : value == 'heat_only' ? 'heat to: ' + dual_temp(data.min_set_point) : 'auto: ' + dual_temp(data.min_set_point) + ' - ' + dual_temp(data.max_set_point))
}

wink.indicator.off_text = function(data, property) {
    return off_text(data, property)
}

var off_text = function(data, property) {
    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return 'ERR'
    if (typeof property !== 'undefined') return ''

    return { buttons          : 'IDLE'
           , garage_doors     : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           , locks            : 'UNLOCKED'
           , shades           : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           }[data.object_type] || 'OFF'
}

freeboard.addStyle('.wink-indicator-circle', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;margin-right:10px;");
freeboard.addStyle('.wink-indicator-triangle', "border-radius:50%;width:0;height:0;border-left:15px solid transparent;border-right:15px solid transparent;margin-top:5px;float:left;margin-right:10px;");

wink.indicator.style_element = function(data, property) {
    var params = style_element(data, property)

    if (params === '') return params

    if (params.shape == 'triangle') {
        return ('

')
    }
    return ('

')
}

DynCol =function (hex, lum) {

	// validate hex string
	hex = String(hex).replace(/[^0-9a-f]/gi, '');
	if (hex.length < 6) {
		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
	}
	lum = lum || 0;

	// convert to decimal and change luminosity
	var rgb = "#", c, i;
	for (i = 0; i < 3; i++) {
		c = parseInt(hex.substr(i*2,2), 16);
		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
		rgb += ("00"+c).substr(c.length);
	}
	return rgb;
}

var style_element = function(data, property) {
    var color, value
      , black  = '#000000'
      , blue   = '#00b8f1'
      , green  = '#00ff00'
      , red    = '#ff0000'
      , shape  = 'circle'
      , white  = '#ffffff'
      , yellow = '#eed202'
      , day = '#FFBF00'
      , rain = '#81DAF5'
      , normal = '#40FF00'
      , moderate = '#FFFF00'
      , warning = '#FF8000'
      , danger = '#FF0000'

    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return { color: red, shape: 'triangle' }

    color = { binary_switches : (data.powered          ? white  : black)
	    , cameras	      : (data.capturing_video ? green : data.connection ? blue : black)
            , outlet          : (data.powered ? white : black)
            , buttons         : (!data.pressed         ? blue  : green)
            , garage_doors    : (data.position === 0.0 ? blue  : yellow)
            , hubs            : (!data.update_needed   ? blue  : yellow)
            , light_bulbs     : (data.powered          ? DynCol("#905030",data.brightness*5) : black)
            , locks           : (data.locked           ? blue  : yellow)
            , shades          : (data.position === 0.0 ? blue  : data.position === null ? yellow : green)
            , thermostats     : ({ cool_only: blue, heat_only: red }[data.mode] || green)
            , air_conditioners: (data.powered ? blue : green)
            , propane_tanks   : (data.remaining == 0 ? red : data.remaining == 1 ? blue : data.remaining > 0.66 ? green : data.remaining > 0.33 ? yellow : red)
            , piggy_banks     : '#'+data.nose_color
            }[data.object_type] || blue

    if (typeof property === 'undefined') return { color: color, shape: shape }
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''

    value = data[property]
    color = { co_detected     : value && red
            , fault           : value && red
            , liquid_detected : value && red
            , locked          : (!value) && red
            , loudness        : value && yellow
            , noise           : value && yellow
            , motion          : value && yellow
            , occupied        : value && yellow
            , opened          : value && yellow
            , presence        : value && yellow
            , smoke_detected  : value && red
            , tamper_detected : value && red
            , vibration       : value && yellow
            , rain            : value && rain
            , timeframe       : (value ? black : day)
            , uvindex         : (value <=4 ? normal : value <=7 ? moderate : value <= 9 ? warning : danger)
            , lat             : blue
            , lon             : blue
            , accuracy        : blue
            , distanceFromHome: blue
            , lastWaypoint    : blue
            , lastEvent       : (value=='enter' ? yellow : blue)
            , battery         : (value ==  1.0 ? blue : value > 0.66 ? green : value > 0.33 ? yellow : red)
            , brightness      : false
            , co_severity     : (value > 0) && red
            , humidity        : false
            , smoke_severity  : (value > 0) && red
	        , mode	      : (value=='cool_only' ? blue : red)
            , temperature     : false
            }[property] || blue
//    if ((color != blue) && (color != green)) shape = 'triangle'

    return { color: color, shape: shape }
}

<|endoftext|>


<commit_before>// This module is neither officially-endorsed or supported by Wink
// however, Wink publishes an API and there are lots of wonderful open source tools that allow us to build on that API!

// jshint asi: true

var wink = { indicator: {} }

// data, e.g., datasources["WinKData"]["light_bulb"]["Kitchen"]

wink.indicator.value = function(data, property) {
    var value

    if (!data) return false
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return false
    if ({remotes: true, sprinklers: true }[data.object_type]) return false
    if (typeof property !== 'undefined') {
        if ((typeof data[property] === 'undefined') || (data[property] === null )) return true
        return { sensor_pods     : true
               , smoke_detectors : true
               , thermostats     : (property === 'temperature') || (property === 'humidity') || (property === 'mode')
	       , air_conditioners: (property === 'temperature')
               }[data.object_type] || (property === 'battery')
    }

    value = { buttons         : true
            , gangs           : true
            , garage_doors    : data.position > 0
            , hubs            : true
            , linked_services : true
            , light_bulbs     : data.powered
            , locks           : data.locked
            , sensor_pods     : true
            , shades          : data.position > 0
            , smoke_detectors : true
            , thermostats     : data.powered
            , air_conditioners: data.mode !== 'off'
            , unknown_devices: true
            , propane_tanks: true
            , piggy_banks: true
            , cameras: true
            }[data.object_type]
    return (typeof value !== 'undefined' ? value : data.powered)
}

wink.indicator.on_text = function(data, property) {
    return on_text(data, property)
}

var on_text = function(data, property) {
    var text, value

    if (!data) return ''
    text =  { buttons         : ''
	    , piggy_banks: 'Balance: ' + dollars(data.balance) + ' / Goal: ' + dollars(data.savings_goal)
            , garage_doors    : 'OPEN'
            , hubs            : (!data.update_needed ? 'OK' : 'UPDATE NEEDED')
            , linked_services : 'OK'
            , light_bulbs     : (data.brightness == 1.0  ? ''     : pct(data.brightness))
            , locks           : 'LOCKED'
            , sensor_pods     : 'PROPERTY?'
            , shades          : (data.position == 1.0    ? 'OPEN' : pct(data.position))
            , smoke_detectors : 'OK'
            , thermostats     : (data.cool_active ? 'COOL' : data.heat_active ? 'HEAT' : data.aux_active ? 'AUX' : data.fan_active ? 'FAN' : data.mode)
            , air_conditioners: (data.powered ? 'COOL to ' + dual_temp(data.max_set_point) : 'IDLE')
            , propane_tanks   : 'OK'
            }[data.object_type] || 'ON'
    if (typeof property === 'undefined') return text
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''
    value = data[property]
    text =  { co_detected     : (value ? 'CO DETECTED'     : '')
            , fault           : (value ? 'FAULT DETECTED'  : '')
            , liquid_detected : (value ? 'LEAK DETECTED'   : '')
            , locked          : (value ? 'LOCKED'          : 'UNLOCKED')
            , loudness        : (value ? 'LOUD'            : '')
            , noise           : (value ? 'NOISY'           : '')
            , motion          : (value ? 'MOTION'          : '')
            , occupied        : (value ? 'OCCUPIED'        : 'VACANT')
            , opened          : (value ? 'OPEN'            : 'CLOSED')
            , presence        : (value ? 'PRESENCE'        : '')
            , smoke_detected  : (value ? 'SMOKE DETECTED'  : '')
            , tamper_detected : (value ? 'TAMPER DETECTED' : '')
            , vibration       : (value ? 'VIBRATION'       : '')
            , presence        : (value ? 'HOME'		   : 'OUT')

            , battery         : pct(value)
            , brightness      : pct(value)
            , co_severity     : pct(value)
            , humidity        : pct(value)
            , smoke_severity  : pct(value)

            , remaining       : pct(value)
            , temperature     : dual_temp(value)
//            , mode            : (value ==  'cool_only' ? 'Cool' : value == 'heat_only' ? 'Heat' : 'Auto')
	    , mode	      : tstat_mode(data,value)

            }[property]
    if (text === '') text = 'OK'

    return text
}

var pct = function(value) {
    return ((value > 1.0 ? value : value * 100).toFixed(0) + '%')
}
var dual_temp = function (value) {
    return (typeof value === 'number' ? (value.toFixed(1) + 'C / ' + ((value * 1.8) + 32).toFixed(1) + 'F') : '')
}

var dollars = function (value) {
    return (typeof value === 'number' ? (value / 100).toFixed(2) : '')
}

var tstat_mode = function (data, value) {
    //    return value
    if (data.users_away) return 'Away mode'
    return (value == 'cool_only' ? 'cool to: ' + dual_temp(data.max_set_point) : value == 'heat_only' ? 'heat to: ' + dual_temp(data.min_set_point) : 'auto: ' + dual_temp(data.min_set_point) + ' - ' + dual_temp(data.max_set_point))
}

wink.indicator.off_text = function(data, property) {
    return off_text(data, property)
}

var off_text = function(data, property) {
    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return 'ERR'
    if (typeof property !== 'undefined') return ''

    return { buttons          : 'IDLE'
           , garage_doors     : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           , locks            : 'UNLOCKED'
           , shades           : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           }[data.object_type] || 'OFF'
}

freeboard.addStyle('.wink-indicator-circle', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;margin-right:10px;");
freeboard.addStyle('.wink-indicator-triangle', "border-radius:50%;width:0;height:0;border-left:15px solid transparent;border-right:15px solid transparent;margin-top:5px;float:left;margin-right:10px;");

wink.indicator.style_element = function(data, property) {
    var params = style_element(data, property)

    if (params === '') return params

    if (params.shape == 'triangle') {
        return ('

')
    }
    return ('

')
}

DynCol =function (hex, lum) {

	// validate hex string
	hex = String(hex).replace(/[^0-9a-f]/gi, '');
	if (hex.length < 6) {
		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
	}
	lum = lum || 0;

	// convert to decimal and change luminosity
	var rgb = "#", c, i;
	for (i = 0; i < 3; i++) {
		c = parseInt(hex.substr(i*2,2), 16);
		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
		rgb += ("00"+c).substr(c.length);
	}
	return rgb;
}

var style_element = function(data, property) {
    var color, value
      , black  = '#000000'
      , blue   = '#00b8f1'
      , green  = '#00ff00'
      , red    = '#ff0000'
      , shape  = 'circle'
      , white  = '#ffffff'
      , yellow = '#eed202'

    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return { color: red, shape: 'triangle' }

    color = { binary_switches : (data.powered          ? white  : black)
	    , cameras	      : (data.capturing_video ? green : data.connection ? blue : black)
            , outlet          : (data.powered ? white : black)
            , buttons         : (!data.pressed         ? blue  : green)
            , garage_doors    : (data.position === 0.0 ? blue  : yellow)
            , hubs            : (!data.update_needed   ? blue  : yellow)
            , light_bulbs     : (data.powered          ? DynCol("#905030",data.brightness*5) : black)
            , locks           : (data.locked           ? blue  : yellow)
            , shades          : (data.position === 0.0 ? blue  : data.position === null ? yellow : green)
            , thermostats     : ({ cool_only: blue, heat_only: red }[data.mode] || green)
            , air_conditioners: (data.powered ? blue : green)
            , propane_tanks   : (data.remaining == 0 ? red : data.remaining == 1 ? blue : data.remaining > 0.66 ? green : data.remaining > 0.33 ? yellow : red)
            , piggy_banks     : '#'+data.nose_color
            }[data.object_type] || blue

    if (typeof property === 'undefined') return { color: color, shape: shape }
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''

    value = data[property]
    color = { co_detected     : value && red
            , fault           : value && red
            , liquid_detected : value && red
            , locked          : (!value) && red
            , loudness        : value && yellow
            , noise           : value && yellow
            , motion          : value && yellow
            , occupied        : value && yellow
            , opened          : value && yellow
            , presence        : value && yellow
            , smoke_detected  : value && red
            , tamper_detected : value && red
            , vibration       : value && yellow

            , battery         : (value ==  1.0 ? blue : value > 0.66 ? green : value > 0.33 ? yellow : red)
            , brightness      : false
            , co_severity     : (value > 0) && red
            , humidity        : false
            , smoke_severity  : (value > 0) && red
	    , mode	      : (value=='cool_only' ? blue : red)
            , temperature     : false
            }[property] || blue
    if ((color != blue) && (color != green)) shape = 'triangle'

    return { color: color, shape: shape }
}

<commit_msg>Update wink.js<commit_after>// This module is neither officially-endorsed or supported by Wink
// however, Wink publishes an API and there are lots of wonderful open source tools that allow us to build on that API!

// jshint asi: true

var wink = { indicator: {} }

// data, e.g., datasources["WinKData"]["light_bulb"]["Kitchen"]

wink.indicator.value = function(data, property) {
    var value

    if (!data) return false
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return false
    if ({remotes: true, sprinklers: true }[data.object_type]) return false
    if (typeof property !== 'undefined') {
        if ((typeof data[property] === 'undefined') || (data[property] === null )) return true
        return { sensor_pods     : true
               , smoke_detectors : true
               , thermostats     : (property === 'temperature') || (property === 'humidity') || (property === 'mode')
	       , air_conditioners: (property === 'temperature')
               }[data.object_type] || (property === 'battery')
    }

    value = { buttons         : true
            , gangs           : true
            , garage_doors    : data.position > 0
            , hubs            : true
            , linked_services : true
            , light_bulbs     : data.powered
            , locks           : data.locked
            , sensor_pods     : true
            , shades          : data.position > 0
            , smoke_detectors : true
            , thermostats     : data.powered
            , air_conditioners: data.mode !== 'off'
            , unknown_devices: true
            , propane_tanks: true
            , piggy_banks: true
            , cameras: true
            }[data.object_type]
    return (typeof value !== 'undefined' ? value : data.powered)
}

wink.indicator.on_text = function(data, property) {
    return on_text(data, property)
}

var on_text = function(data, property) {
    var text, value

    if (!data) return ''
    text =  { buttons         : ''
	    , piggy_banks: 'Balance: ' + dollars(data.balance) + ' / Goal: ' + dollars(data.savings_goal)
            , garage_doors    : 'OPEN'
            , hubs            : (!data.update_needed ? 'OK' : 'UPDATE NEEDED')
            , linked_services : 'OK'
            , light_bulbs     : (data.brightness == 1.0  ? ''     : pct(data.brightness))
            , locks           : 'LOCKED'
            , sensor_pods     : 'PROPERTY?'
            , shades          : (data.position == 1.0    ? 'OPEN' : pct(data.position))
            , smoke_detectors : 'OK'
            , thermostats     : (data.cool_active ? 'COOL' : data.heat_active ? 'HEAT' : data.aux_active ? 'AUX' : data.fan_active ? 'FAN' : data.mode)
            , air_conditioners: (data.powered ? 'COOL to ' + dual_temp(data.max_set_point) : 'IDLE')
            , propane_tanks   : 'OK'
            }[data.object_type] || 'ON'
    if (typeof property === 'undefined') return text
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''
    value = data[property]
    text =  { co_detected     : (value ? 'CO DETECTED'     : '')
            , fault           : (value ? 'FAULT DETECTED'  : '')
            , liquid_detected : (value ? 'LEAK DETECTED'   : '')
            , locked          : (value ? 'LOCKED'          : 'UNLOCKED')
            , loudness        : (value ? 'LOUD'            : '')
            , noise           : (value ? 'NOISY'           : '')
            , motion          : (value ? 'MOTION'          : '')
            , occupied        : (value ? 'OCCUPIED'        : 'VACANT')
            , opened          : (value ? 'OPEN'            : 'CLOSED')
            , smoke_detected  : (value ? 'SMOKE DETECTED'  : '')
            , tamper_detected : (value ? 'TAMPER DETECTED' : '')
            , vibration       : (value ? 'VIBRATION'       : '')
            , presence        : (value ? 'HOME'		   : 'NO')

            , battery         : pct(value)
            , brightness      : pct(value)
            , co_severity     : pct(value)
            , humidity        : pct(value)
            , smoke_severity  : pct(value)

            , remaining       : pct(value)
            , temperature     : dual_temp(value)
//            , mode            : (value ==  'cool_only' ? 'Cool' : value == 'heat_only' ? 'Heat' : 'Auto')
	    , mode	      : tstat_mode(data,value)

            }[property]
    if (text === '') text = 'OK'

    return text
}

var pct = function(value) {
    return ((value > 1.0 ? value : value * 100).toFixed(0) + '%')
}
var dual_temp = function (value) {
    return (typeof value === 'number' ? (value.toFixed(1) + 'C / ' + ((value * 1.8) + 32).toFixed(1) + 'F') : '')
}

var dollars = function (value) {
    return (typeof value === 'number' ? (value / 100).toFixed(2) : '')
}

var tstat_mode = function (data, value) {
    //    return value
    if (data.users_away) return 'Away mode'
    return (value == 'cool_only' ? 'cool to: ' + dual_temp(data.max_set_point) : value == 'heat_only' ? 'heat to: ' + dual_temp(data.min_set_point) : 'auto: ' + dual_temp(data.min_set_point) + ' - ' + dual_temp(data.max_set_point))
}

wink.indicator.off_text = function(data, property) {
    return off_text(data, property)
}

var off_text = function(data, property) {
    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return 'ERR'
    if (typeof property !== 'undefined') return ''

    return { buttons          : 'IDLE'
           , garage_doors     : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           , locks            : 'UNLOCKED'
           , shades           : (data.position !== null ? 'CLOSED' : 'UNKNOWN' )
           }[data.object_type] || 'OFF'
}

freeboard.addStyle('.wink-indicator-circle', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;margin-right:10px;");
freeboard.addStyle('.wink-indicator-triangle', "border-radius:50%;width:0;height:0;border-left:15px solid transparent;border-right:15px solid transparent;margin-top:5px;float:left;margin-right:10px;");

wink.indicator.style_element = function(data, property) {
    var params = style_element(data, property)

    if (params === '') return params

    if (params.shape == 'triangle') {
        return ('

')
    }
    return ('

')
}

DynCol =function (hex, lum) {

	// validate hex string
	hex = String(hex).replace(/[^0-9a-f]/gi, '');
	if (hex.length < 6) {
		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
	}
	lum = lum || 0;

	// convert to decimal and change luminosity
	var rgb = "#", c, i;
	for (i = 0; i < 3; i++) {
		c = parseInt(hex.substr(i*2,2), 16);
		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
		rgb += ("00"+c).substr(c.length);
	}
	return rgb;
}

var style_element = function(data, property) {
    var color, value
      , black  = '#000000'
      , blue   = '#00b8f1'
      , green  = '#00ff00'
      , red    = '#ff0000'
      , shape  = 'circle'
      , white  = '#ffffff'
      , yellow = '#eed202'

    if (!data) return ''
    if ((typeof data.connection !== 'undefined') && (!data.connection)) return { color: red, shape: 'triangle' }

    color = { binary_switches : (data.powered          ? white  : black)
	    , cameras	      : (data.capturing_video ? green : data.connection ? blue : black)
            , outlet          : (data.powered ? white : black)
            , buttons         : (!data.pressed         ? blue  : green)
            , garage_doors    : (data.position === 0.0 ? blue  : yellow)
            , hubs            : (!data.update_needed   ? blue  : yellow)
            , light_bulbs     : (data.powered          ? DynCol("#905030",data.brightness*5) : black)
            , locks           : (data.locked           ? blue  : yellow)
            , shades          : (data.position === 0.0 ? blue  : data.position === null ? yellow : green)
            , thermostats     : ({ cool_only: blue, heat_only: red }[data.mode] || green)
            , air_conditioners: (data.powered ? blue : green)
            , propane_tanks   : (data.remaining == 0 ? red : data.remaining == 1 ? blue : data.remaining > 0.66 ? green : data.remaining > 0.33 ? yellow : red)
            , piggy_banks     : '#'+data.nose_color
            }[data.object_type] || blue

    if (typeof property === 'undefined') return { color: color, shape: shape }
    if ((typeof data[property] === 'undefined') || (data[property] === null )) return ''

    value = data[property]
    color = { co_detected     : value && red
            , fault           : value && red
            , liquid_detected : value && red
            , locked          : (!value) && red
            , loudness        : value && yellow
            , noise           : value && yellow
            , motion          : value && yellow
            , occupied        : value && yellow
            , opened          : value && yellow
            , presence        : value && yellow
            , smoke_detected  : value && red
            , tamper_detected : value && red
            , vibration       : value && yellow

            , battery         : (value ==  1.0 ? blue : value > 0.66 ? green : value > 0.33 ? yellow : red)
            , brightness      : false
            , co_severity     : (value > 0) && red
            , humidity        : false
            , smoke_severity  : (value > 0) && red
	    , mode	      : (value=='cool_only' ? blue : red)
            , temperature     : false
            }[property] || blue
    if ((color != blue) && (color != green)) shape = 'triangle'

    return { color: color, shape: shape }
}

<|endoftext|>


<commit_before>/**
 * Module for chatbox dialog-related functions
 */
/* globals Java, ENGINE */
var varp = require('./var/player');
var varc = require('./var/client');

var widget = require('../widget');
var config = require('./config');
var util = require('./util');

module.exports = function (player) {
	var dialog = {
		chatplayer : chatplayer,
		chatnpc : chatnpc,
		mesbox : mesbox,
		objbox : objbox,
		then : then,
		finish : finish,
		requestCount : requestCount,
		requestName : requestName,
		requestString : requestString,
		requestItem : requestItem,
		confirm : requestConfirm
	};

	var flow = [];

	var Handler = Java.extend(Java.type('org.virtue.game.content.dialogues.InputEnteredHandler'), {
		handle : function (value) {
			var callback = flow.pop();
			while (callback) {
				//Run the callback. If it returns true, reset the resume handler.
				//If false, process the next callback.
				callback = callback(value) ? undefined : flow.pop();
			}
			if (flow) {
				//If there are remaining items, set the handler for the next ones
				ENGINE.setInputHandler(player, new Handler());
			}
		}
	});

	return dialog;

	function chatplayer (message) {
		flow.push(function () {
			//TODO: Remove this from the game engine & replace here
			player.getDialogs().sendPlayerChat(message);
			return true;
		});
		return dialog;
	}

	function chatnpc (npc, message) {
		npc = typeof(npc) !== "number" ? util.getId(npc) : npc;
		flow.push(function () {
			//TODO: Remove this from the game engine & replace here
			player.getDialogs().sendNpcChat(message, npc);
			return true;
		});
		return dialog;
	}

	function mesbox (message) {
		flow.push(function () {
			widget.setText(player, 1186, 2, message);
			widget.hide(player, 1186, 3, false);
			widget.openOverlaySub(player, 1006, 1186, false);
			util.runClientScript(player, 8178, []);
			return true;
		});
		return dialog;
	}

	function objbox (obj, message) {
		obj = typeof(obj) !== "number" ? util.getId(obj) : obj;
		flow.push(function () {
			widget.setText(player, 1184, 11, config.objName(obj));
			widget.setObject(player, 1184, 2, obj, 1);
			widget.setText(player, 1184, 9, message);
			widget.openOverlaySub(player, 1006, 1184);
			return true;
		});
		return dialog;
	}

	/**
	 * Add a custom callback to the dialog flow.
	 * If the proceeding dialog frame returned a value, it will be provided to the callback.
	 * If the callback returns true, the dialog will wait for a response from the player
	 * If it returns false, the dialog will continue to run the next function in the flow immediately
	 * @param callback The function to run.
	 * @return A continuing dialog segment
	 */
	function then (callback) {
		flow.push(callback);
		return dialog;
	}

	/**
	 * Finishes the dialog, closing all dialog windows (and any other open windows)
	 */
	function finish() {
		flow.push(closeDialog);
	}

	/**
	 * Asks the player to enter a number.
	 * The number they enter will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestCount (message) {
		flow.push(function () {
			openModalBase(player);
			util.runClientScript(player, 108, [message]);
			return true;
		});
		return dialog;
	}

	/**
	 * Asks the player to enter a name.
	 * The name they enter will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestName (message) {
		flow.push(function () {
			openModalBase(player);
			util.runClientScript(player, 109, [message]);
			return true;
		});
		return dialog;
	}

	/**
	 * Asks the player to enter a string
	 * The string they enter will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestString (message) {
		flow.push(function () {
			openModalBase(player);
			util.runClientScript(player, 110, [message]);
			return true;
		});
		return dialog;
	}

	/**
	 * Asks the player to select an item
	 * The item they choose will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestItem (message) {
		flow.push(function () {
			widget.open(player, 1477, 521, 1418, true);
			widget.open(player, 1418, 1, 389, true);
			util.runClientScript(player, 8178, []);
			util.runClientScript(player, 570, [message]);
			return true;
		});
		return dialog;
	}

	/**
	 * Sends a Yes/No question dialog to the player.
	 * If they select "Yes", continue to the next dialog segment.
	 * If they select "No", finish the dialog
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestConfirm (message) {
		flow.push(function () {
			ENGINE.requestMulti(player, message, ["Yes", "No"], [0, 1], new Handler());
		});
	}

	function openModalBase (player) {
		widget.open(player, 1477, 521, 1418, true);
		widget.open(player, 1418, 1, 1469, true);
	}

	function closeDialog () {
		widget.closeAll(player);
	}
};
<commit_msg>Add a cancel check on dialogs<commit_after>/**
 * Module for chatbox dialog-related functions
 */
/* globals Java, ENGINE */
var varp = require('./var/player');
var varc = require('./var/client');

var widget = require('../widget');
var config = require('./config');
var util = require('./util');

module.exports = function (player) {
	var dialog = {
		chatplayer : chatplayer,
		chatnpc : chatnpc,
		mesbox : mesbox,
		objbox : objbox,
		then : then,
		finish : finish,
		requestCount : requestCount,
		requestName : requestName,
		requestString : requestString,
		requestItem : requestItem,
		confirm : requestConfirm
	};

	var flow = [];

	var nextHandler;

	var Handler = Java.extend(Java.type('org.virtue.game.content.dialogues.InputEnteredHandler'), {
		handle : function (value) {
			if (nextHandler) {
				nextHandler(value);
				nextHandler = null;
			}
			var callback = flow.pop();
			while (callback) {
				//Run the callback. If it returns true, reset the resume handler.
				//If false, process the next callback.
				callback = callback(value) ? undefined : flow.pop();
			}
			if (flow.length > 0) {
				//If there are remaining items, set the handler for the next ones
				ENGINE.setInputHandler(player, new Handler());
			}
		}
	});

	return dialog;

	function chatplayer (message) {
		flow.push(function () {
			//TODO: Remove this from the game engine & replace here
			player.getDialogs().sendPlayerChat(message);
			return true;
		});
		return dialog;
	}

	function chatnpc (npc, message) {
		npc = typeof(npc) !== "number" ? util.getId(npc) : npc;
		flow.push(function () {
			//TODO: Remove this from the game engine & replace here
			player.getDialogs().sendNpcChat(message, npc);
			return true;
		});
		return dialog;
	}

	function mesbox (message) {
		flow.push(function () {
			widget.setText(player, 1186, 2, message);
			widget.hide(player, 1186, 3, false);
			widget.openOverlaySub(player, 1006, 1186, false);
			util.runClientScript(player, 8178, []);
			return true;
		});
		return dialog;
	}

	function objbox (obj, message) {
		obj = typeof(obj) !== "number" ? util.getId(obj) : obj;
		flow.push(function () {
			widget.setText(player, 1184, 11, config.objName(obj));
			widget.setObject(player, 1184, 2, obj, 1);
			widget.setText(player, 1184, 9, message);
			widget.openOverlaySub(player, 1006, 1184);
			return true;
		});
		return dialog;
	}

	/**
	 * Add a custom callback to the dialog flow.
	 * If the proceeding dialog frame returned a value, it will be provided to the callback.
	 * If the callback returns true, the dialog will wait for a response from the player
	 * If it returns false, the dialog will continue to run the next function in the flow immediately
	 * @param callback The function to run.
	 * @return A continuing dialog segment
	 */
	function then (callback) {
		flow.push(callback);
		return dialog;
	}

	/**
	 * Finishes the dialog, closing all dialog windows (and any other open windows)
	 */
	function finish() {
		flow.push(closeDialog);
	}

	/**
	 * Asks the player to enter a number.
	 * The number they enter will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestCount (message) {
		flow.push(function () {
			openModalBase(player);
			util.runClientScript(player, 108, [message]);
			nextHandler = checkForCancel;
			return true;
		});
		return dialog;
	}

	/**
	 * Asks the player to enter a name.
	 * The name they enter will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestName (message) {
		flow.push(function () {
			openModalBase(player);
			util.runClientScript(player, 109, [message]);
			nextHandler = checkForCancel;
			return true;
		});
		return dialog;
	}

	/**
	 * Asks the player to enter a string
	 * The string they enter will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestString (message) {
		flow.push(function () {
			openModalBase(player);
			util.runClientScript(player, 110, [message]);
			nextHandler = checkForCancel;
			return true;
		});
		return dialog;
	}

	/**
	 * Asks the player to select an item
	 * The item they choose will be provided to the next dialog segment
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestItem (message) {
		flow.push(function () {
			widget.open(player, 1477, 521, 1418, true);
			widget.open(player, 1418, 1, 389, true);
			util.runClientScript(player, 8178, []);
			util.runClientScript(player, 570, [message]);
			nextHandler = checkForCancel;
			return true;
		});
		return dialog;
	}

	/**
	 * Sends a Yes/No question dialog to the player.
	 * If they select "Yes", continue to the next dialog segment.
	 * If they select "No", finish the dialog
	 * @param message The message to ask
	 * @return A continuing dialog segment
	 */
	function requestConfirm (message) {
		flow.push(function () {
			ENGINE.requestMulti(player, message, ["Yes", "No"], [0, 1], new Handler());
			nextHandler = checkForCancel;
			return true;
		});
	}

	function openModalBase (player) {
		widget.open(player, 1477, 521, 1418, true);
		widget.open(player, 1418, 1, 1469, true);
	}

	function closeDialog () {
		widget.closeAll(player);
	}

	/**
	 * Checks if the provided value is falsy (0 or an empty string)
	 * If so, close the dialog
	 */
	function checkForCancel (value) {
		if (!value) {
			//Clear the remaining queue and close the dialog
			flow.length = 0;
			closeDialog();
		}
	}
};
<|endoftext|>


<commit_before>jQuery(document).ready(function($) {
    var editables = $('.editable');

    editables.on('dblclick', function(event) {
        event.preventDefault();

        var el = $(this),
            body = $('body'),
            html = el.html(),
            el_id = el.attr('id'),
            tag = 'textarea',
            plugin = document._front_edit.plugin,
            container,
            editor,
            target;

        if (plugin == 'ace') {
            tag = 'div';
        }

        // this will contain the actual editor block
        container = $('<'+tag+' class="front-edit-container" id="edit-'+el_id+'"></'+tag+'>
<button class="cancel">cancel</button><button class="save">save</button>
');
        if ( plugin === 'epiceditor') {
            container = $('

<button class="cancel">cancel</button><button class="save">save</button>
');
        }
        if(body.is('.front-editing')) {
            return;
        }
        body.addClass('front-editing');

        switch(document._front_edit.edit_mode) {
            case 'inline':
                el.html(container);
                target = el;
                break;

            case 'lightbox':
                $('

').appendTo($('body'));
                var lightbox = $('#front-edit-lightbox');
                lightbox.html(container);
                target = lightbox;
                break;
        }

        switch(plugin) {
            case 'ace':
                $.getScript('http://d1n0x3qji82z53.cloudfront.net/src-min-noconflict/ace.js', function(){
                    target.addClass('front-edit-ace');
                    editor = ace.edit("edit-" + el_id);
                    editor.setTheme("ace/theme/monokai");
                    editor.setValue(html);
                    editor.getSession().setMode("ace/mode/html");
                    editor.getSession().setUseWrapMode(true);
                });
                break;
            case 'wymeditor':
                target.find('.front-edit-container').html(html);
                $.getScript(document._front_edit.static_root+'wymeditor/jquery.wymeditor.min.js', function(){
                    target.addClass('front-edit-wym');
                    var base_path = document._front_edit.static_root+'wymeditor/';
                    $('#edit-' + el_id).wymeditor({
                        updateSelector: "input:submit",
                        updateEvent: "click",
                        logoHtml: '',
                        skin: 'django',
                        classesItems: [
                            {'name': 'image', 'title': 'DIV: Image w/ Caption', 'expr': 'div'},
                            {'name': 'caption', 'title': 'P: Caption', 'expr': 'p'},
                            {'name': 'align-left', 'title': 'Float: Left', 'expr': 'p, div, img'},
                            {'name': 'align-right', 'title': 'Float: Right', 'expr': 'p, div, img'}
                        ],
                        basePath: base_path,
                        wymPath: base_path + 'jquery.wymeditor.min.js',
                        skinPath: document._front_edit.static_root + 'wym/django/'

                    });
                });
                break;
            case 'redactor':
                target.find('.front-edit-container').html(html).redactor();
                break;
            case 'epiceditor':
                $.getScript(document._front_edit.static_root+'epiceditor/js/epiceditor.min.js', function(){
                    var opts = {
                      container: 'epiceditor',
                      textarea: null,
                      basePath: document._front_edit.static_root+'epiceditor',
                      clientSideStorage: true,
                      localStorageName: 'epiceditor',
                      useNativeFullscreen: true,
                      parser: marked,
                      file: {
                        name: 'epiceditor',
                        defaultContent: '',
                        autoSave: 100
                      },
                      button: {
                        preview: true,
                        fullscreen: true
                      },
                      focusOnLoad: false,
                      shortcut: {
                        modifier: 18,
                        fullscreen: 70,
                        preview: 80
                      },
                      string: {
                        togglePreview: 'Toggle Preview Mode',
                        toggleEdit: 'Toggle Edit Mode',
                        toggleFullscreen: 'Enter Fullscreen'
                      }
                    };
                    editor = new EpicEditor(opts).load();
                });
                break;
            default:
                target.find('.front-edit-container').html(html);
                break;
        }

        target.find('.cancel').on('click', function(event) {
            el.html(html);
            body.removeClass('front-editing');
            $('#front-edit-lightbox-container').remove();
        });

        target.find('.save').on('click', function(event) {
            var new_html, key = el_id;
            console.log('save');
            switch(plugin) {
                case 'ace':
                    new_html = editor.getValue();
                    break;
                case 'wymeditor':
                    new_html = $.wymeditors(0).xhtml();
                    break;
                case 'redactor':
                    try {
                        // redactor 0.8+
                        new_html = target.find('.front-edit-container').getCode();
                    } catch(err) {
                        // redactor 0.9+
                        new_html = target.find('.front-edit-container').redactor('get');
                    }
                    break;
                case 'epiceditor':
                    new_html = editor.exportFile('','html');
                    console.log(new_html);
                    break;

                default:
                    new_html = target.find('.front-edit-container').val();
                    break;
            }

            $.post(document._front_edit.save_url, {
                key: key,
                val: new_html,
                csrfmiddlewaretoken: document._front_edit.csrf_token
            }, function(data, textStatus, xhr) {
                // todo: return val
            });
            body.removeClass('front-editing');
            el.html(new_html);
            $('#front-edit-lightbox-container').remove();
        });
    });
});
<commit_msg>clear console.log<commit_after>jQuery(document).ready(function($) {
    var editables = $('.editable');

    editables.on('dblclick', function(event) {
        event.preventDefault();

        var el = $(this),
            body = $('body'),
            html = el.html(),
            el_id = el.attr('id'),
            tag = 'textarea',
            plugin = document._front_edit.plugin,
            container,
            editor,
            target;

        if (plugin == 'ace') {
            tag = 'div';
        }

        // this will contain the actual editor block
        container = $('<'+tag+' class="front-edit-container" id="edit-'+el_id+'"></'+tag+'>
<button class="cancel">cancel</button><button class="save">save</button>
');
        if ( plugin === 'epiceditor') {
            container = $('

<button class="cancel">cancel</button><button class="save">save</button>
');
        }
        if(body.is('.front-editing')) {
            return;
        }
        body.addClass('front-editing');

        switch(document._front_edit.edit_mode) {
            case 'inline':
                el.html(container);
                target = el;
                break;

            case 'lightbox':
                $('

').appendTo($('body'));
                var lightbox = $('#front-edit-lightbox');
                lightbox.html(container);
                target = lightbox;
                break;
        }

        switch(plugin) {
            case 'ace':
                $.getScript('http://d1n0x3qji82z53.cloudfront.net/src-min-noconflict/ace.js', function(){
                    target.addClass('front-edit-ace');
                    editor = ace.edit("edit-" + el_id);
                    editor.setTheme("ace/theme/monokai");
                    editor.setValue(html);
                    editor.getSession().setMode("ace/mode/html");
                    editor.getSession().setUseWrapMode(true);
                });
                break;
            case 'wymeditor':
                target.find('.front-edit-container').html(html);
                $.getScript(document._front_edit.static_root+'wymeditor/jquery.wymeditor.min.js', function(){
                    target.addClass('front-edit-wym');
                    var base_path = document._front_edit.static_root+'wymeditor/';
                    $('#edit-' + el_id).wymeditor({
                        updateSelector: "input:submit",
                        updateEvent: "click",
                        logoHtml: '',
                        skin: 'django',
                        classesItems: [
                            {'name': 'image', 'title': 'DIV: Image w/ Caption', 'expr': 'div'},
                            {'name': 'caption', 'title': 'P: Caption', 'expr': 'p'},
                            {'name': 'align-left', 'title': 'Float: Left', 'expr': 'p, div, img'},
                            {'name': 'align-right', 'title': 'Float: Right', 'expr': 'p, div, img'}
                        ],
                        basePath: base_path,
                        wymPath: base_path + 'jquery.wymeditor.min.js',
                        skinPath: document._front_edit.static_root + 'wym/django/'

                    });
                });
                break;
            case 'redactor':
                target.find('.front-edit-container').html(html).redactor();
                break;
            case 'epiceditor':
                $.getScript(document._front_edit.static_root+'epiceditor/js/epiceditor.min.js', function(){
                    var opts = {
                      container: 'epiceditor',
                      textarea: null,
                      basePath: document._front_edit.static_root+'epiceditor',
                      clientSideStorage: true,
                      localStorageName: 'epiceditor',
                      useNativeFullscreen: true,
                      parser: marked,
                      file: {
                        name: 'epiceditor',
                        defaultContent: '',
                        autoSave: 100
                      },
                      button: {
                        preview: true,
                        fullscreen: true
                      },
                      focusOnLoad: false,
                      shortcut: {
                        modifier: 18,
                        fullscreen: 70,
                        preview: 80
                      },
                      string: {
                        togglePreview: 'Toggle Preview Mode',
                        toggleEdit: 'Toggle Edit Mode',
                        toggleFullscreen: 'Enter Fullscreen'
                      }
                    };
                    editor = new EpicEditor(opts).load();
                });
                break;
            default:
                target.find('.front-edit-container').html(html);
                break;
        }

        target.find('.cancel').on('click', function(event) {
            el.html(html);
            body.removeClass('front-editing');
            $('#front-edit-lightbox-container').remove();
        });

        target.find('.save').on('click', function(event) {
            var new_html, key = el_id;
            switch(plugin) {
                case 'ace':
                    new_html = editor.getValue();
                    break;
                case 'wymeditor':
                    new_html = $.wymeditors(0).xhtml();
                    break;
                case 'redactor':
                    try {
                        // redactor 0.8+
                        new_html = target.find('.front-edit-container').getCode();
                    } catch(err) {
                        // redactor 0.9+
                        new_html = target.find('.front-edit-container').redactor('get');
                    }
                    break;
                case 'epiceditor':
                    new_html = editor.exportFile('','html');
                    break;

                default:
                    new_html = target.find('.front-edit-container').val();
                    break;
            }

            $.post(document._front_edit.save_url, {
                key: key,
                val: new_html,
                csrfmiddlewaretoken: document._front_edit.csrf_token
            }, function(data, textStatus, xhr) {
                // todo: return val
            });
            body.removeClass('front-editing');
            el.html(new_html);
            $('#front-edit-lightbox-container').remove();
        });
    });
});
<|endoftext|>


<commit_before>/*
 * Extend classes/objects with other classes/objects.
 */

import * as CompositionRules from './CompositionRules';

export default class Composable {

  /*
   * Return a subclass of the current class that includes the members indicated
   * in the argument. The argument can be a plain JavaScript object, or a class
   * whose prototype contains the members that will be copied.
   *
   * This can be used for a couple of purposes:
   * 1. Extend a class with mixins/behaviors.
   * 2. Create a component class in ES5.
   *
   * The call
   *
   *   MyBaseClass.compose(Mixin1, Mixin2, Mixin3)
   *
   * will return a new class of MyBaseClass that implements all the methods in
   * the three mixins given. The above is equivalent to
   *
   *   MyBaseClass.compose(Mixin1).compose(Mixin2).compose(Mixin3)
   *
   * This method can be statically invoked to extend plain objects:
   *
   *   let extended = Composable.extend.call(obj1, obj2);
   *
   */
  static compose(...mixins) {
    // We create a new subclass for each mixin in turn. The result becomes
    // the base class extended by any subsequent mixins. It turns out that
    // we can use Array.reduce() to concisely express this, using the current
    // (original) class as the seed for reduce().
    return mixins.reduce(compose, this);
  }

  static decorate(decorators) {
    for (let key in decorators) {
      let decorator = decorators[key];
      let descriptor = Object.getOwnPropertyDescriptor(this, key);
      decorator(this, key, descriptor);
      Object.defineProperty(this, key, descriptor);
    }
  }

  decorate(decorators) {
    Composable.decorate.call(this, decorators);
  }

  // Decorate for annotating how a class member should be composed later.
  // This takes a decorator that will be run at *composition* time.
  // For now, this can only be applied to methods.
  static rule(decorator) {
    // We return a decorator that just adds the decorator given above to the
    // member.
    return function(target, key, descriptor) {
      // TODO: Use a Symbol instead of a string property name to save this.
      descriptor.value._compositionRule = decorator;
    }
  }

}

/*
 * Expose standard composition rules as properties of Composable.
 * This avoids the need for someone to make a separate import of the rules.
 */
Composable.rules = CompositionRules;

/*
 * All Composable-created objects keep references to the mixins that were
 * applied to create them. When a *named* mixin is applied to the prototype
 * chain, the resulting object (or, for a class, the class' prototype) will
 * have a new member with that name that points back to the same object.
 * That facility is useful when dealing with chains that have been extended
 * more than once, as an mixin's name is sufficient to retrieve a reference
 * to that point in the prototype chain.
 *
 * A single mixin can be applied to multiple prototype chains -- the name
 * refers to the prototype on *this particular prototype chain* that was added
 * for that mixin. This lets mixin/mixin code get back to its own
 * prototype, most often in combination with "super" (see below) in order to
 * invoke superclass behavior.
 */
Composable.prototype.Composable = Composable.prototype;

/*
 * All Composable-created objects have a "super" property that references the
 * prototype above them in the prototype chain.
 *
 * This "super" reference is used as a replacement for ES6's "super" keyword in
 * in ES5 (or transpiled ES6) mixins that want to invoke superclass behavior,
 * where the specific superclass will depend upon which mixins have been applied
 * to a given prototype chain.
 *
 * E.g.:
 *   class Mixin {
 *     foo() {
 *       if (this.Mixin.super.foo) {
 *         this.Mixin.super.foo.call(this); // Invoke superclass' foo()
 *       }
 *       // Do Mixin-specific work here...
 *     }
 *   }
 *
 * For consistency, Composable itself records its own superclass as Object.
 */
Composable.prototype.super = Object.prototype;

// Composition rules for standard object members.
Composable.prototype.compositionRules = {
  constructor: Composable.override,
  toString: Composable.override,
};

function applyCompositionRules(obj) {
  let base = Object.getPrototypeOf(obj);
  Object.getOwnPropertyNames(obj).forEach(name => {
    if (name in base) {
      // Base also implements a member with the same name; need to combine.
      let descriptor = Object.getOwnPropertyDescriptor(obj, name);
      let rule = descriptor.value && descriptor.value._compositionRule;
      if (!rule) {
        // See if prototype chain has a rule for this member.
        rule = obj.compositionRules[name];
      }
      if (!rule) {
        rule = getDefaultCompositionRule(descriptor);
      }
      // "override" is a known no-op, so we don't bother trying to redefine the
      // property.
      if (rule && rule !== Composable.override) {
        rule(obj, name, descriptor);
        Object.defineProperty(obj, name, descriptor);
      }
    }
  });
}

/*
 * Copy the given properties/methods to the target.
 */
function copyOwnProperties(source, target, ignorePropertyNames = []) {
  Object.getOwnPropertyNames(source).forEach(name => {
    if (ignorePropertyNames.indexOf(name) < 0) {
      let descriptor = Object.getOwnPropertyDescriptor(source, name);
      Object.defineProperty(target, name, descriptor);
    }
  });
  return target;
}

/*
 * Return a new subclass/object that extends the given base class/object with
 * the members of the indicated mixin.
 */
function compose(base, mixin) {

  // Check whether the base and mixin are classes or plain objects.
  let baseIsClass = isClass(base);
  let mixinIsClass = isClass(mixin);

  // Check to see if the *mixin* has a base class/prototype of its own.
  let mixinBase = mixinIsClass ?
    Object.getPrototypeOf(mixin.prototype).constructor :
    Object.getPrototypeOf(mixin);
  if (mixinBase &&
      mixinBase !== Function &&
      mixinBase !== Object &&
      mixinBase !== Object.prototype) {
    // The mixin itself derives from another class/object.
    // Recurse, and extend with the mixin's base first.
    base = compose(base, mixinBase);
  }

  // Create the extended object we're going to return as a result.
  let result;
  if (baseIsClass) {
    // Create a subclass of base. Once WebKit supports HTMLElement as a real
    // class, we can just say:
    //
    //   class subclass extends base {}
    //
    // However, until that's resolved, we have to construct the class ourselves.
    result = function subclass() {};
    Object.setPrototypeOf(result, base);
    Object.setPrototypeOf(result.prototype, base.prototype);
  } else {
    // Create a plain object that simply uses the base as a prototype.
    result = Object.create(base);
  }

  let source;
  let target;
  if (baseIsClass && mixinIsClass) {
    // Properties defined by Function.
    // We'd prefer to get by interrogating Function itself, but WebKit functions
    // have some properties (arguments and caller) which are not returned by
    // Object.getOwnPropertyNames(Function).
    const FUNCTION_PROPERTIES = [
      'arguments',
      'caller',
      'length',
      'name',
      'prototype'
    ];
    // Extending a class with a class.
    // We'll copy instance members in a moment, but first copy static members.
    copyOwnProperties(mixin, result, FUNCTION_PROPERTIES);
    source = mixin.prototype;
    target = result.prototype;
  } else if (!baseIsClass && mixinIsClass) {
    // Extending a plain object with a class.
    // Copy prototype methods directly to result.
    source = mixin.prototype;
    target = result;
  } else if (baseIsClass && !mixinIsClass) {
    // Extending class with plain object.
    // Copy mixin to result prototype.
    source = mixin;
    target = result.prototype;
  } else {
    // Extending a plain object with a plain object.
    source = mixin;
    target = result;
  }
  copyOwnProperties(source, target, ['constructor']);

  applyCompositionRules(target);

  if (mixin.name) {
    // Use the mixin's name (usually the name of a class' constructor) to
    // save a reference back to the newly-created object in the prototype chain.
    target[mixin.name] = target;

    // Save a reference to the superclass/super-object. See the comments on
    // Composable's "super" property.
    target.super = baseIsClass ? base.prototype : base;
  }

  return result;
}

function getDefaultCompositionRule(descriptor) {
  if (typeof descriptor.value === 'function') {
    return Composable.rules.propagateFunction;
  } else if (typeof descriptor.get === 'function' || typeof descriptor.set === 'function') {
    // Property with getter and/or setter.
    return Composable.rules.propagateProperty;
  }
  return null;
}

// Return true if c is a JavaScript class.
// We use this test because, on WebKit, classes like HTMLElement are special,
// and are not instances of Function. To handle that case, we use a looser
// definition: an object is a class if it has a prototype, and that prototype
// has a constructor that is the original object. This condition holds true even
// for HTMLElement on WebKit.
function isClass(c) {
  return typeof c === 'function' ||                   // Standard
      (c.prototype && c.prototype.constructor === c); // HTMLElement in WebKit
}
<commit_msg>Some clean up and commenting.<commit_after>/*
 * Extend classes/objects with other classes/objects.
 */

import * as CompositionRules from './CompositionRules';

export default class Composable {

  /*
   * Return a subclass of the current class that includes the members indicated
   * in the argument. The argument can be a plain JavaScript object, or a class
   * whose prototype contains the members that will be copied.
   *
   * This can be used for a couple of purposes:
   * 1. Extend a class with mixins/behaviors.
   * 2. Create a component class in ES5.
   *
   * The call
   *
   *   MyBaseClass.compose(Mixin1, Mixin2, Mixin3)
   *
   * will return a new class of MyBaseClass that implements all the methods in
   * the three mixins given. The above is equivalent to
   *
   *   MyBaseClass.compose(Mixin1).compose(Mixin2).compose(Mixin3)
   *
   * This method can be statically invoked to extend plain objects or classes
   * that don't inherit from this class:
   *
   *   let extended = Composable.extend.call(obj1, obj2);
   *
   */
  static compose(...mixins) {
    // We create a new subclass for each mixin in turn. The result becomes
    // the base class extended by any subsequent mixins. It turns out that
    // we can use Array.reduce() to concisely express this, using the current
    // (original) class as the seed for reduce().
    return mixins.reduce(compose, this);
  }

  static decorate(decorators) {
    for (let key in decorators) {
      let decorator = decorators[key];
      let descriptor = Object.getOwnPropertyDescriptor(this, key);
      decorator(this, key, descriptor);
      Object.defineProperty(this, key, descriptor);
    }
  }

  decorate(decorators) {
    Composable.decorate.call(this, decorators);
  }

  // Decorate for annotating how a class member should be composed later.
  // This takes a decorator that will be run at *composition* time.
  // For now, this can only be applied to methods.
  static rule(decorator) {
    // We return a decorator that just adds the decorator given above to the
    // member.
    return function(target, key, descriptor) {
      // TODO: Use a Symbol instead of a string property name to save this.
      descriptor.value._compositionRule = decorator;
    }
  }

}

/*
 * Expose standard composition rules as properties of Composable.
 * This avoids the need for someone to make a separate import of the rules.
 */
Composable.rules = CompositionRules;

/*
 * All Composable-created objects keep references to the mixins that were
 * applied to create them. When a *named* mixin is applied to the prototype
 * chain, the resulting object (or, for a class, the class' prototype) will
 * have a new member with that name that points back to the same object.
 * That facility is useful when dealing with chains that have been extended
 * more than once, as an mixin's name is sufficient to retrieve a reference
 * to that point in the prototype chain.
 *
 * A single mixin can be applied to multiple prototype chains -- the name
 * refers to the prototype on *this particular prototype chain* that was added
 * for that mixin. This lets mixin/mixin code get back to its own
 * prototype, most often in combination with "super" (see below) in order to
 * invoke superclass behavior.
 */
Composable.prototype.Composable = Composable.prototype;

/*
 * All Composable-created objects have a "super" property that references the
 * prototype above them in the prototype chain.
 *
 * This "super" reference is used as a replacement for ES6's "super" keyword in
 * in ES5 (or transpiled ES6) mixins that want to invoke superclass behavior,
 * where the specific superclass will depend upon which mixins have been applied
 * to a given prototype chain.
 *
 * E.g.:
 *   class Mixin {
 *     foo() {
 *       if (this.Mixin.super.foo) {
 *         this.Mixin.super.foo.call(this); // Invoke superclass' foo()
 *       }
 *       // Do Mixin-specific work here...
 *     }
 *   }
 *
 * For consistency, Composable itself records its own superclass as Object.
 */
Composable.prototype.super = Object.prototype;

// Composition rules for standard object members.
Composable.prototype.compositionRules = {
  constructor: Composable.override,
  toString: Composable.override,
};

// Properties defined by Function that we don't want to mixin.
// We'd prefer to get these by interrogating Function itself, but WebKit
// functions have some properties (arguments and caller) which are not returned
// by Object.getOwnPropertyNames(Function).
const NON_MIXABLE_FUNCTION_PROPERTIES = [
  'arguments',
  'caller',
  'length',
  'name',
  'prototype'
];

// Properties defined by Object that we don't want to mixin.
const NON_MIXABLE_OBJECT_PROPERTIES = [
  'constructor'
];

/*
 * Apply the composition rules in effect for the given object, which lies at
 * the tip of a prototype chain. This looks for conflicts between the object's
 * own properties (and methods), and identically-named properties (methods)
 * further up the prototype chain. Conflicts are resolved with rules defined by
 * the affect members.
 */
function applyCompositionRules(obj) {
  let base = Object.getPrototypeOf(obj);
  // For each property name, see if the base has a property with the same name.
  Object.getOwnPropertyNames(obj).forEach(name => {
    if (name in base) {
      // Base does implement a member with the same name; need to combine.
      let descriptor = Object.getOwnPropertyDescriptor(obj, name);
      let rule = descriptor.value && descriptor.value._compositionRule;
      if (!rule) {
        // See if prototype chain has a rule for this member.
        rule = obj.compositionRules[name];
      }
      if (!rule) {
        // See if we have a default rule for this kind of member.
        rule = getDefaultCompositionRule(descriptor);
      }
      // "override" is a known no-op, so we don't bother trying to redefine the
      // property.
      if (rule && rule !== Composable.override) {
        rule(obj, name, descriptor);
        Object.defineProperty(obj, name, descriptor);
      }
    }
  });
}

/*
 * Copy the given properties/methods to the target.
 * Return the updated target.
 */
function copyOwnProperties(source, target, ignorePropertyNames = []) {
  Object.getOwnPropertyNames(source).forEach(name => {
    if (ignorePropertyNames.indexOf(name) < 0) {
      let descriptor = Object.getOwnPropertyDescriptor(source, name);
      Object.defineProperty(target, name, descriptor);
    }
  });
  return target;
}

/*
 * Return a new subclass/object that extends the given base class/object with
 * the members of the indicated mixin.
 */
function compose(base, mixin) {

  // See if the *mixin* has a base class/prototype of its own.
  let mixinIsClass = isClass(mixin);
  let mixinBase = mixinIsClass ?
    Object.getPrototypeOf(mixin.prototype).constructor :
    Object.getPrototypeOf(mixin);
  if (mixinBase &&
      mixinBase !== Function &&
      mixinBase !== Object &&
      mixinBase !== Object.prototype) {
    // The mixin itself derives from another class/object.
    // Recurse, and extend with the mixin's base first.
    base = compose(base, mixinBase);
  }

  // Create the extended object we're going to return as a result.
  let baseIsClass = isClass(base);
  let result = baseIsClass ?
    createSubclass(base) :
    Object.create(base);

  // The "target" here is the target of our property/method composition rules.
  let target;
  if (baseIsClass && mixinIsClass) {
    // Extending class with class: copy static members, then prototype members.
    copyOwnProperties(mixin, result, NON_MIXABLE_FUNCTION_PROPERTIES);
    target = copyOwnProperties(mixin.prototype, result.prototype, NON_MIXABLE_OBJECT_PROPERTIES);
  } else if (!baseIsClass && mixinIsClass) {
    // Extending plain object with class: copy prototype methods to result.
    target = copyOwnProperties(mixin.prototype, result, NON_MIXABLE_FUNCTION_PROPERTIES);
  } else if (baseIsClass && !mixinIsClass) {
    // Extending class with plain object: copy mixin to result prototype.
    target = copyOwnProperties(mixin, result.prototype, NON_MIXABLE_OBJECT_PROPERTIES);
  } else {
    // Extending plain object with plain object: copy former to latter.
    target = copyOwnProperties(mixin, result, NON_MIXABLE_OBJECT_PROPERTIES);
  }

  // Apply the composition rules in effect at the target.
  applyCompositionRules(target);

  if (mixin.name) {
    // Use the mixin's name (usually the name of a class' constructor) to
    // save a reference back to the newly-created object in the prototype chain.
    target[mixin.name] = target;

    // Save a reference to the superclass/super-object. See the comments on
    // Composable's "super" property.
    target.super = baseIsClass ? base.prototype : base;
  }

  return result;
}

/*
 * Return a new subclass of the given base class.
 */
function createSubclass(base) {
  // Once WebKit supports HTMLElement as a real class, we can just say:
  //
  //   class subclass extends base {}
  //
  // However, until that's resolved, we just construct the class ourselves.
  function subclass() {};
  Object.setPrototypeOf(subclass, base);
  Object.setPrototypeOf(subclass.prototype, base.prototype);
  return subclass;
}

/*
 * If the given property descriptor is of a type that has a default composition
 * rule, return that rule.
 */
function getDefaultCompositionRule(descriptor) {
  if (typeof descriptor.value === 'function') {
    // Method
    return Composable.rules.propagateFunction;
  } else if (typeof descriptor.get === 'function'
      || typeof descriptor.set === 'function') {
    // Property with getter and/or setter
    return Composable.rules.propagateProperty;
  }
  return null;
}

/*
 * Return true if c is a JavaScript class.
 *
 * We use this test because, on WebKit, classes like HTMLElement are special,
 * and are not instances of Function. To handle that case, we use a looser
 * definition: an object is a class if it has a prototype, and that prototype
 * has a constructor that is the original object. This condition holds true even
 * for HTMLElement on WebKit.
 */
function isClass(c) {
  return typeof c === 'function' ||                   // Standard
      (c.prototype && c.prototype.constructor === c); // HTMLElement in WebKit
}
<|endoftext|>