<commit_before>// YAML.node, © 2010 Stéphan Kochen
// MIT-licensed. (See the included LICENSE file.)

var sys = require('sys');
var binding = exports.capi = require('./binding');

// Most of these were derived from: http://yaml.org/type/
// Also borrows from tenderlove's `Psych::ScalarScanner`. (MIT-licensed)
var definitelyNonNumericRe = /^[a-z~]/i,
    definitelyNonBooleanRe = /^[^ytonf~]/i,
    canParseIntRe   = /^[-+]?(0x[0-9a-fA-F_]+|[\d_]+)$/,
    canParseFloatRe = /^[-+]?(\d[\d_]*)?\.[\d_]*(e[-+]\d+)?$/i,
    nullRe   = /^(~|null)$/i,
    trueRe   = /^(y|yes|true|on)$/i,
    falseRe  = /^(n|no|false|off)$/i,
    posInfRe = /^\+?\.inf$/i,
    negInfRe = /^-\.inf$/i,
    nanRe    = /^\.nan$/i,
    binRe    = /^[-+]?0b[01_]+$/,
    dateRe      = /^\d{4}-\d\d?-\d\d?$/,
    timestampRe = /^(\d{4}-\d\d?-\d\d?(?:[Tt]|\s+)\d\d?:\d\d:\d\d(?:\.\d*)?)(?:\s*(Z|[-+]\d\d?(?::\d\d)?))?$/,
    timeIntRe   = /^[-+]?[1-9][\d_]*(:[0-5]?\d)+$/,
    timeFloatRe = /^[-+]?[0-9][\d_]*(:[0-5]?\d)+\.[\d_]*$/;
    underscoresRe = /_/g;

// Helper function that converts a string scalar to its actual type.
var parseScalar = function(v) {
  if (!v) return null;

  if (definitelyNonNumericRe.test(v)) {
    if (v.length > 5 || definitelyNonBooleanRe.test(v)) return v;
    if (nullRe.test(v)) return null;
    if (trueRe.test(v)) return true;
    if (falseRe.test(v)) return false;
    return v;
  }

  if (posInfRe.test(v)) return  1/0;
  if (negInfRe.test(v)) return -1/0;
  if (nanRe.test(v))    return  0/0;

  // JavaScript's datetime parsing is subtly different from YAML's.
  var m = timestampRe.exec(v);
  if (m) {
    var offset = 0, dateTimePart = m[1].replace('t', 'T');
    if (m[2] && m[2] !== 'Z') {
      var parts = m[2].split(':');
      offset = parseInt(parts[0]) * 100;
      if (parts.length == 2)
        offset += parseInt(parts[1]);
    }
    if (offset >= 0)
      offset = "+" + offset;
    return new Date(dateTimePart + offset);
  }
  if (dateRe.test(v))
    return new Date(v + "T00:00:00Z");

  if (canParseIntRe.test(v))   return parseInt(  v.replace(underscoresRe, ''));
  if (canParseFloatRe.test(v)) return parseFloat(v.replace(underscoresRe, ''));
  // FIXME: Parse times and binary numbers.
  return v;
};

// `parse` is the binding to LibYAML's parser. It's signature is:
//
//     function(input, handler) { [native] }
//
// The handler object exposes methods for each LibYAML parser event. These are named `onScalar`,
// `onSequenceStart`, etc. All of these methods take an event object that is similar in structure
// to a flattened `yaml_event_t`.
exports.parse = binding.parse;

// The `load` function loads all of a YAML-file's contents into a JavaScript structure. The return
// value is an array of documents found in the input.
exports.load = function(input) {
  var documents = [],
      document;

  // A stack of value handlers, as they occur.
  // Document, sequence and mapping events add functions to this stack.
  var stack = [],
      newValue = function(v) {
        stack[0](v);
      },
      pushHandler = function(h) {
        stack.unshift(h);
      },
      popHandler = function()  {
        stack.shift();
      };

  // Call into the parser and build the documents.
  // FIXME: Handle tags and anchors. Parse scalars.
  binding.parse(input, {
    onDocumentStart: function(e) {
      pushHandler(function(v) {
        document = v;
      });
    },
    onDocumentEnd: function(e) {
      popHandler();
      documents.push(document);
    },

    onScalar: function(e) {
      newValue(parseScalar(e.value));
    },

    onSequenceStart: function(e) {
      var sequence = [];
      newValue(sequence);
      pushHandler(function(v) {
        sequence.push(v);
      });
    },
    onSequenceEnd: function(e) {
      popHandler();
    },

    onMappingStart: function(e) {
      var mapping = {},
          key,
          keyHandler = function(v) {
            key = v;
            pushHandler(valueHandler);
          },
          valueHandler = function(v) {
            mapping[key] = v;
            popHandler();
          };
      newValue(mapping);
      pushHandler(keyHandler);
    },
    onMappingEnd: function(e) {
      popHandler();
    }
  });

  return documents;
};
<commit_msg>Implement time parsing.<commit_after>// YAML.node, © 2010 Stéphan Kochen
// MIT-licensed. (See the included LICENSE file.)

var sys = require('sys');
var binding = exports.capi = require('./binding');

// Most of these were derived from: http://yaml.org/type/
// Also borrows from tenderlove's `Psych::ScalarScanner`. (MIT-licensed)
var definitelyNonNumericRe = /^[a-z~]/i,
    definitelyNonBooleanRe = /^[^ytonf~]/i,
    canParseIntRe   = /^[-+]?(0x[0-9a-fA-F_]+|[\d_]+)$/,
    canParseFloatRe = /^[-+]?(\d[\d_]*)?\.[\d_]*(e[-+]\d+)?$/i,
    nullRe   = /^(~|null)$/i,
    trueRe   = /^(y|yes|true|on)$/i,
    falseRe  = /^(n|no|false|off)$/i,
    posInfRe = /^\+?\.inf$/i,
    negInfRe = /^-\.inf$/i,
    nanRe    = /^\.nan$/i,
    binRe    = /^[-+]?0b[01_]+$/,
    timeRe   = /^([-+]?)([0-9][\d_]*(?::[0-5]?\d)+(?:\.[\d_]*)?)$/,
    dateRe   = /^\d{4}-\d\d?-\d\d?$/,
    timestampRe = /^(\d{4}-\d\d?-\d\d?(?:[Tt]|\s+)\d\d?:\d\d:\d\d(?:\.\d*)?)(?:\s*(Z|[-+]\d\d?(?::\d\d)?))?$/,
    underscoresRe = /_/g;

// Helper function that converts a string scalar to its actual type.
var parseScalar = function(v) {
  if (!v) return null;

  if (definitelyNonNumericRe.test(v)) {
    if (v.length > 5 || definitelyNonBooleanRe.test(v)) return v;
    if (nullRe.test(v)) return null;
    if (trueRe.test(v)) return true;
    if (falseRe.test(v)) return false;
    return v;
  }

  if (posInfRe.test(v)) return  1/0;
  if (negInfRe.test(v)) return -1/0;
  if (nanRe.test(v))    return  0/0;

  // JavaScript's datetime parsing is subtly different from YAML's.
  var m = timestampRe.exec(v);
  if (m) {
    var offset = 0, dateTimePart = m[1].replace('t', 'T');
    if (m[2] && m[2] !== 'Z') {
      var parts = m[2].split(':');
      offset = parseInt(parts[0]) * 100;
      if (parts.length == 2)
        offset += parseInt(parts[1]);
    }
    if (offset >= 0)
      offset = "+" + offset;
    return new Date(dateTimePart + offset);
  }
  if (dateRe.test(v))
    return new Date(v + "T00:00:00Z");

  if (canParseIntRe.test(v))   return parseInt(  v.replace(underscoresRe, ''));
  if (canParseFloatRe.test(v)) return parseFloat(v.replace(underscoresRe, ''));

  m = timeRe.exec(v);
  if (m) {
    var parts = m[2].split(':'), length = parts.length, result = 0, i;
    for (i = 0; i < parts.length; i++) {
      result *= 60;
      if (i + 1 == parts.length)
        result += parseFloat(parts[i]);
      else
        result += parseInt(parts[i]);
    }
    if (m[1] == '-')
      result *= -1;
    return result;
  }

  // FIXME: Parse binary numbers.
  return v;
};

// `parse` is the binding to LibYAML's parser. It's signature is:
//
//     function(input, handler) { [native] }
//
// The handler object exposes methods for each LibYAML parser event. These are named `onScalar`,
// `onSequenceStart`, etc. All of these methods take an event object that is similar in structure
// to a flattened `yaml_event_t`.
exports.parse = binding.parse;

// The `load` function loads all of a YAML-file's contents into a JavaScript structure. The return
// value is an array of documents found in the input.
exports.load = function(input) {
  var documents = [],
      document;

  // A stack of value handlers, as they occur.
  // Document, sequence and mapping events add functions to this stack.
  var stack = [],
      newValue = function(v) {
        stack[0](v);
      },
      pushHandler = function(h) {
        stack.unshift(h);
      },
      popHandler = function()  {
        stack.shift();
      };

  // Call into the parser and build the documents.
  // FIXME: Handle tags and anchors. Parse scalars.
  binding.parse(input, {
    onDocumentStart: function(e) {
      pushHandler(function(v) {
        document = v;
      });
    },
    onDocumentEnd: function(e) {
      popHandler();
      documents.push(document);
    },

    onScalar: function(e) {
      newValue(parseScalar(e.value));
    },

    onSequenceStart: function(e) {
      var sequence = [];
      newValue(sequence);
      pushHandler(function(v) {
        sequence.push(v);
      });
    },
    onSequenceEnd: function(e) {
      popHandler();
    },

    onMappingStart: function(e) {
      var mapping = {},
          key,
          keyHandler = function(v) {
            key = v;
            pushHandler(valueHandler);
          },
          valueHandler = function(v) {
            mapping[key] = v;
            popHandler();
          };
      newValue(mapping);
      pushHandler(keyHandler);
    },
    onMappingEnd: function(e) {
      popHandler();
    }
  });

  return documents;
};
<|endoftext|>


<commit_before>/** @jsx React.DOM */
'use strict';
var class_ = require('class');
var React = require('react');
var url = require('url');
var globals = require('./globals');

    var Collection = module.exports.Collection = React.createClass({
        render: function () {
            var context = this.props.context;
            return (

{context.title}

{context.description}

                    {this.transferPropsTo(
)}

            );
        }
    });

    globals.content_views.register(Collection, 'collection');

    var Cell = class_({
        constructor: function(value, sortable) {
            this.value = value;
            this.sortable = sortable;
        }
    });

    var Row = class_({
        constructor: function(item, cells, text) {
            this.item = item;
            this.cells = cells;
            this.text = text;
        }
    });

    var Data = class_({
        constructor: function(rows) {
            this.rows = rows;
            this.sortedOn = null;
            this.reversed = false;
        },
        sort: function(sortColumn, reverse) {
            reverse = !!reverse;
            if (this.sortedOn === sortColumn && this.reversed === reverse) return;
            this.sortedOn = sortColumn;
            this.reversed = reverse;
            this.rows.sort(function (rowA, rowB) {
                var a = '' + rowA.cells[sortColumn].sortable;
                var b = '' + rowB.cells[sortColumn].sortable;
                if (a  b) {
                    return reverse ? -1 : 1;
                }
                return 0;
            });
        }
    });

    var RowView = function (props) {
        var row = props.row;
        var id = row.item['@id'];
        var tds = row.cells.map( function (cell, index) {
            if (index === 0) {
                return (
                    {cell.value}
                );
            }
            return (
                {cell.value}
            );
        });
        return (

{tds}

        );
    };

    var Table = module.exports.Table = React.createClass({
        getDefaultProps: function () {
            return {
                defaultSortOn: 0
            };
        },

        getInitialState: function () {
            var state = this.extractParams(this.props);
            state.columns = this.guessColumns(this.props);
            state.data = Data([]);  // Tables may be long so render empty first
            state.communicating = true;
            return state;
        },

        componentWillReceiveProps: function (nextProps) {
            var updateData = false;
            if (nextProps.context !== this.props.context) {
                updateData = true;
                this.setState({
                    communicating: this.fetchAll(nextProps)
                });
            }
            if (nextProps.columns !== this.props.columns) {
                updateData = true;
            }
            if (updateData) {
                var columns = this.guessColumns(nextProps);
                this.extractData(nextProps, columns);
            }
            if (nextProps.href !== this.props.href) {
                this.extractParams(nextProps);
            }

        },

        extractParams: function(props) {
            var params = url.parse(props.href, true).query;
            var sorton = parseInt(params.sorton, 10);
            if (isNaN(sorton)) {
                sorton = props.defaultSortOn;
            }
            var state = {
                sortOn: sorton,
                reversed: params.reversed || false,
                searchTerm: params.q || ''
            };
            this.setState(state);
            return state;
        },

        guessColumns: function (props) {
            var column_list = props.context.columns;
            var columns = []
            if (Object.keys(column_list).length === 0) {
                for (var key in props.context['@graph'][0]) {
                    if (key.slice(0, 1) != '@' && key.search(/(uuid|_no|accession)/) == -1) {
                        columns.push(key);
                    }
                }
                columns.sort();
                columns.unshift('@id');
            } else {
                for(var column in column_list) {
                    columns.push(column)
                }
            }
            this.setState({columns: columns});
            return columns;
        },

        extractData: function (props, columns) {
            var context = props.context;
            columns = columns || this.state.columns;
            var rows = context['@graph'].map(function (item) {
                var cells = columns.map(function (column) {
                    var factory;
                    // cell factories
                    //if (factory) {
                    //    return factory({context: item, column: column});
                    //}
                    var value = item[column];
                    if (column == '@id') {
                        factory = globals.listing_titles.lookup(item);
                        value = factory({context: item});
                    } else if (value == null) {
                        value = '';
                    } else if (value instanceof Array) {
                        value = value;
                    } else if (value['@type']) {
                        factory = globals.listing_titles.lookup(value);
                        value = factory({context: value});
                    }
                    var sortable = ('' + value).toLowerCase();
                    return Cell(value, sortable);
                });
                var text = cells.map(function (cell) {
                    return cell.value;
                }).join(' ').toLowerCase();
                return Row(item, cells, text);
            });
            var data = Data(rows);
            this.setState({data: data})
            return data;
        },

        fetchAll: function (props) {
            var context = props.context;
            var communicating;
            if (this.allRequest && this.allRequest.state() == 'pending') {
                this.allRequest.abort();
            }
            var self = this;
            var $ = require('jquery');
            if (context.all) {
                communicating = true;
                this.setState({communicating: true});
                this.allRequest = $.ajax({
                    url: context.all,
                    type: 'GET',
                    dataType: 'json'
                }).done(function (data) {
                    self.extractData({context: data});
                    self.setState({communicating: false});
                });
            }
            return communicating;
        },

        render: function () {
            var columns = this.state.columns;
            var context = this.props.context;
            var defaultSortOn = this.props.defaultSortOn;
            var sortOn = this.state.sortOn;
            var reversed = this.state.reversed;
            var searchTerm = this.state.searchTerm;
            this.state.searchTerm = searchTerm;
            var titles = context.columns;
            var data = this.state.data;
            var params = url.parse(this.props.href, true).query;
            var total = context.count || data.rows.length;
            data.sort(sortOn, reversed);
            var self = this;
            var headers = columns.map(function (column, index) {
                var className = "sortdirection icon-";
                if (index === sortOn) {
                    className += reversed ? " icon-chevron-down" : " icon-chevron-up";
                }
                return (

                        {titles[column] || column}

                );
            });
            var searchTermLower = this.state.searchTerm.trim().toLowerCase();
            var matching = [];
            var not_matching = [];
            // Reorder rows so that the nth-child works
            if (searchTerm) {
                data.rows.forEach(function (row) {
                    if (row.text.indexOf(searchTermLower) == -1) {
                        not_matching.push(row);
                    } else {
                        matching.push(row);
                    }
                });
            } else {
                matching = data.rows;
            }
            var rows = matching.map(function (row) {
                return (
                    <RowView row={row} />
                );
            });
            rows.push.apply(rows, not_matching.map(function (row) {
                return (
                    <RowView row={row} hidden={true} />
                );
            }));
            var table_class = "sticky-area collection-table";
            var loading_or_total;
            if (this.state.communicating) {
                table_class += ' communicating';
                loading_or_total = (
                    Loading...
                );
            } else {
                loading_or_total = (

                        {matching.length}
                        of {total} records

                );
            }
            return (

                                {loading_or_total}
                                <form ref="form" className="table-filter" onKeyUp={this.handleKeyUp}
                                	data-skiprequest="true" data-removeempty="true">
                                    <input ref="q" disabled={this.state.communicating || undefined}
                                    	name="q" type="search" defaultValue={searchTerm}
                                    	placeholder="Filter table by..." className="filter"
                                    	id="table-filter" />

                                    <input ref="sorton" type="hidden" name="sorton" defaultValue={sortOn !== defaultSortOn ? sortOn : ''} />
                                    <input ref="reversed" type="hidden" name="reversed" defaultValue={!!reversed || ''} />
                                </form>

                            {headers}

                        {rows}

            );
        },

        componentDidMount: function () {
            this.setState({
                data: this.extractData(this.props),
                communicating: this.fetchAll(this.props),
                mounted: true,
            });
        },

        handleClickHeader: function (event) {
            var target = event.target;
            while (target.tagName != 'TH') {
                target = target.parentElement;
            }
            var cellIndex = target.cellIndex;
            var reversed = '';
            var sorton = this.refs.sorton.getDOMNode()
            if (this.props.defaultSortOn !== cellIndex) {
                sorton.value = cellIndex;
            } else {
                sorton.value = '';
            }
            if (this.state.sortOn == cellIndex) {
                reversed = !this.state.reversed || ''
            }
            this.refs.reversed.getDOMNode().value = reversed;
            event.preventDefault();
            event.stopPropagation();
            this.submit();
        },

        handleKeyUp: function (event) {
            if (typeof this.submitTimer != 'undefined') {
                clearTimeout(this.submitTimer);
            }
            // Skip when enter key is pressed
            if (event.nativeEvent.keyCode == 13) return;
            // IE8 should only submit on enter as page reload is triggered
            if (!this.hasEvent) return;
            this.submitTimer = setTimeout(this.submit, 200);
        },

        hasEvent: typeof Event !== 'undefined',

        submit: function () {
            // form.submit() does not fire onsubmit handlers...
            var target = this.refs.form.getDOMNode();

            // IE8 does not support the Event constructor
            if (!this.hasEvent) {
                target.submit();
                return;
            }

            var event = new Event('submit', {bubbles: true, cancelable: true});
            target.dispatchEvent(event);
        },

        clearFilter: function (event) {
            this.refs.q.getDOMNode().value = '';
            this.submitTimer = setTimeout(this.submit);
        },

        componentWillUnmount: function () {
            if (typeof this.submitTimer != 'undefined') {
                clearTimeout(this.submitTimer);
            }
            if (this.allRequest && this.allRequest.state() == 'pending') {
                this.allRequest.abort();
            }
        }

    });
<commit_msg>Handle missing columns specification.<commit_after>/** @jsx React.DOM */
'use strict';
var class_ = require('class');
var React = require('react');
var url = require('url');
var globals = require('./globals');

    var Collection = module.exports.Collection = React.createClass({
        render: function () {
            var context = this.props.context;
            return (

{context.title}

{context.description}

                    {this.transferPropsTo(
)}

            );
        }
    });

    globals.content_views.register(Collection, 'collection');

    var Cell = class_({
        constructor: function(value, sortable) {
            this.value = value;
            this.sortable = sortable;
        }
    });

    var Row = class_({
        constructor: function(item, cells, text) {
            this.item = item;
            this.cells = cells;
            this.text = text;
        }
    });

    var Data = class_({
        constructor: function(rows) {
            this.rows = rows;
            this.sortedOn = null;
            this.reversed = false;
        },
        sort: function(sortColumn, reverse) {
            reverse = !!reverse;
            if (this.sortedOn === sortColumn && this.reversed === reverse) return;
            this.sortedOn = sortColumn;
            this.reversed = reverse;
            this.rows.sort(function (rowA, rowB) {
                var a = '' + rowA.cells[sortColumn].sortable;
                var b = '' + rowB.cells[sortColumn].sortable;
                if (a  b) {
                    return reverse ? -1 : 1;
                }
                return 0;
            });
        }
    });

    var RowView = function (props) {
        var row = props.row;
        var id = row.item['@id'];
        var tds = row.cells.map( function (cell, index) {
            if (index === 0) {
                return (
                    {cell.value}
                );
            }
            return (
                {cell.value}
            );
        });
        return (

{tds}

        );
    };

    var Table = module.exports.Table = React.createClass({
        getDefaultProps: function () {
            return {
                defaultSortOn: 0
            };
        },

        getInitialState: function () {
            var state = this.extractParams(this.props);
            state.columns = this.guessColumns(this.props);
            state.data = Data([]);  // Tables may be long so render empty first
            state.communicating = true;
            return state;
        },

        componentWillReceiveProps: function (nextProps) {
            var updateData = false;
            if (nextProps.context !== this.props.context) {
                updateData = true;
                this.setState({
                    communicating: this.fetchAll(nextProps)
                });
            }
            if (nextProps.columns !== this.props.columns) {
                updateData = true;
            }
            if (updateData) {
                var columns = this.guessColumns(nextProps);
                this.extractData(nextProps, columns);
            }
            if (nextProps.href !== this.props.href) {
                this.extractParams(nextProps);
            }

        },

        extractParams: function(props) {
            var params = url.parse(props.href, true).query;
            var sorton = parseInt(params.sorton, 10);
            if (isNaN(sorton)) {
                sorton = props.defaultSortOn;
            }
            var state = {
                sortOn: sorton,
                reversed: params.reversed || false,
                searchTerm: params.q || ''
            };
            this.setState(state);
            return state;
        },

        guessColumns: function (props) {
            var column_list = props.context.columns;
            var columns = []
            if (column_list && Object.keys(column_list).length === 0) {
                for (var key in props.context['@graph'][0]) {
                    if (key.slice(0, 1) != '@' && key.search(/(uuid|_no|accession)/) == -1) {
                        columns.push(key);
                    }
                }
                columns.sort();
                columns.unshift('@id');
            } else {
                for(var column in column_list) {
                    columns.push(column)
                }
            }
            this.setState({columns: columns});
            return columns;
        },

        extractData: function (props, columns) {
            var context = props.context;
            columns = columns || this.state.columns;
            var rows = context['@graph'].map(function (item) {
                var cells = columns.map(function (column) {
                    var factory;
                    // cell factories
                    //if (factory) {
                    //    return factory({context: item, column: column});
                    //}
                    var value = item[column];
                    if (column == '@id') {
                        factory = globals.listing_titles.lookup(item);
                        value = factory({context: item});
                    } else if (value == null) {
                        value = '';
                    } else if (value instanceof Array) {
                        value = value;
                    } else if (value['@type']) {
                        factory = globals.listing_titles.lookup(value);
                        value = factory({context: value});
                    }
                    var sortable = ('' + value).toLowerCase();
                    return Cell(value, sortable);
                });
                var text = cells.map(function (cell) {
                    return cell.value;
                }).join(' ').toLowerCase();
                return Row(item, cells, text);
            });
            var data = Data(rows);
            this.setState({data: data})
            return data;
        },

        fetchAll: function (props) {
            var context = props.context;
            var communicating;
            if (this.allRequest && this.allRequest.state() == 'pending') {
                this.allRequest.abort();
            }
            var self = this;
            var $ = require('jquery');
            if (context.all) {
                communicating = true;
                this.setState({communicating: true});
                this.allRequest = $.ajax({
                    url: context.all,
                    type: 'GET',
                    dataType: 'json'
                }).done(function (data) {
                    self.extractData({context: data});
                    self.setState({communicating: false});
                });
            }
            return communicating;
        },

        render: function () {
            var columns = this.state.columns;
            var context = this.props.context;
            var defaultSortOn = this.props.defaultSortOn;
            var sortOn = this.state.sortOn;
            var reversed = this.state.reversed;
            var searchTerm = this.state.searchTerm;
            this.state.searchTerm = searchTerm;
            var titles = context.columns;
            var data = this.state.data;
            var params = url.parse(this.props.href, true).query;
            var total = context.count || data.rows.length;
            data.sort(sortOn, reversed);
            var self = this;
            var headers = columns.map(function (column, index) {
                var className = "sortdirection icon-";
                if (index === sortOn) {
                    className += reversed ? " icon-chevron-down" : " icon-chevron-up";
                }
                return (

                        {titles[column] || column}

                );
            });
            var searchTermLower = this.state.searchTerm.trim().toLowerCase();
            var matching = [];
            var not_matching = [];
            // Reorder rows so that the nth-child works
            if (searchTerm) {
                data.rows.forEach(function (row) {
                    if (row.text.indexOf(searchTermLower) == -1) {
                        not_matching.push(row);
                    } else {
                        matching.push(row);
                    }
                });
            } else {
                matching = data.rows;
            }
            var rows = matching.map(function (row) {
                return (
                    <RowView row={row} />
                );
            });
            rows.push.apply(rows, not_matching.map(function (row) {
                return (
                    <RowView row={row} hidden={true} />
                );
            }));
            var table_class = "sticky-area collection-table";
            var loading_or_total;
            if (this.state.communicating) {
                table_class += ' communicating';
                loading_or_total = (
                    Loading...
                );
            } else {
                loading_or_total = (

                        {matching.length}
                        of {total} records

                );
            }
            return (

                                {loading_or_total}
                                <form ref="form" className="table-filter" onKeyUp={this.handleKeyUp}
                                	data-skiprequest="true" data-removeempty="true">
                                    <input ref="q" disabled={this.state.communicating || undefined}
                                    	name="q" type="search" defaultValue={searchTerm}
                                    	placeholder="Filter table by..." className="filter"
                                    	id="table-filter" />

                                    <input ref="sorton" type="hidden" name="sorton" defaultValue={sortOn !== defaultSortOn ? sortOn : ''} />
                                    <input ref="reversed" type="hidden" name="reversed" defaultValue={!!reversed || ''} />
                                </form>

                            {headers}

                        {rows}

            );
        },

        componentDidMount: function () {
            this.setState({
                data: this.extractData(this.props),
                communicating: this.fetchAll(this.props),
                mounted: true,
            });
        },

        handleClickHeader: function (event) {
            var target = event.target;
            while (target.tagName != 'TH') {
                target = target.parentElement;
            }
            var cellIndex = target.cellIndex;
            var reversed = '';
            var sorton = this.refs.sorton.getDOMNode()
            if (this.props.defaultSortOn !== cellIndex) {
                sorton.value = cellIndex;
            } else {
                sorton.value = '';
            }
            if (this.state.sortOn == cellIndex) {
                reversed = !this.state.reversed || ''
            }
            this.refs.reversed.getDOMNode().value = reversed;
            event.preventDefault();
            event.stopPropagation();
            this.submit();
        },

        handleKeyUp: function (event) {
            if (typeof this.submitTimer != 'undefined') {
                clearTimeout(this.submitTimer);
            }
            // Skip when enter key is pressed
            if (event.nativeEvent.keyCode == 13) return;
            // IE8 should only submit on enter as page reload is triggered
            if (!this.hasEvent) return;
            this.submitTimer = setTimeout(this.submit, 200);
        },

        hasEvent: typeof Event !== 'undefined',

        submit: function () {
            // form.submit() does not fire onsubmit handlers...
            var target = this.refs.form.getDOMNode();

            // IE8 does not support the Event constructor
            if (!this.hasEvent) {
                target.submit();
                return;
            }

            var event = new Event('submit', {bubbles: true, cancelable: true});
            target.dispatchEvent(event);
        },

        clearFilter: function (event) {
            this.refs.q.getDOMNode().value = '';
            this.submitTimer = setTimeout(this.submit);
        },

        componentWillUnmount: function () {
            if (typeof this.submitTimer != 'undefined') {
                clearTimeout(this.submitTimer);
            }
            if (this.allRequest && this.allRequest.state() == 'pending') {
                this.allRequest.abort();
            }
        }

    });
<|endoftext|>


<commit_before>import request from 'request-promise';

import log from '../lib/logger-service';

const Messenger = {
  send(id, text, quickReplies = []) {
    let body = {
      recipient: {
        id,
      },
      message: {
        text,
      },
    };

    if (quickReplies.length) {
      body.message['quick_replies'] = [];
    }

    for (let quickReply of quickReplies) {
      body.message['quick_replies'].push({
        'content_type': 'text',
        'title': quickReply.title,
        'payload': quickReply.payload || quickReply.title,
      });
    }

    log.info('Sending FB message to {0}: "{1}"', id, text);

    return _fbMessageRequest(body);
  },

  toggleTypingIndicator(id, typing = true) {
    const action = (typing) ? 'typing_on' : 'typing_off';
    const body = {
      'recipient': {
        id,
      },
      'sender_action': action,
    };

    return _fbMessageRequest(body);
  },

  /**
   * @param {any} data
   * @param {Chatbot} chatbot
   * @return {Promise}
   */
  receive(data, chatbot) {
    if (data.object === 'page') {
      const promises = [];

      data.entry.forEach((entry) => {
        entry.messaging.forEach((messageEvent) => {
          if (messageEvent.message && !messageEvent.message.is_echo) {
            promises.push(_receiveMessage(messageEvent, chatbot));
          }
        });
      });

      return Promise.all(promises);
    } else {
      return Promise.reject(new Error('Bad event'));
    }
  },

  verify(token, challenge) {
    if (token === process.env.FACEBOOK_VERIFY_TOKEN) {
      return Promise.resolve({
        response: challenge,
      });
    }

    return Promise.reject(new Error('400 Bad Token'));
  },

  getUserProfile(id) {
    if (process.env.RUN_ENV === 'dev') {
      return Promise
        .resolve({ firstName: 'test', lastName: 'test', profilePic: 'http://test' });
    }

    if (!process.env.FACEBOOK_PAGE_ACCESS_TOKEN) {
      return Promise
        .reject(new Error('No FACEBOOK_PAGE_ACCESS_TOKEN defined'));
    }

    log.info('Getting Facebook User Profile');

    return request({
      url: 'https://graph.facebook.com/v2.6/' + id,
      qs: {
        fields: 'first_name,last_name,profile_pic',
        access_token: process.env.FACEBOOK_PAGE_ACCESS_TOKEN,
      },
      method: 'GET',
    });
  },
};

function _receiveMessage(messageEvent, chatbot) {
  return new Promise((resolve, reject) => {
    const sender = messageEvent.sender.id;
    const text = messageEvent.message.text;

    if (!text) {
      const unhandledMessageTypeResponse =
        'Voit lähettää minulle vain tekstiä tai painaa antamiani nappeja.';
      return resolve(Messenger.send(sender, unhandledMessageTypeResponse));
    }

    return resolve(Messenger.toggleTypingIndicator(sender, true)
      .then(() => chatbot.receive(sender, text))
      .then((response) => {
        let promise = Promise.resolve();
        for (let r of response) {
          promise = promise.then(() => {
            return Messenger.send(sender,
              r.message, r.quickReplies);
          });
        }
        return promise
          .then(() => Messenger.toggleTypingIndicator(sender, false));
      }));
  });
}

function _fbMessageRequest(json) {
  // skip facebook message sending on local client
  if (process.env.RUN_ENV === 'dev') {
    return Promise.resolve();
  }

  if (!process.env.FACEBOOK_PAGE_ACCESS_TOKEN) {
    return Promise
      .reject(new Error('No FACEBOOK_PAGE_ACCESS_TOKEN defined'));
  }

  log.info('Sending message to Facebook: {0}', JSON.stringify(json));

  return request({
    url: 'https://graph.facebook.com/v2.6/me/messages',
    qs: {
      access_token: process.env.FACEBOOK_PAGE_ACCESS_TOKEN,
    },
    method: 'POST',
    json,
  });
}

module.exports = Messenger;
<commit_msg>fb user profile<commit_after>import request from 'request-promise';

import log from '../lib/logger-service';

const Messenger = {
  send(id, text, quickReplies = []) {
    let body = {
      recipient: {
        id,
      },
      message: {
        text,
      },
    };

    if (quickReplies.length) {
      body.message['quick_replies'] = [];
    }

    for (let quickReply of quickReplies) {
      body.message['quick_replies'].push({
        'content_type': 'text',
        'title': quickReply.title,
        'payload': quickReply.payload || quickReply.title,
      });
    }

    log.info('Sending FB message to {0}: "{1}"', id, text);

    return _fbMessageRequest(body);
  },

  toggleTypingIndicator(id, typing = true) {
    const action = (typing) ? 'typing_on' : 'typing_off';
    const body = {
      'recipient': {
        id,
      },
      'sender_action': action,
    };

    return _fbMessageRequest(body);
  },

  /**
   * @param {any} data
   * @param {Chatbot} chatbot
   * @return {Promise}
   */
  receive(data, chatbot) {
    if (data.object === 'page') {
      const promises = [];

      data.entry.forEach((entry) => {
        entry.messaging.forEach((messageEvent) => {
          if (messageEvent.message && !messageEvent.message.is_echo) {
            promises.push(_receiveMessage(messageEvent, chatbot));
          }
        });
      });

      return Promise.all(promises);
    } else {
      return Promise.reject(new Error('Bad event'));
    }
  },

  verify(token, challenge) {
    if (token === process.env.FACEBOOK_VERIFY_TOKEN) {
      return Promise.resolve({
        response: challenge,
      });
    }

    return Promise.reject(new Error('400 Bad Token'));
  },

  getUserProfile(id, input) {
    if (process.env.RUN_ENV === 'dev') {
      return Promise
        .resolve({ firstName: input, lastName: '', profilePic: '' });
    }

    if (!process.env.FACEBOOK_PAGE_ACCESS_TOKEN) {
      return Promise
        .reject(new Error('No FACEBOOK_PAGE_ACCESS_TOKEN defined'));
    }

    log.info('Getting Facebook User Profile');

    return request({
      url: 'https://graph.facebook.com/v2.6/' + id,
      qs: {
        fields: 'first_name,last_name,profile_pic',
        access_token: process.env.FACEBOOK_PAGE_ACCESS_TOKEN,
      },
      method: 'GET',
    });
  },
};

function _receiveMessage(messageEvent, chatbot) {
  return new Promise((resolve, reject) => {
    const sender = messageEvent.sender.id;
    const text = messageEvent.message.text;

    if (!text) {
      const unhandledMessageTypeResponse =
        'Voit lähettää minulle vain tekstiä tai painaa antamiani nappeja.';
      return resolve(Messenger.send(sender, unhandledMessageTypeResponse));
    }

    return resolve(Messenger.toggleTypingIndicator(sender, true)
      .then(() => chatbot.receive(sender, text))
      .then((response) => {
        let promise = Promise.resolve();
        for (let r of response) {
          promise = promise.then(() => {
            return Messenger.send(sender,
              r.message, r.quickReplies);
          });
        }
        return promise
          .then(() => Messenger.toggleTypingIndicator(sender, false));
      }));
  });
}

function _fbMessageRequest(json) {
  // skip facebook message sending on local client
  if (process.env.RUN_ENV === 'dev') {
    return Promise.resolve();
  }

  if (!process.env.FACEBOOK_PAGE_ACCESS_TOKEN) {
    return Promise
      .reject(new Error('No FACEBOOK_PAGE_ACCESS_TOKEN defined'));
  }

  log.info('Sending message to Facebook: {0}', JSON.stringify(json));

  return request({
    url: 'https://graph.facebook.com/v2.6/me/messages',
    qs: {
      access_token: process.env.FACEBOOK_PAGE_ACCESS_TOKEN,
    },
    method: 'POST',
    json,
  });
}

module.exports = Messenger;
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

define([
       "app",
       "api",
       "addons/activetasks/resources"
],

function (app, FauxtonAPI, activetasks) {

  var Views = {},
      Events = {},
      pollingInfo ={
        rate: "5",
        intervalId: null
      };

  Views.Events = _.extend(Events, Backbone.Events);

  Views.TabMenu = FauxtonAPI.View.extend({
    template: "addons/activetasks/templates/tabs",
    events: {
      "click .task-tabs li": "requestByType",
      "change #pollingRange": "changePollInterval"
    },
    establish: function(){
      return [this.model.fetch({reset: true})];
    },
    serialize: function(){
      return {
        filters: this.model.alltypes
      };
    },
    afterRender: function(){
      this.$('.task-tabs').find('li').eq(0).addClass('active');
    },
    changePollInterval: function(e){
      var range = this.$(e.currentTarget).val();
      this.$('label[for="pollingRange"] span').text(range);
      pollingInfo.rate = range;
      clearInterval(pollingInfo.intervalId);
      Events.trigger('update:poll');
    },

    cleanup: function () {
      clearInterval(pollingInfo.intervalId);
    },

    requestByType: function(e){
      var currentTarget = e.currentTarget;
      datatype = this.$(currentTarget).attr("data-type");

      this.$('.task-tabs').find('li').removeClass('active');
      this.$(currentTarget).addClass('active');
      this.model.changeView(datatype);
    }
  });

  Views.DataSection = FauxtonAPI.View.extend({
    showData: function(){
      var currentData = this.model.getCurrentViewData();

      if (this.dataView) {
       this.dataView.update(currentData, this.model.get('currentView').replace('_',' '));
      } else {
        this.dataView = this.insertView( new Views.TableData({
          collection: currentData,
          currentView: this.model.get('currentView').replace('_',' ')
        }));
      }
    },
    showDataAndRender: function () {
      this.showData();
      this.dataView.render();
    },

    beforeRender: function () {
      this.showData();
    },
    establish: function(){
      return [this.model.fetch()];
    },
    setPolling: function(){
      var that = this;
      clearInterval(pollingInfo.intervalId);
      pollingInfo.intervalId = setInterval(function() {
        that.establish();
      }, pollingInfo.rate*1000);
    },
    cleanup: function(){
      clearInterval(pollingInfo.intervalId);
    },
    afterRender: function(){
      this.listenTo(this.model, "change", this.showDataAndRender);
      Events.bind('update:poll', this.setPolling, this);
      this.setPolling();
    }
  });

  Views.TableData = FauxtonAPI.View.extend({
    tagName: "table",
    className: "table table-bordered table-striped active-tasks",
    template: "addons/activetasks/templates/table",
    events: {
      "click th": "sortByType"
    },
    initialize: function(){
      currentView = this.options.currentView;
    },
    sortByType:  function(e){
      var currentTarget = e.currentTarget;
      datatype = $(currentTarget).attr("data-type");
      this.collection.sortByColumn(datatype);
      this.render();
    },
    serialize: function(){
      return {
        currentView: currentView,
        collection: this.collection
      };
    },

    update: function (collection, currentView) {
      this.collection = collection;
      this.currentView = currentView;
    },

    beforeRender: function(){
      //iterate over the collection to add each
      this.collection.forEach(function(item) {
        this.insertView("#tasks_go_here", new Views.TableDetail({
          model: item
        }));
      }, this);
    }
  });

  Views.TableDetail = FauxtonAPI.View.extend({
    tagName: 'tr',
    template: "addons/activetasks/templates/tabledetail",
    initialize: function(){
      this.type = this.model.get('type');
    },
    getObject: function(){
      var objectField = this.model.get('database');
      if (this.type === "replication"){
        objectField = this.model.get('source') + " to " + this.model.get('target');
      }
      return objectField;
    },
    getProgress:  function(){
      var progress = "";
      if (this.type === "indexer"){
        progress = "Processed " +this.model.get('changes_done')+ " of "+this.model.get('total_changes')+ ' changes.';
      } else if (this.type === "replication"){
        progress = this.model.get('docs_written')+ " docs written. ";
        if (this.model.get('changes_pending') !== undefined) {
          progress += this.model.get('changes_pending') + ' pending changes. ';
        }
      }
      if (this.model.get('source_seq') !== undefined) {
        progress += "Current source sequence: " + this.model.get('source_seq') + ". ";
      }
      if (this.model.get('changes_done') !== undefined) {
        progress += this.model.get('changes_done') + " Changes done. ";
      }
      if (this.model.get('progress') !== undefined) {
        progress += "Progress: " + this.model.get('progress') + "% ";
      }

      return progress;
    },
    serialize: function(){
      return {
        model: this.model,
        objectField: this.getObject(),
        progress: this.getProgress()
      };
    }
  });

  return Views;
});
<commit_msg>Fauxton: fix global var<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

define([
       "app",
       "api",
       "addons/activetasks/resources"
],

function (app, FauxtonAPI, activetasks) {

  var Views = {},
      Events = {},
      pollingInfo ={
        rate: "5",
        intervalId: null
      };

  Views.Events = _.extend(Events, Backbone.Events);

  Views.TabMenu = FauxtonAPI.View.extend({
    template: "addons/activetasks/templates/tabs",
    events: {
      "click .task-tabs li": "requestByType",
      "change #pollingRange": "changePollInterval"
    },
    establish: function(){
      return [this.model.fetch({reset: true})];
    },
    serialize: function(){
      return {
        filters: this.model.alltypes
      };
    },
    afterRender: function(){
      this.$('.task-tabs').find('li').eq(0).addClass('active');
    },
    changePollInterval: function(e){
      var range = this.$(e.currentTarget).val();
      this.$('label[for="pollingRange"] span').text(range);
      pollingInfo.rate = range;
      clearInterval(pollingInfo.intervalId);
      Events.trigger('update:poll');
    },

    cleanup: function () {
      clearInterval(pollingInfo.intervalId);
    },

    requestByType: function(e){
      var currentTarget = e.currentTarget,
          datatype = this.$(currentTarget).attr("data-type");

      this.$('.task-tabs').find('li').removeClass('active');
      this.$(currentTarget).addClass('active');
      this.model.changeView(datatype);
    }
  });

  Views.DataSection = FauxtonAPI.View.extend({
    showData: function(){
      var currentData = this.model.getCurrentViewData();

      if (this.dataView) {
       this.dataView.update(currentData, this.model.get('currentView').replace('_',' '));
      } else {
        this.dataView = this.insertView( new Views.TableData({
          collection: currentData,
          currentView: this.model.get('currentView').replace('_',' ')
        }));
      }
    },
    showDataAndRender: function () {
      this.showData();
      this.dataView.render();
    },

    beforeRender: function () {
      this.showData();
    },
    establish: function(){
      return [this.model.fetch()];
    },
    setPolling: function(){
      var that = this;
      clearInterval(pollingInfo.intervalId);
      pollingInfo.intervalId = setInterval(function() {
        that.establish();
      }, pollingInfo.rate*1000);
    },
    cleanup: function(){
      clearInterval(pollingInfo.intervalId);
    },
    afterRender: function(){
      this.listenTo(this.model, "change", this.showDataAndRender);
      Events.bind('update:poll', this.setPolling, this);
      this.setPolling();
    }
  });

  Views.TableData = FauxtonAPI.View.extend({
    tagName: "table",
    className: "table table-bordered table-striped active-tasks",
    template: "addons/activetasks/templates/table",
    events: {
      "click th": "sortByType"
    },
    initialize: function(){
      currentView = this.options.currentView;
    },
    sortByType:  function(e){
      var currentTarget = e.currentTarget,
          datatype = $(currentTarget).attr("data-type");

      this.collection.sortByColumn(datatype);
      this.render();
    },
    serialize: function(){
      return {
        currentView: currentView,
        collection: this.collection
      };
    },

    update: function (collection, currentView) {
      this.collection = collection;
      this.currentView = currentView;
    },

    beforeRender: function(){
      //iterate over the collection to add each
      this.collection.forEach(function(item) {
        this.insertView("#tasks_go_here", new Views.TableDetail({
          model: item
        }));
      }, this);
    }
  });

  Views.TableDetail = FauxtonAPI.View.extend({
    tagName: 'tr',
    template: "addons/activetasks/templates/tabledetail",
    initialize: function(){
      this.type = this.model.get('type');
    },
    getObject: function(){
      var objectField = this.model.get('database');
      if (this.type === "replication"){
        objectField = this.model.get('source') + " to " + this.model.get('target');
      }
      return objectField;
    },
    getProgress:  function(){
      var progress = "";
      if (this.type === "indexer"){
        progress = "Processed " +this.model.get('changes_done')+ " of "+this.model.get('total_changes')+ ' changes.';
      } else if (this.type === "replication"){
        progress = this.model.get('docs_written')+ " docs written. ";
        if (this.model.get('changes_pending') !== undefined) {
          progress += this.model.get('changes_pending') + ' pending changes. ';
        }
      }
      if (this.model.get('source_seq') !== undefined) {
        progress += "Current source sequence: " + this.model.get('source_seq') + ". ";
      }
      if (this.model.get('changes_done') !== undefined) {
        progress += this.model.get('changes_done') + " Changes done. ";
      }
      if (this.model.get('progress') !== undefined) {
        progress += "Progress: " + this.model.get('progress') + "% ";
      }

      return progress;
    },
    serialize: function(){
      return {
        model: this.model,
        objectField: this.getObject(),
        progress: this.getProgress()
      };
    }
  });

  return Views;
});
<|endoftext|>


<commit_before>pc.gfx.programlib.phong = {
    generateKey: function (device, options) {

        var props = [];
        var key = "phong";
        for(prop in options) {
            props.push(prop+"="+(options[prop]));
        }
        props.sort();
         // The key is quite longer this way and it's not great for string comparisons.
         // However, it frees us from managing key generation after adding each new parameter.
         // We don't create new shaders real-time anyway... so string comparison shouldn't have huge impact.
        for(prop in props) key += "_" + props[prop];

        /* We shouldn't specify numbers of lights in options!
           Instead we should specify each light as separate option, because we have compilation-time per-light parameters like shadows on/off
        */

        return key;
    },

    _setMapTransform: function (codes, name) {
        var capitalName = name.charAt(0).toUpperCase() + name.slice(1);
        codes[0] += "uniform vec4 texture_"+name+"MapTransform;\n"
        codes[1] += "varying vec2 vUv"+capitalName+";\n"

        codes[2] += "   vUv"+capitalName+"   = vUv0 * texture_"+name+"MapTransform.xy + texture_"+name+"MapTransform.zw;\n";

        return codes;
    },

    _uvSource: function(name) {
        var capitalName = name.charAt(0).toUpperCase() + name.slice(1);
        return this.options[name+"MapTransform"]? "vUv"+capitalName : "vUv0";
    },

    createShaderDefinition: function (device, options) {
        var i;
        var lighting = options.numLights > 0;
        var useTangents = pc.gfx.precalculatedTangents;

        this.options = options;

        ////////////////////////////
        // GENERATE VERTEX SHADER //
        ////////////////////////////
        var getSnippet = pc.gfx.programlib.getSnippet;
        var code = '';
        var codeBody = '';

        var varyings = ""; // additional varyings for map transforms

        var chunks = pc.gfx.shaderChunks;
        code += chunks.baseVS;

        var attributes = {
            vertex_position: pc.gfx.SEMANTIC_POSITION
        }
        codeBody += "   vPositionW    = getWorldPosition(data);\n";

        if (lighting) {
            attributes.vertex_normal = pc.gfx.SEMANTIC_NORMAL;
            codeBody += "   vNormalW    = getNormal(data);\n";

            if ((options.sphereMap) && (device.fragmentUniformsCount <= 16)) {
                code += chunks.viewNormalVS;
                codeBody += "   vNormalV    = getViewNormal(data);\n";
            }

            if (options.normalMap && useTangents) {
                attributes.vertex_tangent = pc.gfx.SEMANTIC_TANGENT;
                code += chunks.tangentBinormalVS;
                codeBody += "   vTangentW   = getTangent(data);\n";
                codeBody += "   vBinormalW  = getBinormal(data);\n";
            }
        }

        if (options.diffuseMap || options.specularMap || options.glossMap ||
            options.emissiveMap || options.normalMap || options.heightMap || options.opacityMap) {
            attributes.vertex_texCoord0 = pc.gfx.SEMANTIC_TEXCOORD0;
            code += chunks.uv0VS;
            codeBody += "   vUv0        = getUv0(data);\n";

            var codes = [code, varyings, codeBody];
            if (options.diffuseMapTransform)    this._setMapTransform(codes, "diffuse");
            if (options.normalMapTransform)     this._setMapTransform(codes, "normal");
            if (options.heightMapTransform)     this._setMapTransform(codes, "height");
            if (options.opacityMapTransform)    this._setMapTransform(codes, "opacity");
            if (options.useSpecular) {
                if (options.specularMapTransform)   this._setMapTransform(codes, "specular");
                if (options.glossMapTransform)      this._setMapTransform(codes, "gloss");
            }
            if (options.emissiveMapTransform)   this._setMapTransform(codes, "emissive");
            code = codes[0] + codes[1];
            varyings = codes[1];
            codeBody = codes[2];
        }

        if (options.lightMap) {
            attributes.vertex_texCoord1 = pc.gfx.SEMANTIC_TEXCOORD1;
            code += chunks.uv1VS;
            codeBody += "   vUv1        = getUv1(data);\n";
        }

        if (options.vertexColors) {
            attributes.vertex_color = pc.gfx.SEMANTIC_COLOR;
        }

        if (options.skin) {
            attributes.vertex_boneWeights = pc.gfx.SEMANTIC_BLENDWEIGHT;
            attributes.vertex_boneIndices = pc.gfx.SEMANTIC_BLENDINDICES;
            code += getSnippet(device, 'vs_skin_decl');
            code += chunks.transformSkinnedVS;
            if (lighting) code += chunks.normalSkinnedVS;
        } else {
            code += chunks.transformVS;
            if (lighting) code += chunks.normalVS;
        }

        code += "\n";

        code += chunks.startVS;
        code += codeBody;
        code += "}";

        var vshader = code;

        //////////////////////////////
        // GENERATE FRAGMENT SHADER //
        //////////////////////////////
        code = getSnippet(device, 'fs_precision');
        code += chunks.basePS;
        code += varyings;

        // FRAGMENT SHADER INPUTS: UNIFORMS
        var numShadowLights = 0;
        for (i = 0; i < options.numLights; i++) {
            var lightType = options.lightType[i];
            code += "uniform vec3 light" + i + "_color;\n";
            if (lightType==pc.scene.LIGHTTYPE_DIRECTIONAL) {
                code += "uniform vec3 light" + i + "_direction;\n";
            } else {
                code += "uniform vec3 light" + i + "_position;\n";
                code += "uniform float light" + i + "_radius;\n";
                if (lightType==pc.scene.LIGHTTYPE_SPOT) {
                    code += "uniform vec3 light" + i + "_spotDirection;\n";
                    code += "uniform float light" + i + "_innerConeAngle;\n";
                    code += "uniform float light" + i + "_outerConeAngle;\n";
                }
            }
            if (options.lightShadow[i]) {
                code += "uniform mat4 light" + i + "_shadowMatrix;\n";
                code += "uniform vec3 light" + i + "_shadowParams;\n"; // Width, height, bias
                code += "uniform sampler2D light" + i + "_shadowMap;\n";
                numShadowLights++;
            }
        }

        switch (options.fog) {
            case 'linear':
                code += getSnippet(device, 'fs_fog_linear_decl');
                break;
            case 'exp':
                code += getSnippet(device, 'fs_fog_exp_decl');
                break;
            case 'exp2':
                code += getSnippet(device, 'fs_fog_exp2_decl');
                break;
        }

        if (options.alphaTest) {
            code += getSnippet(device, 'fs_alpha_test_decl');
        }

        code += "\n"; // End of uniform declarations

        var uvOffset = options.heightMap? " + data.uvOffset" : "";

        if (options.normalMap && useTangents) {
            code += chunks.normalMapPS.replace(/\$UV/g, this._uvSource("normal")+uvOffset);
            code += chunks.TBNPS;
        } else {
            code += chunks.normalVertexPS
        }

        code += chunks.defaultGamma;

        if (options.diffuseMap) {
            code += chunks.albedoTexPS.replace(/\$UV/g, this._uvSource("diffuse")+uvOffset);
        } else {
            code += chunks.albedoColorPS;
        }

        if (options.useSpecular) {
            if (options.specularMap) {
                code += chunks.specularityTexPS.replace(/\$UV/g, this._uvSource("specular")+uvOffset);
            } else {
                code += chunks.specularityColorPS;
            }
            if (options.useFresnel) {
                code += chunks.defaultFresnel;
            }
        }

        if (options.glossMap) {
            code += chunks.glossinessTexPS.replace(/\$UV/g, this._uvSource("gloss")+uvOffset);
        } else {
            code += chunks.glossinessFloatPS;
        }

        if (options.opacityMap) {
            code += chunks.opacityTexPS.replace(/\$UV/g, this._uvSource("opacity")+uvOffset);
        } else {
            code += chunks.opacityFloatPS;
        }

        if (options.emissiveMap) {
            code += chunks.emissionTexPS.replace(/\$UV/g, this._uvSource("emissive")+uvOffset);
        } else {
            code += chunks.emissionColorPS;
        }

        if (options.heightMap) {
            if (!options.normalMap) code += chunks.TBNPS;
            code += chunks.parallaxPS.replace(/\$UV/g, this._uvSource("height"));
        }

        if (options.cubeMap) {
            code += chunks.reflectionCubePS;
        }

        if (options.sphereMap) {
            code += device.fragmentUniformsCount>16? chunks.reflectionSpherePS : chunks.reflectionSphereLowPS;
        }

        if (options.lightMap) {
            code += chunks.lightmapSinglePS;
        }

        if (numShadowLights > 0) {
            code += chunks.shadowPS;
        }

        code += chunks.lightDiffuseLambertPS;
        if (options.useSpecular) {
            code += chunks.defaultSpecular;
            if (options.sphereMap || options.cubeMap || options.useFresnel) {
                if (options.useFresnel) {
                    code += chunks.combineDiffuseSpecularPS; // this one is correct, others are old stuff
                } else {
                    code += chunks.combineDiffuseSpecularOldPS;
                }
            } else {
                if (options.diffuseMap) {
                    code += chunks.combineDiffuseSpecularNoReflPS;
                } else {
                    code += chunks.combineDiffuseSpecularNoReflSeparateAmbientPS;
                }
            }
        } else {
            code += chunks.combineDiffusePS;
        }

        // FRAGMENT SHADER BODY
        code += chunks.startPS;

        if (lighting) {
            code += "   getViewDir(data);\n";
            if (options.heightMap || options.normalMap) {
                code += "   getTBN(data);\n";
            }
            if (options.heightMap) {
                code += "   getParallax(data);\n"
            }
            code += "   getNormal(data);\n";
            if (options.useSpecular) code += "   getReflDir(data);\n";
        }

        code += "   getAlbedo(data);\n";

        if (lighting && options.useSpecular) {
            code += "   getSpecularity(data);\n";
            if (options.useFresnel) code += "   getFresnel(data);\n";
            code += "   getGlossiness(data);\n";
        }

        code += "   getOpacity(data);\n";
        if (options.alphaTest) {
            code += "   if (data.alpha < alpha_ref) discard;"
        }

        if (options.lightMap) {
                code += "    addLightmap(data);\n";
         }

        if (lighting) {
            code += "   addAmbientConstant(data);\n";

            if (options.cubeMap) {
                code += "   addCubemapReflection(data);\n";
            } else if (options.sphereMap) {
                code += "   addSpheremapReflection(data);\n";
            }

            for (i = 0; i < options.numLights; i++) {
                // The following code is not decoupled to separate shader files, because most of it can be actually changed to achieve different behaviours like:
                // - different falloffs
                // - different shadow coords (point shadows will use drastically different genShadowCoord)
                // - different shadow filter modes

                // getLightDiffuse and getLightSpecular is BRDF (currently lame Lambert+Phong) itself.

                if (options.lightType[i]==pc.scene.LIGHTTYPE_DIRECTIONAL) {
                    // directional
                    code += "   data.lightDirNormW = light"+i+"_direction;\n";
                    code += "   data.atten = 1.0;\n";
                } else {
                    code += "   getLightDirPoint(data, light"+i+"_position);\n";
                    code += "   data.atten = getFalloffLinear(data, light"+i+"_radius);\n";
                    if (options.lightType[i]==pc.scene.LIGHTTYPE_SPOT) {
                        code += "   data.atten *= getSpotEffect(data, light"+i+"_spotDirection, light"+i+"_innerConeAngle, light"+i+"_outerConeAngle);\n";
                    }
                }

                code += "   data.atten *= getLightDiffuse(data);\n";
                if (options.lightShadow[i]) {
                    code += "   getShadowCoord(data, light"+i+"_shadowMatrix, light"+i+"_shadowParams);\n";
                    code += "   data.atten *= getShadowPCF3x3(data, light"+i+"_shadowMap, light"+i+"_shadowParams);\n";
                }

                code += "   data.diffuseLight += data.atten * light"+i+"_color;\n";

                if (options.useSpecular) {
                    code += "   data.atten *= getLightSpecular(data);\n"
                    code += "   data.specularLight += data.atten * light"+i+"_color;\n";
                }
                code += "\n";
            }
        }
        code += "\n";
        code += chunks.endPS;

        // Fog
        switch (options.fog) {
            case 'linear':
                code += getSnippet(device, 'fs_fog_linear');
                break;
            case 'exp':
                code += getSnippet(device, 'fs_fog_exp');
                break;
            case 'exp2':
                code += getSnippet(device, 'fs_fog_exp2');
                break;
        }

        // Make sure all components are between 0 and 1
        code += getSnippet(device, 'fs_clamp');

        code += getSnippet(device, 'common_main_end');

        var fshader = code;

        return {
            attributes: attributes,
            vshader: vshader,
            fshader: fshader
        };
    }
};
<commit_msg>[FIX] Ambient lighting is now respected when there are no lights enabled.<commit_after>pc.gfx.programlib.phong = {
    generateKey: function (device, options) {

        var props = [];
        var key = "phong";
        for(prop in options) {
            props.push(prop+"="+(options[prop]));
        }
        props.sort();
         // The key is quite longer this way and it's not great for string comparisons.
         // However, it frees us from managing key generation after adding each new parameter.
         // We don't create new shaders real-time anyway... so string comparison shouldn't have huge impact.
        for(prop in props) key += "_" + props[prop];

        /* We shouldn't specify numbers of lights in options!
           Instead we should specify each light as separate option, because we have compilation-time per-light parameters like shadows on/off
        */

        return key;
    },

    _setMapTransform: function (codes, name) {
        var capitalName = name.charAt(0).toUpperCase() + name.slice(1);
        codes[0] += "uniform vec4 texture_"+name+"MapTransform;\n"
        codes[1] += "varying vec2 vUv"+capitalName+";\n"

        codes[2] += "   vUv"+capitalName+"   = vUv0 * texture_"+name+"MapTransform.xy + texture_"+name+"MapTransform.zw;\n";

        return codes;
    },

    _uvSource: function(name) {
        var capitalName = name.charAt(0).toUpperCase() + name.slice(1);
        return this.options[name+"MapTransform"]? "vUv"+capitalName : "vUv0";
    },

    createShaderDefinition: function (device, options) {
        var i;
        var lighting = options.numLights > 0;
        var useTangents = pc.gfx.precalculatedTangents;

        this.options = options;

        ////////////////////////////
        // GENERATE VERTEX SHADER //
        ////////////////////////////
        var getSnippet = pc.gfx.programlib.getSnippet;
        var code = '';
        var codeBody = '';

        var varyings = ""; // additional varyings for map transforms

        var chunks = pc.gfx.shaderChunks;
        code += chunks.baseVS;

        var attributes = {
            vertex_position: pc.gfx.SEMANTIC_POSITION
        }
        codeBody += "   vPositionW    = getWorldPosition(data);\n";

        if (lighting) {
            attributes.vertex_normal = pc.gfx.SEMANTIC_NORMAL;
            codeBody += "   vNormalW    = getNormal(data);\n";

            if ((options.sphereMap) && (device.fragmentUniformsCount <= 16)) {
                code += chunks.viewNormalVS;
                codeBody += "   vNormalV    = getViewNormal(data);\n";
            }

            if (options.normalMap && useTangents) {
                attributes.vertex_tangent = pc.gfx.SEMANTIC_TANGENT;
                code += chunks.tangentBinormalVS;
                codeBody += "   vTangentW   = getTangent(data);\n";
                codeBody += "   vBinormalW  = getBinormal(data);\n";
            }
        }

        if (options.diffuseMap || options.specularMap || options.glossMap ||
            options.emissiveMap || options.normalMap || options.heightMap || options.opacityMap) {
            attributes.vertex_texCoord0 = pc.gfx.SEMANTIC_TEXCOORD0;
            code += chunks.uv0VS;
            codeBody += "   vUv0        = getUv0(data);\n";

            var codes = [code, varyings, codeBody];
            if (options.diffuseMapTransform)    this._setMapTransform(codes, "diffuse");
            if (options.normalMapTransform)     this._setMapTransform(codes, "normal");
            if (options.heightMapTransform)     this._setMapTransform(codes, "height");
            if (options.opacityMapTransform)    this._setMapTransform(codes, "opacity");
            if (options.useSpecular) {
                if (options.specularMapTransform)   this._setMapTransform(codes, "specular");
                if (options.glossMapTransform)      this._setMapTransform(codes, "gloss");
            }
            if (options.emissiveMapTransform)   this._setMapTransform(codes, "emissive");
            code = codes[0] + codes[1];
            varyings = codes[1];
            codeBody = codes[2];
        }

        if (options.lightMap) {
            attributes.vertex_texCoord1 = pc.gfx.SEMANTIC_TEXCOORD1;
            code += chunks.uv1VS;
            codeBody += "   vUv1        = getUv1(data);\n";
        }

        if (options.vertexColors) {
            attributes.vertex_color = pc.gfx.SEMANTIC_COLOR;
        }

        if (options.skin) {
            attributes.vertex_boneWeights = pc.gfx.SEMANTIC_BLENDWEIGHT;
            attributes.vertex_boneIndices = pc.gfx.SEMANTIC_BLENDINDICES;
            code += getSnippet(device, 'vs_skin_decl');
            code += chunks.transformSkinnedVS;
            if (lighting) code += chunks.normalSkinnedVS;
        } else {
            code += chunks.transformVS;
            if (lighting) code += chunks.normalVS;
        }

        code += "\n";

        code += chunks.startVS;
        code += codeBody;
        code += "}";

        var vshader = code;

        //////////////////////////////
        // GENERATE FRAGMENT SHADER //
        //////////////////////////////
        code = getSnippet(device, 'fs_precision');
        code += chunks.basePS;
        code += varyings;

        // FRAGMENT SHADER INPUTS: UNIFORMS
        var numShadowLights = 0;
        for (i = 0; i < options.numLights; i++) {
            var lightType = options.lightType[i];
            code += "uniform vec3 light" + i + "_color;\n";
            if (lightType==pc.scene.LIGHTTYPE_DIRECTIONAL) {
                code += "uniform vec3 light" + i + "_direction;\n";
            } else {
                code += "uniform vec3 light" + i + "_position;\n";
                code += "uniform float light" + i + "_radius;\n";
                if (lightType==pc.scene.LIGHTTYPE_SPOT) {
                    code += "uniform vec3 light" + i + "_spotDirection;\n";
                    code += "uniform float light" + i + "_innerConeAngle;\n";
                    code += "uniform float light" + i + "_outerConeAngle;\n";
                }
            }
            if (options.lightShadow[i]) {
                code += "uniform mat4 light" + i + "_shadowMatrix;\n";
                code += "uniform vec3 light" + i + "_shadowParams;\n"; // Width, height, bias
                code += "uniform sampler2D light" + i + "_shadowMap;\n";
                numShadowLights++;
            }
        }

        switch (options.fog) {
            case 'linear':
                code += getSnippet(device, 'fs_fog_linear_decl');
                break;
            case 'exp':
                code += getSnippet(device, 'fs_fog_exp_decl');
                break;
            case 'exp2':
                code += getSnippet(device, 'fs_fog_exp2_decl');
                break;
        }

        if (options.alphaTest) {
            code += getSnippet(device, 'fs_alpha_test_decl');
        }

        code += "\n"; // End of uniform declarations

        var uvOffset = options.heightMap? " + data.uvOffset" : "";

        if (options.normalMap && useTangents) {
            code += chunks.normalMapPS.replace(/\$UV/g, this._uvSource("normal")+uvOffset);
            code += chunks.TBNPS;
        } else {
            code += chunks.normalVertexPS
        }

        code += chunks.defaultGamma;

        if (options.diffuseMap) {
            code += chunks.albedoTexPS.replace(/\$UV/g, this._uvSource("diffuse")+uvOffset);
        } else {
            code += chunks.albedoColorPS;
        }

        if (options.useSpecular) {
            if (options.specularMap) {
                code += chunks.specularityTexPS.replace(/\$UV/g, this._uvSource("specular")+uvOffset);
            } else {
                code += chunks.specularityColorPS;
            }
            if (options.useFresnel) {
                code += chunks.defaultFresnel;
            }
        }

        if (options.glossMap) {
            code += chunks.glossinessTexPS.replace(/\$UV/g, this._uvSource("gloss")+uvOffset);
        } else {
            code += chunks.glossinessFloatPS;
        }

        if (options.opacityMap) {
            code += chunks.opacityTexPS.replace(/\$UV/g, this._uvSource("opacity")+uvOffset);
        } else {
            code += chunks.opacityFloatPS;
        }

        if (options.emissiveMap) {
            code += chunks.emissionTexPS.replace(/\$UV/g, this._uvSource("emissive")+uvOffset);
        } else {
            code += chunks.emissionColorPS;
        }

        if (options.heightMap) {
            if (!options.normalMap) code += chunks.TBNPS;
            code += chunks.parallaxPS.replace(/\$UV/g, this._uvSource("height"));
        }

        if (options.cubeMap) {
            code += chunks.reflectionCubePS;
        }

        if (options.sphereMap) {
            code += device.fragmentUniformsCount>16? chunks.reflectionSpherePS : chunks.reflectionSphereLowPS;
        }

        if (options.lightMap) {
            code += chunks.lightmapSinglePS;
        }

        if (numShadowLights > 0) {
            code += chunks.shadowPS;
        }

        code += chunks.lightDiffuseLambertPS;
        if (options.useSpecular) {
            code += chunks.defaultSpecular;
            if (options.sphereMap || options.cubeMap || options.useFresnel) {
                if (options.useFresnel) {
                    code += chunks.combineDiffuseSpecularPS; // this one is correct, others are old stuff
                } else {
                    code += chunks.combineDiffuseSpecularOldPS;
                }
            } else {
                if (options.diffuseMap) {
                    code += chunks.combineDiffuseSpecularNoReflPS;
                } else {
                    code += chunks.combineDiffuseSpecularNoReflSeparateAmbientPS;
                }
            }
        } else {
            code += chunks.combineDiffusePS;
        }

        // FRAGMENT SHADER BODY
        code += chunks.startPS;

        if (lighting) {
            code += "   getViewDir(data);\n";
            if (options.heightMap || options.normalMap) {
                code += "   getTBN(data);\n";
            }
            if (options.heightMap) {
                code += "   getParallax(data);\n"
            }
            code += "   getNormal(data);\n";
            if (options.useSpecular) code += "   getReflDir(data);\n";
        }

        code += "   getAlbedo(data);\n";

        if (lighting && options.useSpecular) {
            code += "   getSpecularity(data);\n";
            if (options.useFresnel) code += "   getFresnel(data);\n";
            code += "   getGlossiness(data);\n";
        }

        code += "   getOpacity(data);\n";
        if (options.alphaTest) {
            code += "   if (data.alpha < alpha_ref) discard;"
        }

        if (options.lightMap) {
                code += "    addLightmap(data);\n";
         }

        code += "   addAmbientConstant(data);\n";
        if (lighting) {
            if (options.cubeMap) {
                code += "   addCubemapReflection(data);\n";
            } else if (options.sphereMap) {
                code += "   addSpheremapReflection(data);\n";
            }

            for (i = 0; i < options.numLights; i++) {
                // The following code is not decoupled to separate shader files, because most of it can be actually changed to achieve different behaviours like:
                // - different falloffs
                // - different shadow coords (point shadows will use drastically different genShadowCoord)
                // - different shadow filter modes

                // getLightDiffuse and getLightSpecular is BRDF (currently lame Lambert+Phong) itself.

                if (options.lightType[i]==pc.scene.LIGHTTYPE_DIRECTIONAL) {
                    // directional
                    code += "   data.lightDirNormW = light"+i+"_direction;\n";
                    code += "   data.atten = 1.0;\n";
                } else {
                    code += "   getLightDirPoint(data, light"+i+"_position);\n";
                    code += "   data.atten = getFalloffLinear(data, light"+i+"_radius);\n";
                    if (options.lightType[i]==pc.scene.LIGHTTYPE_SPOT) {
                        code += "   data.atten *= getSpotEffect(data, light"+i+"_spotDirection, light"+i+"_innerConeAngle, light"+i+"_outerConeAngle);\n";
                    }
                }

                code += "   data.atten *= getLightDiffuse(data);\n";
                if (options.lightShadow[i]) {
                    code += "   getShadowCoord(data, light"+i+"_shadowMatrix, light"+i+"_shadowParams);\n";
                    code += "   data.atten *= getShadowPCF3x3(data, light"+i+"_shadowMap, light"+i+"_shadowParams);\n";
                }

                code += "   data.diffuseLight += data.atten * light"+i+"_color;\n";

                if (options.useSpecular) {
                    code += "   data.atten *= getLightSpecular(data);\n"
                    code += "   data.specularLight += data.atten * light"+i+"_color;\n";
                }
                code += "\n";
            }
        }
        code += "\n";
        code += chunks.endPS;

        // Fog
        switch (options.fog) {
            case 'linear':
                code += getSnippet(device, 'fs_fog_linear');
                break;
            case 'exp':
                code += getSnippet(device, 'fs_fog_exp');
                break;
            case 'exp2':
                code += getSnippet(device, 'fs_fog_exp2');
                break;
        }

        // Make sure all components are between 0 and 1
        code += getSnippet(device, 'fs_clamp');

        code += getSnippet(device, 'common_main_end');

        var fshader = code;

        return {
            attributes: attributes,
            vshader: vshader,
            fshader: fshader
        };
    }
};
<|endoftext|>