<commit_before>
$('window').ready(function(){

  var f = new Date();
  var MonthNames = ["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"];
  var DayNames = ["Lun","Mar","Mie","Jue","Vie","Sab","Dom"];

  //Viajes en el ultimo mes
	var posDias = 1;
	var dias = new Array();
	var diasMes = new Array();

	$.getJSON('/statistics/gettravelsbylastmonth/', function(json) {
    $.each( json, function( key, val ) {
			diasMes[posDias] = val;
			dias[posDias] = key;
			posDias = posDias + 1;
		});

		var lineChartData = {
     	labels : [dias[1],dias[2],dias[3],dias[4],dias[5],dias[6],
           			dias[7],dias[8],dias[9],dias[10],dias[11],dias[12],
          			dias[13],dias[14],dias[15],dias[16],dias[17],dias[18],
          			dias[19],dias[20],dias[21],dias[22],dias[23],dias[24],
          			dias[25],dias[26],dias[27],dias[28],dias[29],dias[30],dias[31]
               ],
      datasets : [{
        fillColor : "rgba(120,120,120,0.5)",
        strokeColor : "rgba(120,120,120,1)",
        pointColor : "rgba(120,120,120,1)",
        pointStrokeColor : "#fff",
  			data : [diasMes[1],diasMes[2],diasMes[3],diasMes[4],diasMes[5],diasMes[6],
            		diasMes[7],diasMes[8],diasMes[9],diasMes[10],diasMes[11],diasMes[12],
            		diasMes[13],diasMes[14],diasMes[15],diasMes[16],diasMes[17],diasMes[18],
            		diasMes[19],diasMes[20],diasMes[21],diasMes[22],diasMes[23],diasMes[24],
            		diasMes[25],diasMes[26],diasMes[27],diasMes[28],diasMes[29],diasMes[30],diasMes[31]
               ]
			},]
	  };

		var options = {
			scaleShowGridLines: true,
			scaleOverride: true,
			scaleSteps: 2,
			scaleStepWidth: 5,
			scaleStartValue: 0,
      scaleLineColor: "rgba(0.9,0.2,0,.1)",
			scaleLineWidth: 0,
			scaleShowLabels: true,
			scaleFontColor: "#000",
			barDatasetSpacing: 2,
			barStrokeWidth: 2
		};

		new Chart(document.getElementById("travelsLastMonth").getContext("2d")).Line(lineChartData,options);

  });

  //Viajes en el último año
	var posMes = 1;
	var meses = new Array();
	var viajesUYear = new Array();

	$.getJSON('/statistics/gettravelsbylastyear/', function(json) {
    $.each( json, function( key, val ) {
			viajesUYear[posMes] = val;
			meses[posMes] = key;
			posMes = posMes + 1;
  	});

    var labels = [];
    var data = [];
    var j = f.getMonth()+1;

    for (var i=0; i<12; i++) {
      labels[i] = MonthNames[j];
      data[i] = viajesUYear[j+1];
      j++;
      if (j == 12) {
        j = 0;
      }
    }

		var lineChartData = {
     	labels : labels,
			datasets : [ {
        fillColor : "rgba(120,120,120,0.5)",
        strokeColor : "rgba(120,120,120,1)",
        pointColor : "rgba(120,120,120,1)",
        pointStrokeColor : "#fff",
				data : data
			}, ]
    };

		var options = {
  		scaleShowGridLines: true,
  		scaleOverride: true,
  		scaleSteps: 2,
  		scaleStepWidth: 5,
  		scaleStartValue: 0,
      scaleLineColor: "rgba(0.9,0.2,0,.1)",
  		scaleLineWidth: 0,
  		scaleShowLabels: true,
  		scaleFontColor: "#000",
  		barDatasetSpacing: 2,
  		barStrokeWidth: 2
  	};

		new Chart(document.getElementById("travelsLastYear").getContext("2d")).Line(lineChartData, options);

	});

  //Viajes por mes
  var viajesMes = new Array();
  var pos = 1;

  $.getJSON('/statistics/gettravelsbymonth/', function(json) {
    $.each( json, function( key, val ) {
      viajesMes[pos] = val;
      pos = pos + 1;
    });

    var dataMonth = []
    for (var i=0; i<13; i++) {
      dataMonth[i] = viajesMes[i+1];
    }

    var barChartData = {
      labels : MonthNames,
      datasets : [{
        fillColor : "rgba(120,120,120,0.5)",
        strokeColor : "rgba(120,120,120,1)",
        pointColor : "rgba(120,120,120,1)",
        pointStrokeColor : "#fff",
        data : dataMonth
      },]
    };

    var options = {
      scaleShowGridLines: true,
      scaleOverride: true,
      scaleSteps: 4,
      scaleStepWidth: 25,
      scaleStartValue: 0,
      scaleLineColor: "rgba(0.9,0.2,0,.1)",
      scaleLineWidth: 0,
      scaleShowLabels: true,
      scaleFontColor: "#000",
      barDatasetSpacing: 2,
      barStrokeWidth: 2
    };

    new Chart(document.getElementById("travelsByMonth").getContext("2d")).Bar(barChartData, options);

  });

  //Viajes por dia
  var posi = 0;
	var viajesDias = new Array();

	$.getJSON('/statistics/gettravelsbyday/', function(json) {
    $.each( json, function( key, val ) {
			viajesDias[posi] = val;
			posi = posi + 1;
		});

    var dataDaysWeek = []
    for (var i=0; i<7; i++) {
      dataDaysWeek[i] = viajesDias[i];
    }

  	var barChartData = {
     	labels : DayNames,
			datasets : [{
  			fillColor : "rgba(120,120,120,0.5)",
  			strokeColor : "rgba(120,120,120,1)",
  			pointColor : "rgba(120,120,120,1)",
  			pointStrokeColor : "#fff",
				data : dataDaysWeek
			}, ]
  	};

  	var options = {
  		scaleShowGridLines: true,
  		scaleOverride: true,
  		scaleSteps: 4,
  		scaleStepWidth: 25,
  		scaleStartValue: 0,
  		scaleLineColor: "rgba(0.9,0.2,0,.1)",
  		scaleLineWidth: 0,
  		scaleShowLabels: true,
  		scaleFontColor: "#000",
  		barDatasetSpacing: 2,
  		barStrokeWidth: 2
  	};

  	new Chart(document.getElementById("travelsByDay").getContext("2d")).Bar(barChartData, options);

  });

});

/*
var doughnutData = [
				{
					value: 30,
					color:"#F7464A"
				},
				{
					value : 50,
					color : "#46BFBD"
				},
				{
					value : 100,
					color : "#FDB45C"
				},
				{
					value : 40,
					color : "#949FB1"
				},
				{
					value : 120,
					color : "#4D5360"
				}

			];

*/
<commit_msg>debug<commit_after>
$('window').ready(function(){

  var f = new Date();
  var MonthNames = ["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"];
  var DayNames = ["Lun","Mar","Mie","Jue","Vie","Sab","Dom"];

  //Viajes en el ultimo mes
	var posDias = 1;
	var diasMes = new Array();

	$.getJSON('/statistics/gettravelsbylastmonth/', function(json) {
    $.each( json, function( key, val ) {
			diasMes[posDias] = val;
			posDias = posDias + 1;
		});

    var dias = []
    var dataYear = []
    var l = 0;
    var k = new Date();
    var today = f.getDate();
    console.log("entro");
    while ( (k.getDate()-1) != today ){
      console.log k.getDate();
      dias[l] = k.getDate();
      dataYear[l] = diasMes[k.getDate()];
      k = new Date(k.getTime()-(86400000))
      l++;
    }

		var lineChartData = {
     	labels : dias,
      datasets : [{
        fillColor : "rgba(120,120,120,0.5)",
        strokeColor : "rgba(120,120,120,1)",
        pointColor : "rgba(120,120,120,1)",
        pointStrokeColor : "#fff",
  			data : dataYear
			},]
	  };

		var options = {
			scaleShowGridLines: true,
			scaleOverride: true,
			scaleSteps: 2,
			scaleStepWidth: 5,
			scaleStartValue: 0,
      scaleLineColor: "rgba(0.9,0.2,0,.1)",
			scaleLineWidth: 0,
			scaleShowLabels: true,
			scaleFontColor: "#000",
			barDatasetSpacing: 2,
			barStrokeWidth: 2
		};

		new Chart(document.getElementById("travelsLastMonth").getContext("2d")).Line(lineChartData,options);

  });

  //Viajes en el último año
	var posMes = 1;
	var meses = new Array();
	var viajesUYear = new Array();

	$.getJSON('/statistics/gettravelsbylastyear/', function(json) {
    $.each( json, function( key, val ) {
			viajesUYear[posMes] = val;
			meses[posMes] = key;
			posMes = posMes + 1;
  	});

    var labels = [];
    var data = [];
    var j = f.getMonth()+1;
    var max = 0;

    for (var i=0; i<12; i++) {
      labels[i] = MonthNames[j];
      data[i] = viajesUYear[j+1];
      if ((viajesUYear[j+1])>max){
        max = viajesUYear[j+1]
      }
      j++;
      if (j == 12) {
        j = 0;
      }
    }

    var steps = (Math.round(max/5))+1;

		var lineChartData = {
     	labels : labels,
			datasets : [ {
        fillColor : "rgba(120,120,120,0.5)",
        strokeColor : "rgba(120,120,120,1)",
        pointColor : "rgba(120,120,120,1)",
        pointStrokeColor : "#fff",
				data : data
			}, ]
    };

		var options = {
  		scaleShowGridLines: true,
  		scaleOverride: true,
  		scaleSteps: steps,
  		scaleStepWidth: 5,
  		scaleStartValue: 0,
      scaleLineColor: "rgba(0.9,0.2,0,.1)",
  		scaleLineWidth: 0,
  		scaleShowLabels: true,
  		scaleFontColor: "#000",
  		barDatasetSpacing: 2,
  		barStrokeWidth: 2
  	};

		new Chart(document.getElementById("travelsLastYear").getContext("2d")).Line(lineChartData, options);

	});

  //Viajes por mes
  var viajesMes = new Array();
  var pos = 1;

  $.getJSON('/statistics/gettravelsbymonth/', function(json) {
    $.each( json, function( key, val ) {
      viajesMes[pos] = val;
      pos = pos + 1;
    });

    var dataMonth = []
    for (var i=0; i<13; i++) {
      dataMonth[i] = viajesMes[i+1];
    }

    var barChartData = {
      labels : MonthNames,
      datasets : [{
        fillColor : "rgba(120,120,120,0.5)",
        strokeColor : "rgba(120,120,120,1)",
        pointColor : "rgba(120,120,120,1)",
        pointStrokeColor : "#fff",
        data : dataMonth
      },]
    };

    var options = {
      scaleShowGridLines: true,
      scaleOverride: true,
      scaleSteps: 4,
      scaleStepWidth: 25,
      scaleStartValue: 0,
      scaleLineColor: "rgba(0.9,0.2,0,.1)",
      scaleLineWidth: 0,
      scaleShowLabels: true,
      scaleFontColor: "#000",
      barDatasetSpacing: 2,
      barStrokeWidth: 2
    };

    new Chart(document.getElementById("travelsByMonth").getContext("2d")).Bar(barChartData, options);

  });

  //Viajes por dia
  var posi = 0;
	var viajesDias = new Array();

	$.getJSON('/statistics/gettravelsbyday/', function(json) {
    $.each( json, function( key, val ) {
			viajesDias[posi] = val;
			posi = posi + 1;
		});

    var dataDaysWeek = []
    for (var i=0; i<7; i++) {
      dataDaysWeek[i] = viajesDias[i];
    }

  	var barChartData = {
     	labels : DayNames,
			datasets : [{
  			fillColor : "rgba(120,120,120,0.5)",
  			strokeColor : "rgba(120,120,120,1)",
  			pointColor : "rgba(120,120,120,1)",
  			pointStrokeColor : "#fff",
				data : dataDaysWeek
			}, ]
  	};

  	var options = {
  		scaleShowGridLines: true,
  		scaleOverride: true,
  		scaleSteps: 4,
  		scaleStepWidth: 25,
  		scaleStartValue: 0,
  		scaleLineColor: "rgba(0.9,0.2,0,.1)",
  		scaleLineWidth: 0,
  		scaleShowLabels: true,
  		scaleFontColor: "#000",
  		barDatasetSpacing: 2,
  		barStrokeWidth: 2
  	};

  	new Chart(document.getElementById("travelsByDay").getContext("2d")).Bar(barChartData, options);

  });

});

/*
var doughnutData = [
				{
					value: 30,
					color:"#F7464A"
				},
				{
					value : 50,
					color : "#46BFBD"
				},
				{
					value : 100,
					color : "#FDB45C"
				},
				{
					value : 40,
					color : "#949FB1"
				},
				{
					value : 120,
					color : "#4D5360"
				}

			];

*/
<|endoftext|>


<commit_before>/**
 * states.js
 * Created by anonymous on 09/12/15 13:12.
 */

(function() {
    'use strict';

    angular
        .module('client')
        .config(clientRouter);

    clientRouter.$inject = ['$stateProvider', '$urlRouterProvider'];

    /* @ngInject */
    function clientRouter($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise('/');

        $stateProvider
            .state('client', {
                abstract: true,
                views   : {
                    'layout@'      : {
                        templateUrl : layout('client.simple'),
                        controller  : 'ClientController',
                        controllerAs: 'client'
                    },
                    'header@client': {
                        templateUrl : view('client.header'),
                        controller  : 'ClientHeaderController',
                        controllerAs: 'header'
                    },
                    'sidenav@client'  : {
                        templateUrl : view('client.sidenav'),
                        controller  : 'ClientSidenavController',
                        controllerAs: 'sidenav'
                    },
                    'aside@client' : {
                        templateUrl : view('client.aside'),
                        controller  : 'ClientAsideController',
                        controllerAs: 'aside'
                    },
                    'footer@client': {
                        templateUrl : view('client.footer'),
                        controller  : 'ClientFooterController',
                        controllerAs: 'footer'
                    },
                    'main@client'  : {}
                }
            })
            .state('client.home', {
                url  : '/',
                data : {pageName: 'Homepage'},
                views: {
                    'main@client': {
                        templateUrl : view('client.home'),
                        controller  : 'ClientHomeController',
                        controllerAs: 'home'
                    }
                }
            })
            .state('staticpage', {
                abstract: true,
                url     : '/page',
                views   : {
                    'layout@'          : {
                        templateUrl : layout('staticpage.simple'),
                        controller  : 'StaticpageController',
                        controllerAs: 'staticpage'
                    },
                    'header@staticpage': {
                        templateUrl : view('staticpage.header'),
                        controller  : 'StaticpageHeaderController',
                        controllerAs: 'header'
                    },
                    'sidenav@staticpage'  : {
                        templateUrl : view('staticpage.sidenav'),
                        controller  : 'StaticpageSidenavController',
                        controllerAs: 'sidenav'
                    },
                    'aside@staticpage' : {
                        templateUrl : view('staticpage.aside'),
                        controller  : 'StaticpageAsideController',
                        controllerAs: 'aside'
                    },
                    'footer@staticpage': {
                        templateUrl : view('staticpage.footer'),
                        controller  : 'StaticpageFooterController',
                        controllerAs: 'footer'
                    },
                    'main@staticpage'  : {}
                }
            })
            .state('staticpage.landing', {
                url  : '/',
                data : {pageName: 'Landing page'},
                views: {
                    'main@staticpage': {
                        templateUrl : view('staticpage.landing'),
                        controller  : 'StaticpageLandingController',
                        controllerAs: 'landing'
                    }
                }
            });

        function dashboard(viewName) {
            if (viewName !== '') {
                return './views/dashboard/' + appName(viewName) + '/' + fileDir(viewName) + '/' + fileName(viewName) + '.html';
            } else {
                return './views/app/app/home/home.html';
            }
        }

        function view(viewName) {
            if (viewName !== '') {
                return './views/app/' + appName(viewName) + '/' + fileDir(viewName) + '/' + fileName(viewName) + '.html';
            } else {
                return './views/app/app/home/home.html';
            }
        }

        function layout(viewName) {
            if (viewName !== '') {
                return './views/layouts/' + appName(viewName) + '/' + fileDir(viewName) + '/' + fileName(viewName) + '.html';
            } else {
                return './views/app/app/home/home.html';
            }
        }

        function appName(v) {
            if (v.split('.')[0]) {
                return v.split('.')[0];
            } else {
                return 'app';
            }
        }

        function fileDir(v) {
            if (v.split('.')[1]) {
                return v.split('.')[1];
            } else if (!v.split('.')[0]) {
                return v;
            } else {
                return 'home';
            }
        }

        function fileName(v) {
            if (v.split('.')[2]) {
                return v.split('.')[2];
            } else if (!v.split('.')[2]) {
                if (v.split('.')[1]) {
                    return v.split('.')[1];
                }
            } else {
                return 'home';
            }
        }

    }

})();

<commit_msg>Modified states<commit_after>/**
 * states.js
 * Created by anonymous on 09/12/15 13:12.
 */

(function() {
    'use strict';

    angular
        .module('client')
        .config(clientRouter);

    clientRouter.$inject = ['$stateProvider', '$urlRouterProvider', 'layoutProvider'];

    /* @ngInject */
    function clientRouter($stateProvider, $urlRouterProvider, layoutProvider) {
        $urlRouterProvider.otherwise('/');

        $stateProvider
            .state('client', {
                abstract: true,
                views   : {
                    'layout@'       : {
                        templateUrl : layoutProvider.layout('themes.minimalist'),
                        controller  : 'ClientController',
                        controllerAs: 'client'
                    },
                    'header@client' : {
                        templateUrl : layoutProvider.view('client.header'),
                        controller  : 'ClientHeaderController',
                        controllerAs: 'header'
                    },
                    'sidenav@client': {
                        templateUrl : layoutProvider.view('client.sidenav'),
                        controller  : 'ClientSidenavController',
                        controllerAs: 'sidenav'
                    },
                    'aside@client'  : {
                        templateUrl : layoutProvider.view('client.aside'),
                        controller  : 'ClientAsideController',
                        controllerAs: 'aside'
                    },
                    'footer@client' : {
                        templateUrl : layoutProvider.view('client.footer'),
                        controller  : 'ClientFooterController',
                        controllerAs: 'footer'
                    },
                    'main@client'   : {}
                }
            })
            .state('client.home', {
                url  : '/',
                data : {pageName: 'Homepage'},
                views: {
                    'main@client': {
                        templateUrl : layoutProvider.view('client.home'),
                        controller  : 'ClientHomeController',
                        controllerAs: 'home'
                    }
                }
            })
            .state('staticpage', {
                abstract: true,
                url     : '/page',
                views   : {
                    'layout@'           : {
                        templateUrl : layoutProvider.layout('staticpage.simple'),
                        controller  : 'StaticpageController',
                        controllerAs: 'staticpage'
                    },
                    'header@staticpage' : {
                        templateUrl : layoutProvider.view('staticpage.header'),
                        controller  : 'StaticpageHeaderController',
                        controllerAs: 'header'
                    },
                    'sidenav@staticpage': {
                        templateUrl : layoutProvider.view('staticpage.sidenav'),
                        controller  : 'StaticpageSidenavController',
                        controllerAs: 'sidenav'
                    },
                    'aside@staticpage'  : {
                        templateUrl : layoutProvider.view('staticpage.aside'),
                        controller  : 'StaticpageAsideController',
                        controllerAs: 'aside'
                    },
                    'footer@staticpage' : {
                        templateUrl : layoutProvider.view('staticpage.footer'),
                        controller  : 'StaticpageFooterController',
                        controllerAs: 'footer'
                    },
                    'main@staticpage'   : {}
                }
            })
            .state('staticpage.landing', {
                url  : '/',
                data : {pageName: 'Landing page'},
                views: {
                    'main@staticpage': {
                        templateUrl : layoutProvider.view('staticpage.landing'),
                        controller  : 'StaticpageLandingController',
                        controllerAs: 'landing'
                    }
                }
            });

    }

})();

<|endoftext|>


<commit_before>var SortedArray = require('sorted-array-functions')
var Value = require('@mmckegg/mutant/value')
var MutantMap = require('@mmckegg/mutant/map')
var h = require('@mmckegg/mutant/html-element')
var when = require('@mmckegg/mutant/when')
var m = require('../lib/h')

var pull = require('pull-stream')

var plugs = require('patchbay/plugs')
var message_render = plugs.first(exports.message_render = [])
var message_compose = plugs.first(exports.message_compose = [])
var sbot_log = plugs.first(exports.sbot_log = [])
var avatar_name = plugs.first(exports.avatar_name = [])
var avatar_link = plugs.first(exports.avatar_link = [])
var message_link = plugs.first(exports.message_link = [])

exports.screen_view = function (path, sbot) {
  if (path === '/public') {
    var sync = Value(false)
    var events = Value([])
    var updates = Value(0)

    var updateLoader = m('a', {
      href: '#',
      style: {
        'padding': '10px',
        'display': 'block',
        'background': '#d6e4ec',
        'border': '1px solid #bbc9d2',
        'text-align': 'center'
      },
      'ev-click': refresh
    }, [ 'Load ', h('strong', [updates]), ' update(s)' ])

    var content = h('div.column.scroller__content', [
      when(updates, updateLoader),
      MutantMap(events, (group) => {
        if (group.type === 'message') {
          var meta = null
          var replies = group.replies.slice(-3).map(message_render)
          var renderedMessage = group.message ? message_render(group.message) : null
          if (renderedMessage) {
            if (group.lastUpdateType === 'reply') {
              meta = m('div.meta', [
                manyPeople(group.repliesFrom), ' replied'
              ])
            } else if (group.lastUpdateType === 'dig') {
              meta = m('div.meta', [
                manyPeople(group.digs), ' dug this message'
              ])
            }

            return m('FeedEvent', [
              meta,
              renderedMessage,
              when(replies.length, [
                when(group.replies > replies.length,
                  m('a.full', {href: `#${group.messageId}`}, ['View full thread'])
                ),
                m('div.replies', replies)
              ])
            ])
          } else {
            if (group.lastUpdateType === 'reply') {
              meta = m('div.meta', [
                manyPeople(group.repliesFrom), ' replied to ', message_link(group.messageId)
              ])
            } else if (group.lastUpdateType === 'dig') {
              meta = m('div.meta', [
                manyPeople(group.digs), ' dug ', message_link(group.messageId)
              ])
            }

            if (meta || replies.length) {
              return m('FeedEvent', [
                meta, m('div.replies', replies)
              ])
            }
          }
        } else if (group.type === 'follow') {
          return m('FeedEvent -follow', [
            m('div.meta', [
              person(group.id), ' followed ', manyPeople(group.contacts)
            ])
          ])
        }
      }, {maxTime: 5})
    ])

    var div = h('div.column.scroller', {
      style: {
        'overflow': 'auto'
      }
    }, [
      h('div.scroller__wrapper', [
        message_compose({type: 'post'}, {placeholder: 'Write a public message'}),
        content
      ])
    ])

    refresh()

    pull(
      sbot_log({old: false}),
      pull.drain((item) => {
        updates.set(updates() + 1)
      })
    )

    // pull(
    //   u.next(sbot_log, {reverse: true, limit: 100, live: false}),
    //   Scroller(div, content, message_render, false, false)
    // )

    return div

    // scoped

    function refresh () {
      pull(
        sbot_log({reverse: true, limit: 500, live: false}),
        pull.collect((err, values) => {
          if (err) throw err
          events.set(groupMessages(values))
          sync.set(true)
          updates.set(0)
        })
      )
    }
  }
}

function person (id) {
  return avatar_link(id, avatar_name(id), '')
}

function manyPeople (ids) {
  ids = Array.from(ids)
  var featuredIds = ids.slice(-3).reverse()

  if (ids.length > 3) {
    return [
      person(featuredIds[0]), ', ',
      person(featuredIds[1]),
      ' and ', ids.length - 2, ' others'
    ]
  } else if (ids.length === 3) {
    return [
      person(featuredIds[0]), ', ',
      person(featuredIds[1]), ' and ',
      person(featuredIds[2])
    ]
  } else if (ids.length === 2) {
    return [
      person(featuredIds[0]), ' and ',
      person(featuredIds[1])
    ]
  } else {
    return person(featuredIds[0])
  }
}

function groupMessages (messages) {
  var follows = {}
  var messageUpdates = {}
  reverseForEach(messages, function (msg) {
    var c = msg.value.content
    if (c.type === 'contact') {
      updateContact(msg, follows)
    } else if (c.type === 'vote') {
      if (c.vote && c.vote.link) {
        const group = ensureMessage(c.root, messageUpdates)
        if (c.vote.value > 1) {
          group.lastUpdateType = 'vote'
          group.digs.add(msg.value.author)
          group.updated = msg.timestamp
        } else {
          group.digs.delete(msg.value.author)
        }
      }
    } else {
      if (c.root) {
        const group = ensureMessage(c.root, messageUpdates)
        group.lastUpdateType = 'reply'
        group.repliesFrom.add(msg.value.author)
        group.replies.push(msg)
        group.updated = msg.timestamp
      } else {
        const group = ensureMessage(msg.key, messageUpdates)
        group.lastUpdateType = 'post'
        group.updated = msg.timestamp
        group.message = msg
      }
    }
  })

  var result = []
  Object.keys(follows).forEach((key) => {
    SortedArray.add(result, follows[key], compareUpdated)
  })
  Object.keys(messageUpdates).forEach((key) => {
    SortedArray.add(result, messageUpdates[key], compareUpdated)
  })
  return result
}

function compareUpdated (a, b) {
  return b.updated - a.updated
}

function reverseForEach (items, fn) {
  var i = items.length - 1
  var start = Date.now()
  nextBatch()

  function nextBatch () {
    while (i >= 0 && Date.now() - start < 10) {
      fn(items[i], i)
      i -= 1
    }

    if (i > 0) {
      setImmediate(nextBatch)
    }
  }
}

function updateContact (msg, groups) {
  var c = msg.value.content
  var id = msg.value.author
  var group = groups[id]
  if (c.following) {
    if (!group) {
      group = groups[id] = {
        type: 'follow',
        contacts: new Set(),
        updated: 0,
        id: id
      }
    }
    group.contacts.add(c.contact)
    group.updated = msg.timestamp
  } else {
    if (group) {
      group.contacts.delete(c.contact)
      if (!group.contacts.size) {
        delete groups[id]
      }
    }
  }
}

function ensureMessage (id, groups) {
  var group = groups[id]
  if (!group) {
    group = groups[id] = {
      type: 'message',
      repliesFrom: new Set(),
      replies: [],
      message: null,
      messageId: id,
      digs: new Set(),
      updated: 0
    }
  }
  return group
}
<commit_msg>fix displaying of "view full thread" button<commit_after>var SortedArray = require('sorted-array-functions')
var Value = require('@mmckegg/mutant/value')
var MutantMap = require('@mmckegg/mutant/map')
var h = require('@mmckegg/mutant/html-element')
var when = require('@mmckegg/mutant/when')
var m = require('../lib/h')

var pull = require('pull-stream')

var plugs = require('patchbay/plugs')
var message_render = plugs.first(exports.message_render = [])
var message_compose = plugs.first(exports.message_compose = [])
var sbot_log = plugs.first(exports.sbot_log = [])
var avatar_name = plugs.first(exports.avatar_name = [])
var avatar_link = plugs.first(exports.avatar_link = [])
var message_link = plugs.first(exports.message_link = [])

exports.screen_view = function (path, sbot) {
  if (path === '/public') {
    var sync = Value(false)
    var events = Value([])
    var updates = Value(0)

    var updateLoader = m('a', {
      href: '#',
      style: {
        'padding': '10px',
        'display': 'block',
        'background': '#d6e4ec',
        'border': '1px solid #bbc9d2',
        'text-align': 'center'
      },
      'ev-click': refresh
    }, [ 'Load ', h('strong', [updates]), ' update(s)' ])

    var content = h('div.column.scroller__content', [
      when(updates, updateLoader),
      MutantMap(events, (group) => {
        if (group.type === 'message') {
          var meta = null
          var replies = group.replies.slice(-3).map(message_render)
          var renderedMessage = group.message ? message_render(group.message) : null
          if (renderedMessage) {
            if (group.lastUpdateType === 'reply') {
              meta = m('div.meta', [
                manyPeople(group.repliesFrom), ' replied'
              ])
            } else if (group.lastUpdateType === 'dig') {
              meta = m('div.meta', [
                manyPeople(group.digs), ' dug this message'
              ])
            }

            return m('FeedEvent', [
              meta,
              renderedMessage,
              when(replies.length, [
                when(group.replies.length > replies.length,
                  m('a.full', {href: `#${group.messageId}`}, ['View full thread'])
                ),
                m('div.replies', replies)
              ])
            ])
          } else {
            if (group.lastUpdateType === 'reply') {
              meta = m('div.meta', [
                manyPeople(group.repliesFrom), ' replied to ', message_link(group.messageId)
              ])
            } else if (group.lastUpdateType === 'dig') {
              meta = m('div.meta', [
                manyPeople(group.digs), ' dug ', message_link(group.messageId)
              ])
            }

            if (meta || replies.length) {
              return m('FeedEvent', [
                meta, m('div.replies', replies)
              ])
            }
          }
        } else if (group.type === 'follow') {
          return m('FeedEvent -follow', [
            m('div.meta', [
              person(group.id), ' followed ', manyPeople(group.contacts)
            ])
          ])
        }
      }, {maxTime: 5})
    ])

    var div = h('div.column.scroller', {
      style: {
        'overflow': 'auto'
      }
    }, [
      h('div.scroller__wrapper', [
        message_compose({type: 'post'}, {placeholder: 'Write a public message'}),
        content
      ])
    ])

    refresh()

    pull(
      sbot_log({old: false}),
      pull.drain((item) => {
        updates.set(updates() + 1)
      })
    )

    // pull(
    //   u.next(sbot_log, {reverse: true, limit: 100, live: false}),
    //   Scroller(div, content, message_render, false, false)
    // )

    return div

    // scoped

    function refresh () {
      pull(
        sbot_log({reverse: true, limit: 500, live: false}),
        pull.collect((err, values) => {
          if (err) throw err
          events.set(groupMessages(values))
          sync.set(true)
          updates.set(0)
        })
      )
    }
  }
}

function person (id) {
  return avatar_link(id, avatar_name(id), '')
}

function manyPeople (ids) {
  ids = Array.from(ids)
  var featuredIds = ids.slice(-3).reverse()

  if (ids.length > 3) {
    return [
      person(featuredIds[0]), ', ',
      person(featuredIds[1]),
      ' and ', ids.length - 2, ' others'
    ]
  } else if (ids.length === 3) {
    return [
      person(featuredIds[0]), ', ',
      person(featuredIds[1]), ' and ',
      person(featuredIds[2])
    ]
  } else if (ids.length === 2) {
    return [
      person(featuredIds[0]), ' and ',
      person(featuredIds[1])
    ]
  } else {
    return person(featuredIds[0])
  }
}

function groupMessages (messages) {
  var follows = {}
  var messageUpdates = {}
  reverseForEach(messages, function (msg) {
    var c = msg.value.content
    if (c.type === 'contact') {
      updateContact(msg, follows)
    } else if (c.type === 'vote') {
      if (c.vote && c.vote.link) {
        const group = ensureMessage(c.root, messageUpdates)
        if (c.vote.value > 1) {
          group.lastUpdateType = 'vote'
          group.digs.add(msg.value.author)
          group.updated = msg.timestamp
        } else {
          group.digs.delete(msg.value.author)
        }
      }
    } else {
      if (c.root) {
        const group = ensureMessage(c.root, messageUpdates)
        group.lastUpdateType = 'reply'
        group.repliesFrom.add(msg.value.author)
        group.replies.push(msg)
        group.updated = msg.timestamp
      } else {
        const group = ensureMessage(msg.key, messageUpdates)
        group.lastUpdateType = 'post'
        group.updated = msg.timestamp
        group.message = msg
      }
    }
  })

  var result = []
  Object.keys(follows).forEach((key) => {
    SortedArray.add(result, follows[key], compareUpdated)
  })
  Object.keys(messageUpdates).forEach((key) => {
    SortedArray.add(result, messageUpdates[key], compareUpdated)
  })
  return result
}

function compareUpdated (a, b) {
  return b.updated - a.updated
}

function reverseForEach (items, fn) {
  var i = items.length - 1
  var start = Date.now()
  nextBatch()

  function nextBatch () {
    while (i >= 0 && Date.now() - start < 10) {
      fn(items[i], i)
      i -= 1
    }

    if (i > 0) {
      setImmediate(nextBatch)
    }
  }
}

function updateContact (msg, groups) {
  var c = msg.value.content
  var id = msg.value.author
  var group = groups[id]
  if (c.following) {
    if (!group) {
      group = groups[id] = {
        type: 'follow',
        contacts: new Set(),
        updated: 0,
        id: id
      }
    }
    group.contacts.add(c.contact)
    group.updated = msg.timestamp
  } else {
    if (group) {
      group.contacts.delete(c.contact)
      if (!group.contacts.size) {
        delete groups[id]
      }
    }
  }
}

function ensureMessage (id, groups) {
  var group = groups[id]
  if (!group) {
    group = groups[id] = {
      type: 'message',
      repliesFrom: new Set(),
      replies: [],
      message: null,
      messageId: id,
      digs: new Set(),
      updated: 0
    }
  }
  return group
}
<|endoftext|>


<commit_before>'use strict';

var React = require('react');
var ReactDOM = require('react-dom');

var containmentPropType = React.PropTypes.any;

if (typeof window !== 'undefined') {
  containmentPropType = React.PropTypes.instanceOf(window.Element);
}

function debounce(func, wait) {
  var timeout;
  return function() {
    var context = this, args = arguments;
    var later = function() {
      timeout = null;
      func.apply(context, args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

module.exports = React.createClass({
  displayName: 'VisibilitySensor',

  propTypes: {
    onChange: React.PropTypes.func.isRequired,
    active: React.PropTypes.bool,
    partialVisibility: React.PropTypes.oneOfType([
      React.PropTypes.bool,
      React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
    ]),
    delayedCall: React.PropTypes.bool,
    scrollCheck: React.PropTypes.bool,
    scrollDelay: React.PropTypes.number,
    intervalCheck: React.PropTypes.bool,
    intervalDelay: React.PropTypes.number,
    containment: containmentPropType,
    children: React.PropTypes.element,
    minTopValue: React.PropTypes.number
  },

  getDefaultProps: function () {
    return {
      active: true,
      partialVisibility: false,
      minTopValue: 0,
      scrollCheck: false,
      scrollDelay: 250,
      intervalCheck: true,
      intervalDelay: 1500,
      delayedCall: false,
      containment: null,
      children: React.createElement('span')
    };
  },

  getInitialState: function () {
    return {
      isVisible: null,
      visibilityRect: {}
    };
  },

  componentDidMount: function () {
    if (this.props.active) {
      this.startWatching();
    }
  },

  componentWillUnmount: function () {
    this.stopWatching();
  },

  componentWillReceiveProps: function (nextProps) {
    if (nextProps.active) {
      this.setState(this.getInitialState());
      this.startWatching();
    } else {
      this.stopWatching();
    }
  },

  getContainer: function () {
    return this.props.containment || window;
  },

  startWatching: function () {
    if (this.debounceCheck || this.interval) { return; }

    if (this.props.intervalCheck) {
      this.interval = setInterval(this.check, this.props.intervalDelay);
    }

    if (this.props.scrollCheck) {
      this.debounceCheck = debounce(this.check, this.props.scrollDelay);
      this.getContainer().addEventListener('scroll', this.debounceCheck);
    }

    // if dont need delayed call, check on load ( before the first interval fires )
    !this.props.delayedCall && this.check();
  },

  stopWatching: function () {
    if (this.debounceCheck) {
      this.getContainer().removeEventListener('scroll', this.debounceCheck);
      this.debounceCheck = null;
    }
    if (this.interval) { this.interval = clearInterval(this.interval); }
  },

  /**
   * Check if the element is within the visible viewport
   */
  check: function () {
    var el = ReactDOM.findDOMNode(this);
    var rect;
    var containmentRect;

    // if the component has rendered to null, dont update visibility
    if (!el) {
      return this.state;
    }

    rect = el.getBoundingClientRect();

    if (this.props.containment) {
      containmentRect = this.props.containment.getBoundingClientRect();
    } else {
      containmentRect = {
        top: 0,
        left: 0,
        bottom: window.innerHeight || document.documentElement.clientHeight,
        right: window.innerWidth || document.documentElement.clientWidth
      };
    }

    var visibilityRect = {
      top: rect.top >= containmentRect.top,
      left: rect.left >= containmentRect.left,
      bottom: rect.bottom <= containmentRect.bottom,
      right: rect.right <= containmentRect.right
    };

    var isVisible = (
      visibilityRect.top &&
      visibilityRect.left &&
      visibilityRect.bottom &&
      visibilityRect.right
    );

    // check for partial visibility
    if (this.props.partialVisibility) {
      var partialVisible =
          rect.top <= containmentRect.bottom && rect.bottom >= containmentRect.top &&
          rect.left <= containmentRect.right && rect.right >= containmentRect.left;

      // account for partial visibility on a single edge
      if (typeof this.props.partialVisibility === 'string') {
        partialVisible = visibilityRect[this.props.partialVisibility]
      }

      // if we have minimum top visibility set by props, lets check, if it meets the passed value
      // so if for instance element is at least 200px in viewport, then show it.
      isVisible = this.props.minTopValue
        ? partialVisible && rect.top <= (containmentRect.bottom - this.props.minTopValue)
        : partialVisible
    }

    var state = this.state;
    // notify the parent when the value changes
    if (this.state.isVisible !== isVisible) {
      state = {
        isVisible: isVisible,
        visibilityRect: visibilityRect
      };
      this.setState(state);
      this.props.onChange(isVisible, visibilityRect);
    }

    return state;
  },

  render: function () {
    return React.Children.only(this.props.children);
  }
});
<commit_msg>Added ability to use offsets.<commit_after>'use strict';

var React = require('react');
var ReactDOM = require('react-dom');

var containmentPropType = React.PropTypes.any;

if (typeof window !== 'undefined') {
  containmentPropType = React.PropTypes.instanceOf(window.Element);
}

function debounce(func, wait) {
  var timeout;
  return function() {
    var context = this, args = arguments;
    var later = function() {
      timeout = null;
      func.apply(context, args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

module.exports = React.createClass({
  displayName: 'VisibilitySensor',

  propTypes: {
    onChange: React.PropTypes.func.isRequired,
    active: React.PropTypes.bool,
    partialVisibility: React.PropTypes.oneOfType([
      React.PropTypes.bool,
      React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
    ]),
    delayedCall: React.PropTypes.bool,
    offset: React.PropTypes.shape({
      direction: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
      value: React.PropTypes.number
    }),
    scrollCheck: React.PropTypes.bool,
    scrollDelay: React.PropTypes.number,
    intervalCheck: React.PropTypes.bool,
    intervalDelay: React.PropTypes.number,
    containment: containmentPropType,
    children: React.PropTypes.element,
    minTopValue: React.PropTypes.number
  },

  getDefaultProps: function () {
    return {
      active: true,
      partialVisibility: false,
      minTopValue: 0,
      scrollCheck: false,
      scrollDelay: 250,
      intervalCheck: true,
      intervalDelay: 1500,
      delayedCall: false,
      offset: {},
      containment: null,
      children: React.createElement('span')
    };
  },

  getInitialState: function () {
    return {
      isVisible: null,
      visibilityRect: {}
    };
  },

  componentDidMount: function () {
    if (this.props.active) {
      this.startWatching();
    }
  },

  componentWillUnmount: function () {
    this.stopWatching();
  },

  componentWillReceiveProps: function (nextProps) {
    if (nextProps.active) {
      this.setState(this.getInitialState());
      this.startWatching();
    } else {
      this.stopWatching();
    }
  },

  getContainer: function () {
    return this.props.containment || window;
  },

  startWatching: function () {
    if (this.debounceCheck || this.interval) { return; }

    if (this.props.intervalCheck) {
      this.interval = setInterval(this.check, this.props.intervalDelay);
    }

    if (this.props.scrollCheck) {
      this.debounceCheck = debounce(this.check, this.props.scrollDelay);
      this.getContainer().addEventListener('scroll', this.debounceCheck);
    }

    // if dont need delayed call, check on load ( before the first interval fires )
    !this.props.delayedCall && this.check();
  },

  stopWatching: function () {
    if (this.debounceCheck) {
      this.getContainer().removeEventListener('scroll', this.debounceCheck);
      this.debounceCheck = null;
    }
    if (this.interval) { this.interval = clearInterval(this.interval); }
  },

  /**
   * Check if the element is within the visible viewport
   */
  check: function () {
    var el = ReactDOM.findDOMNode(this);
    var rect;
    var containmentRect;

    // if the component has rendered to null, dont update visibility
    if (!el) {
      return this.state;
    }

    rect = el.getBoundingClientRect();

    if (this.props.containment) {
      containmentRect = this.props.containment.getBoundingClientRect();
    } else {
      containmentRect = {
        top: 0,
        left: 0,
        bottom: window.innerHeight || document.documentElement.clientHeight,
        right: window.innerWidth || document.documentElement.clientWidth
      };
    }

    var visibilityRect = {
      top: rect.top >= containmentRect.top,
      left: rect.left >= containmentRect.left,
      bottom: rect.bottom <= containmentRect.bottom,
      right: rect.right <= containmentRect.right
    };

    var isVisible = (
      visibilityRect.top &&
      visibilityRect.left &&
      visibilityRect.bottom &&
      visibilityRect.right
    );

    // check for partial visibility
    if (this.props.partialVisibility) {
      var partialVisible =
          rect.top <= containmentRect.bottom && rect.bottom >= containmentRect.top &&
          rect.left <= containmentRect.right && rect.right >= containmentRect.left;

      // account for partial visibility on a single edge
      if (typeof this.props.partialVisibility === 'string') {
        partialVisible = visibilityRect[this.props.partialVisibility]
      }

      // if we have minimum top visibility set by props, lets check, if it meets the passed value
      // so if for instance element is at least 200px in viewport, then show it.
      isVisible = this.props.minTopValue
        ? partialVisible && rect.top <= (containmentRect.bottom - this.props.minTopValue)
        : partialVisible
    }

    // Check if visibility is wanted via offset?
    if (typeof this.props.offset === 'object' &&
        typeof this.props.offset.direction === 'string' &&
        typeof this.props.offset.value === 'number'
      ) {
      var offsetDir = this.props.offset.direction;
      var offsetVal = Math.abs(this.props.offset.value);

      // Rules for checking different kind of offsets. In example if the element is
      // 90px below viewport and offsetTop is 100, it is considered visible.
      var offsetVisibility = {
        top: ((containmentRect.bottom + offsetVal) > rect.top) &&
             (containmentRect.top < rect.bottom) &&
             (containmentRect.left < rect.right) &&
             (containmentRect.right > rect.left),
        left: ((containmentRect.right + offsetVal) > rect.left) &&
              (containmentRect.bottom > rect.top) &&
              (containmentRect.top < rect.bottom) &&
              (containmentRect.left < rect.right),
        bottom: ((containmentRect.top - offsetVal) < rect.bottom) &&
                (containmentRect.left < rect.right) &&
                (containmentRect.right > rect.left) &&
                (containmentRect.bottom > rect.top),
        right: ((containmentRect.left - offsetVal) < rect.right) &&
               (containmentRect.right > rect.left) &&
               (containmentRect.top < rect.bottom) &&
               (containmentRect.bottom > rect.top)
      }

      isVisible = offsetVisibility[offsetDir]
    }

    var state = this.state;
    // notify the parent when the value changes
    if (this.state.isVisible !== isVisible) {
      state = {
        isVisible: isVisible,
        visibilityRect: visibilityRect
      };
      this.setState(state);
      this.props.onChange(isVisible, visibilityRect);
    }

    return state;
  },

  render: function () {
    return React.Children.only(this.props.children);
  }
});
<|endoftext|>


<commit_before>import { SYNC_RENDER, DOM_RENDER, NO_RENDER, FORCE_RENDER, EMPTY_BASE } from '../constants';
import options from '../options';
import { isFunction, clone, extend, empty } from '../util';
import { hook, deepHook } from '../hooks';
import { enqueueRender } from '../render-queue';
import { getNodeProps } from '.';
import diff from './diff';
import { removeOrphanedChildren, recollectNodeTree } from './diff';
import { createComponent, collectComponent } from './component-recycler';
import { removeNode } from '../dom';

/** Mark component as dirty and queue up a render.
 *	@param {Component} component
 *	@private
 */
export function triggerComponentRender(component) {
	if (!component._dirty) {
		component._dirty = true;
		enqueueRender(component);
	}
}

/** Set a component's `props` (generally derived from JSX attributes).
 *	@param {Object} props
 *	@param {Object} [opts]
 *	@param {boolean} [opts.renderSync=false]	If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering.
 *	@param {boolean} [opts.render=true]			If `false`, no render will be triggered.
 */
export function setComponentProps(component, props, opts, context, mountAll) {
	let d = component._disableRendering===true;
	component._disableRendering = true;

	if ((component.__ref = props.ref)) delete props.ref;
	if ((component.__key = props.key)) delete props.key;

	if (!empty(component.base)) {
		hook(component, 'componentWillReceiveProps', props, context);
	}

	if (context && context!==component.context) {
		if (!component.prevContext) component.prevContext = component.context;
		component.context = context;
	}

	if (!component.prevProps) component.prevProps = component.props;
	component.props = props;

	component._disableRendering = d;

	if (opts!==NO_RENDER) {
		if (opts===SYNC_RENDER || options.syncComponentUpdates!==false) {
			renderComponent(component, SYNC_RENDER, mountAll);
		}
		else {
			triggerComponentRender(component);
		}
	}

	hook(component, '__ref', component);
}

/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.
 *	@param {Component} component
 *	@param {Object} [opts]
 *	@param {boolean} [opts.build=false]		If `true`, component will build and store a DOM node if not already associated with one.
 *	@private
 */
export function renderComponent(component, opts, mountAll) {
	if (component._disableRendering) return;

	let skip, rendered,
		props = component.props,
		state = component.state,
		context = component.context,
		previousProps = component.prevProps || props,
		previousState = component.prevState || state,
		previousContext = component.prevContext || context,
		isUpdate = component.base,
		initialBase = isUpdate || component.nextBase;

	// if updating
	if (isUpdate) {
		component.props = previousProps;
		component.state = previousState;
		component.context = previousContext;
		if (opts!==FORCE_RENDER && hook(component, 'shouldComponentUpdate', props, state, context)===false) {
			skip = true;
		}
		else {
			hook(component, 'componentWillUpdate', props, state, context);
		}
		component.props = props;
		component.state = state;
		component.context = context;
	}

	component.prevProps = component.prevState = component.prevContext = component.nextBase = null;
	component._dirty = false;

	if (!skip) {
		rendered = hook(component, 'render', props, state, context);

		let childComponent = rendered && rendered.nodeName,
			toUnmount, base;

		// context to pass to the child, can be updated via (grand-)parent component
		if (component.getChildContext) {
			context = extend(clone(context), component.getChildContext());
		}

		if (isFunction(childComponent) && childComponent.prototype.render) {
			// set up high order component link

			let inst = component._component;
			if (inst && inst.constructor!==childComponent) {
				toUnmount = inst;
				inst = null;
			}

			let childProps = getNodeProps(rendered);

			if (inst) {
				setComponentProps(inst, childProps, SYNC_RENDER, context);
			}
			else {
				inst = createComponent(childComponent, childProps, context);
				inst._parentComponent = component;
				component._component = inst;
				if (isUpdate) deepHook(inst, 'componentWillMount');
				setComponentProps(inst, childProps, NO_RENDER, context);
				renderComponent(inst, DOM_RENDER);
				if (isUpdate) deepHook(inst, 'componentDidMount');
			}

			base = inst.base;
		}
		else {
			let cbase = initialBase;

			// destroy high order component link
			toUnmount = component._component;
			if (toUnmount) {
				cbase = component._component = null;
			}

			if (initialBase || opts===DOM_RENDER) {
				if (cbase) cbase._component = null;
				base = diff(cbase, rendered || EMPTY_BASE, context, mountAll || !isUpdate);
			}
		}

		if (initialBase && base!==initialBase) {
			let p = initialBase.parentNode;
			if (p && base!==p) p.replaceChild(base, initialBase);
		}

		if (toUnmount) {
			unmountComponent(toUnmount, true);
		}

		component.base = base;
		if (base) {
			let componentRef = component,
				t = component;
			while ((t=t._parentComponent)) { componentRef = t; }
			base._component = componentRef;
			base._componentConstructor = componentRef.constructor;
		}

		if (isUpdate) {
			hook(component, 'componentDidUpdate', previousProps, previousState, previousContext);
		}
	}

	let cb = component._renderCallbacks, fn;
	if (cb) while ( (fn = cb.pop()) ) fn.call(component);

	return rendered;
}

/** Apply the Component referenced by a VNode to the DOM.
 *	@param {Element} dom	The DOM node to mutate
 *	@param {VNode} vnode	A Component-referencing VNode
 *	@returns {Element} dom	The created/mutated element
 *	@private
 */
export function buildComponentFromVNode(dom, vnode, context, mountAll) {
	let c = dom && dom._component,
		oldDom = dom;

	let isOwner = c && dom._componentConstructor===vnode.nodeName;
	while (c && !isOwner && (c=c._parentComponent)) {
		isOwner = c.constructor===vnode.nodeName;
	}

	if (isOwner) {
		setComponentProps(c, getNodeProps(vnode), SYNC_RENDER, context, mountAll);
		dom = c.base;
	}
	else {
		if (c) {
			unmountComponent(c, true);
			dom = oldDom = null;
		}
		dom = createComponentFromVNode(vnode, dom, context, mountAll);
		if (oldDom && dom!==oldDom) {
			oldDom._component = null;
			recollectNodeTree(oldDom);
		}
	}

	return dom;
}

/** Instantiate and render a Component, given a VNode whose nodeName is a constructor.
 *	@param {VNode} vnode
 *	@private
 */
function createComponentFromVNode(vnode, dom, context, mountAll) {
	let props = getNodeProps(vnode);
	let component = createComponent(vnode.nodeName, props, context);

	if (dom && !component.base) component.base = dom;

	setComponentProps(component, props, NO_RENDER, context);
	renderComponent(component, DOM_RENDER, mountAll);

	// let node = component.base;
	//if (!node._component) {
	//	node._component = component;
	//	node._componentConstructor = vnode.nodeName;
	//}

	return component.base;
}

/** Remove a component from the DOM and recycle it.
 *	@param {Element} dom			A DOM node from which to unmount the given Component
 *	@param {Component} component	The Component instance to unmount
 *	@private
 */
export function unmountComponent(component, remove) {
	// console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`, component);

	hook(component, '__ref', null);
	hook(component, 'componentWillUnmount');

	// recursively tear down & recollect high-order component children:
	let inner = component._component;
	if (inner) {
		unmountComponent(inner, remove);
		remove = false;
	}

	let base = component.base;
	if (base) {
		if (remove!==false) removeNode(base);
		removeOrphanedChildren(base.childNodes, true);
	}

	if (remove) {
		component._parentComponent = null;
		collectComponent(component);
	}

	hook(component, 'componentDidUnmount');
}
<commit_msg>Re-instance components when remounting recursively. Fixes #164.<commit_after>import { SYNC_RENDER, DOM_RENDER, NO_RENDER, FORCE_RENDER, EMPTY_BASE } from '../constants';
import options from '../options';
import { isFunction, clone, extend, empty } from '../util';
import { hook, deepHook } from '../hooks';
import { enqueueRender } from '../render-queue';
import { getNodeProps } from '.';
import diff from './diff';
import { removeOrphanedChildren, recollectNodeTree } from './diff';
import { createComponent, collectComponent } from './component-recycler';
import { removeNode } from '../dom';

/** Mark component as dirty and queue up a render.
 *	@param {Component} component
 *	@private
 */
export function triggerComponentRender(component) {
	if (!component._dirty) {
		component._dirty = true;
		enqueueRender(component);
	}
}

/** Set a component's `props` (generally derived from JSX attributes).
 *	@param {Object} props
 *	@param {Object} [opts]
 *	@param {boolean} [opts.renderSync=false]	If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering.
 *	@param {boolean} [opts.render=true]			If `false`, no render will be triggered.
 */
export function setComponentProps(component, props, opts, context, mountAll) {
	let d = component._disableRendering===true;
	component._disableRendering = true;

	if ((component.__ref = props.ref)) delete props.ref;
	if ((component.__key = props.key)) delete props.key;

	if (!empty(component.base)) {
		hook(component, 'componentWillReceiveProps', props, context);
	}

	if (context && context!==component.context) {
		if (!component.prevContext) component.prevContext = component.context;
		component.context = context;
	}

	if (!component.prevProps) component.prevProps = component.props;
	component.props = props;

	component._disableRendering = d;

	if (opts!==NO_RENDER) {
		if (opts===SYNC_RENDER || options.syncComponentUpdates!==false) {
			renderComponent(component, SYNC_RENDER, mountAll);
		}
		else {
			triggerComponentRender(component);
		}
	}

	hook(component, '__ref', component);
}

/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.
 *	@param {Component} component
 *	@param {Object} [opts]
 *	@param {boolean} [opts.build=false]		If `true`, component will build and store a DOM node if not already associated with one.
 *	@private
 */
export function renderComponent(component, opts, mountAll) {
	if (component._disableRendering) return;

	let skip, rendered,
		props = component.props,
		state = component.state,
		context = component.context,
		previousProps = component.prevProps || props,
		previousState = component.prevState || state,
		previousContext = component.prevContext || context,
		isUpdate = component.base,
		initialBase = isUpdate || component.nextBase;

	// if updating
	if (isUpdate) {
		component.props = previousProps;
		component.state = previousState;
		component.context = previousContext;
		if (opts!==FORCE_RENDER && hook(component, 'shouldComponentUpdate', props, state, context)===false) {
			skip = true;
		}
		else {
			hook(component, 'componentWillUpdate', props, state, context);
		}
		component.props = props;
		component.state = state;
		component.context = context;
	}

	component.prevProps = component.prevState = component.prevContext = component.nextBase = null;
	component._dirty = false;

	if (!skip) {
		rendered = hook(component, 'render', props, state, context);

		let childComponent = rendered && rendered.nodeName,
			toUnmount, base;

		// context to pass to the child, can be updated via (grand-)parent component
		if (component.getChildContext) {
			context = extend(clone(context), component.getChildContext());
		}

		if (isFunction(childComponent) && childComponent.prototype.render) {
			// set up high order component link

			let inst = component._component;
			if (inst && inst.constructor!==childComponent) {
				toUnmount = inst;
				inst = null;
			}

			let childProps = getNodeProps(rendered);

			if (inst) {
				setComponentProps(inst, childProps, SYNC_RENDER, context);
			}
			else {
				inst = createComponent(childComponent, childProps, context);
				inst._parentComponent = component;
				component._component = inst;
				if (isUpdate) deepHook(inst, 'componentWillMount');
				setComponentProps(inst, childProps, NO_RENDER, context);
				renderComponent(inst, DOM_RENDER);
				if (isUpdate) deepHook(inst, 'componentDidMount');
			}

			base = inst.base;
		}
		else {
			let cbase = initialBase;

			// destroy high order component link
			toUnmount = component._component;
			if (toUnmount) {
				cbase = component._component = null;
			}

			if (initialBase || opts===DOM_RENDER) {
				if (cbase) cbase._component = null;
				base = diff(cbase, rendered || EMPTY_BASE, context, mountAll || !isUpdate);
			}
		}

		if (initialBase && base!==initialBase) {
			let p = initialBase.parentNode;
			if (p && base!==p) p.replaceChild(base, initialBase);
		}

		if (toUnmount) {
			unmountComponent(toUnmount, true);
		}

		component.base = base;
		if (base) {
			let componentRef = component,
				t = component;
			while ((t=t._parentComponent)) { componentRef = t; }
			base._component = componentRef;
			base._componentConstructor = componentRef.constructor;
		}

		if (isUpdate) {
			hook(component, 'componentDidUpdate', previousProps, previousState, previousContext);
		}
	}

	let cb = component._renderCallbacks, fn;
	if (cb) while ( (fn = cb.pop()) ) fn.call(component);

	return rendered;
}

/** Apply the Component referenced by a VNode to the DOM.
 *	@param {Element} dom	The DOM node to mutate
 *	@param {VNode} vnode	A Component-referencing VNode
 *	@returns {Element} dom	The created/mutated element
 *	@private
 */
export function buildComponentFromVNode(dom, vnode, context, mountAll) {
	let c = dom && dom._component,
		oldDom = dom;

	let isOwner = !mountAll && c && dom._componentConstructor===vnode.nodeName;
	while (c && !isOwner && (c=c._parentComponent)) {
		isOwner = c.constructor===vnode.nodeName;
	}

	if (isOwner) {
		setComponentProps(c, getNodeProps(vnode), SYNC_RENDER, context, mountAll);
		dom = c.base;
	}
	else {
		if (c) {
			unmountComponent(c, true);
			dom = oldDom = null;
		}
		dom = createComponentFromVNode(vnode, dom, context, mountAll);
		if (oldDom && dom!==oldDom) {
			oldDom._component = null;
			recollectNodeTree(oldDom);
		}
	}

	return dom;
}

/** Instantiate and render a Component, given a VNode whose nodeName is a constructor.
 *	@param {VNode} vnode
 *	@private
 */
function createComponentFromVNode(vnode, dom, context, mountAll) {
	let props = getNodeProps(vnode);
	let component = createComponent(vnode.nodeName, props, context);

	if (dom && !component.base) component.base = dom;

	setComponentProps(component, props, NO_RENDER, context);
	renderComponent(component, DOM_RENDER, mountAll);

	// let node = component.base;
	//if (!node._component) {
	//	node._component = component;
	//	node._componentConstructor = vnode.nodeName;
	//}

	return component.base;
}

/** Remove a component from the DOM and recycle it.
 *	@param {Element} dom			A DOM node from which to unmount the given Component
 *	@param {Component} component	The Component instance to unmount
 *	@private
 */
export function unmountComponent(component, remove) {
	// console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`, component);

	hook(component, '__ref', null);
	hook(component, 'componentWillUnmount');

	// recursively tear down & recollect high-order component children:
	let inner = component._component;
	if (inner) {
		unmountComponent(inner, remove);
		remove = false;
	}

	let base = component.base;
	if (base) {
		if (remove!==false) removeNode(base);
		removeOrphanedChildren(base.childNodes, true);
	}

	if (remove) {
		component._parentComponent = null;
		collectComponent(component);
	}

	hook(component, 'componentDidUnmount');
}
<|endoftext|>


<commit_before>module.exports = function() {
	'use strict';
	var tasks = {
		'build': [
			'readpkg',
			'clean',
			'shell:bower',
			'build:dependencies:php',
			'build:fonts',
			'build:images',
			'build:css',
			'build:js',
			'clean:bowercomponents',
			'build:i18n'
		],
		'build:dist': [
			'readpkg',
			'clean',
			'shell:bower',
			'build:dependencies:php',
			'build:fonts',
			'build:images',
			'build:css:dist',
			'build:js:dist',
			'clean:bowercomponents',
			'build:i18n'
		],
		'build:dependencies': [
			'build:dependencies:css',
			'build:dependencies:fonts',
			'build:dependencies:js',
			'build:dependencies:php'
		],

		'build:i18n': [
			'clean:languages',
			'addtextdomain',
			'makepot',
			'newer:copy:languages'
		],
		'default': [
			'watch'
		],
		'newtheme': [
			'replace:packagename',
			'build'
		],
		'package': [
			'build',
			'compress'
		],
		'package:dist': [
			'build:dist',
			'compress:dist'
		],
		'package:dev': [
			'build',
			'compress:dev'
		],
		'release:major': [
			'version::major',
			'package'
		],
		'release:minor': [
			'version::minor',
			'package'
		],
		'release:patch': [
			'version::patch',
			'package'
		]
	};

	return tasks;
};
<commit_msg>Move initial package read back to init<commit_after>module.exports = function() {
	'use strict';
	var tasks = {
		'build': [
			'clean',
			'shell:bower',
			'build:dependencies:php',
			'build:fonts',
			'build:images',
			'build:css',
			'build:js',
			'clean:bowercomponents',
			'build:i18n'
		],
		'build:dist': [
			'clean',
			'shell:bower',
			'build:dependencies:php',
			'build:fonts',
			'build:images',
			'build:css:dist',
			'build:js:dist',
			'clean:bowercomponents',
			'build:i18n'
		],
		'build:dependencies': [
			'build:dependencies:css',
			'build:dependencies:fonts',
			'build:dependencies:js',
			'build:dependencies:php'
		],

		'build:i18n': [
			'clean:languages',
			'addtextdomain',
			'makepot',
			'newer:copy:languages'
		],
		'default': [
			'watch'
		],
		'newtheme': [
			'replace:packagename',
			'build'
		],
		'package': [
			'build',
			'compress'
		],
		'package:dist': [
			'build:dist',
			'compress:dist'
		],
		'package:dev': [
			'build',
			'compress:dev'
		],
		'release:major': [
			'version::major',
			'package'
		],
		'release:minor': [
			'version::minor',
			'package'
		],
		'release:patch': [
			'version::patch',
			'package'
		]
	};

	return tasks;
};
<|endoftext|>


<commit_before>// @flow

import { hasAvailableDevices } from '../base/devices';
import { TOOLBOX_ALWAYS_VISIBLE, getFeatureFlag, TOOLBOX_ENABLED } from '../base/flags';
import { toState } from '../base/redux';
import { isLocalVideoTrackDesktop } from '../base/tracks';

const WIDTH = {
    FIT_9_ICONS: 560,
    FIT_8_ICONS: 500,
    FIT_7_ICONS: 440,
    FIT_6_ICONS: 380
};

/**
 * Returns a set of the buttons that are shown in the toolbar
 * but removed from the overflow menu, based on the width of the screen.
 *
 * @param {number} width - The width of the screen.
 * @returns {Set}
 */
export function getMovableButtons(width: number): Set<string> {
    let buttons = [];

    switch (true) {
    case width >= WIDTH.FIT_9_ICONS: {
        buttons = [ 'togglecamera', 'chat', 'invite', 'raisehand', 'tileview' ];
        break;
    }
    case width >= WIDTH.FIT_8_ICONS: {
        buttons = [ 'chat', 'invite', 'raisehand', 'tileview' ];
        break;
    }

    case width >= WIDTH.FIT_7_ICONS: {
        buttons = [ 'chat', 'raisehand', 'invite' ];
        break;
    }

    case width >= WIDTH.FIT_6_ICONS: {
        buttons = [ 'chat', 'raisehand' ];
        break;
    }

    default: {
        buttons = [ 'chat' ];
    }
    }

    return new Set(buttons);
}

/**
 * Returns true if the toolbox is visible.
 *
 * @param {Object | Function} stateful - A function or object that can be
 * resolved to Redux state by the function {@code toState}.
 * @returns {boolean}
 */
export function isToolboxVisible(stateful: Object | Function) {
    const state = toState(stateful);
    const { alwaysVisible, enabled, visible } = state['features/toolbox'];
    const { length: participantCount } = state['features/base/participants'];
    const alwaysVisibleFlag = getFeatureFlag(state, TOOLBOX_ALWAYS_VISIBLE, false);
    const enabledFlag = getFeatureFlag(state, TOOLBOX_ENABLED, true);

    return enabledFlag && enabled && (alwaysVisible || visible || participantCount === 1 || alwaysVisibleFlag);
}

/**
 * Indicates if the video mute button is disabled or not.
 *
 * @param {string} state - The state from the Redux store.
 * @returns {boolean}
 */
export function isVideoMuteButtonDisabled(state: Object) {
    return !hasAvailableDevices(state, 'videoInput') || isLocalVideoTrackDesktop(state);
}
<commit_msg>fix(rn, toolbox): Change button appearing order<commit_after>// @flow

import { hasAvailableDevices } from '../base/devices';
import { TOOLBOX_ALWAYS_VISIBLE, getFeatureFlag, TOOLBOX_ENABLED } from '../base/flags';
import { toState } from '../base/redux';
import { isLocalVideoTrackDesktop } from '../base/tracks';

const WIDTH = {
    FIT_9_ICONS: 560,
    FIT_8_ICONS: 500,
    FIT_7_ICONS: 440,
    FIT_6_ICONS: 380
};

/**
 * Returns a set of the buttons that are shown in the toolbar
 * but removed from the overflow menu, based on the width of the screen.
 *
 * @param {number} width - The width of the screen.
 * @returns {Set}
 */
export function getMovableButtons(width: number): Set<string> {
    let buttons = [];

    switch (true) {
    case width >= WIDTH.FIT_9_ICONS: {
        buttons = [ 'togglecamera', 'chat', 'invite', 'raisehand', 'tileview' ];
        break;
    }
    case width >= WIDTH.FIT_8_ICONS: {
        buttons = [ 'chat', 'togglecamera', 'raisehand', 'tileview' ];
        break;
    }

    case width >= WIDTH.FIT_7_ICONS: {
        buttons = [ 'chat', 'togglecamera', 'raisehand' ];
        break;
    }

    case width >= WIDTH.FIT_6_ICONS: {
        buttons = [ 'chat', 'togglecamera' ];
        break;
    }

    default: {
        buttons = [ 'chat' ];
    }
    }

    return new Set(buttons);
}

/**
 * Returns true if the toolbox is visible.
 *
 * @param {Object | Function} stateful - A function or object that can be
 * resolved to Redux state by the function {@code toState}.
 * @returns {boolean}
 */
export function isToolboxVisible(stateful: Object | Function) {
    const state = toState(stateful);
    const { alwaysVisible, enabled, visible } = state['features/toolbox'];
    const { length: participantCount } = state['features/base/participants'];
    const alwaysVisibleFlag = getFeatureFlag(state, TOOLBOX_ALWAYS_VISIBLE, false);
    const enabledFlag = getFeatureFlag(state, TOOLBOX_ENABLED, true);

    return enabledFlag && enabled && (alwaysVisible || visible || participantCount === 1 || alwaysVisibleFlag);
}

/**
 * Indicates if the video mute button is disabled or not.
 *
 * @param {string} state - The state from the Redux store.
 * @returns {boolean}
 */
export function isVideoMuteButtonDisabled(state: Object) {
    return !hasAvailableDevices(state, 'videoInput') || isLocalVideoTrackDesktop(state);
}
<|endoftext|>


<commit_before><commit_msg>Rearranged wire disconnect logic<commit_after><|endoftext|>


<commit_before>var transform = require('./presenters/profile').transform,
    Hapi = require('hapi'),
    Joi = require('joi'),
    log = require('bole')('billing');

var Customer = new (require('../../lib/customer-api'))();
var billing = module.exports = {};

billing.getBillingInfo = function (request, reply) {
  var opts = {
    namespace: 'billing',
    title: 'Billing',
    updated: ('updated' in request.query),
    canceled: ('canceled' in request.query),
    stripePublicKey: process.env.STRIPE_PUBLIC_KEY
  };

  Customer.get(request.auth.credentials.name, function(err, resp, body) {
    if (resp && resp.statusCode == 200) {
      opts.customer = body;
    }
    return reply.view('user/billing', opts);
  });
}

billing.updateBillingInfo = function(request, reply) {

  var billingInfo = {
    name: request.auth.credentials.name,
    email: request.auth.credentials.email,
    card: request.payload.stripeToken
  }

  Customer.update(billingInfo, function(err, resp, body) {
    // if (err) return showError(err);
    console.log("updateBillingInfo end")
    if (resp && resp.statusCode == 200) {
      return reply.redirect('/settings/billing?updated=1')
    }
  })

}

billing.deleteBillingInfo = function(request, reply) {

  Customer.del(request.auth.credentials.name, function(err, resp, body) {
    // if (err) return showError(err);
    if (resp && resp.statusCode == 200) {
      return reply.redirect('/settings/billing?canceled=1')
    }
  })
}
<commit_msg>handle stripe billing api errors<commit_after>var transform = require('./presenters/profile').transform,
    Hapi = require('hapi'),
    Joi = require('joi'),
    log = require('bole')('billing');

var Customer = new (require('../../lib/customer-api'))();
var billing = module.exports = {};

billing.getBillingInfo = function (request, reply) {
  var opts = {
    namespace: 'billing',
    title: 'Billing',
    updated: ('updated' in request.query),
    canceled: ('canceled' in request.query),
    stripePublicKey: process.env.STRIPE_PUBLIC_KEY
  };

  Customer.get(request.auth.credentials.name, function(err, resp, body) {
    if (resp && resp.statusCode == 200) {
      opts.customer = body;
    }
    return reply.view('user/billing', opts);
  });
}

billing.updateBillingInfo = function(request, reply) {

  var billingInfo = {
    name: request.auth.credentials.name,
    email: request.auth.credentials.email,
    card: request.payload.stripeToken
  }

  Customer.update(billingInfo, function(err, resp, body) {
    if (err) {
      request.logger.error(err);
      return reply.view('errors/internal', opts).code(500);
    }

    if (resp && resp.statusCode == 200) {
      return reply.redirect('/settings/billing?updated=1')
    }
  })

}

billing.deleteBillingInfo = function(request, reply) {

  Customer.del(request.auth.credentials.name, function(err, resp, body) {
    if (err) {
      request.logger.error(err);
      return reply.view('errors/internal', opts).code(500);
    }

    if (resp && resp.statusCode == 200) {
      return reply.redirect('/settings/billing?canceled=1')
    }
  })
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import PlaylistModal from 'components/Modals/PlaylistModal';
import TagModal from './TagModal';
import { hashify } from 'helpers/stringHelpers';
import { fetchPlaylistsContaining } from 'helpers/requestHelpers';
import { connect } from 'react-redux';
import { css } from 'emotion';
import { Color } from 'constants/css';

class TagStatus extends Component {
  mounted = false;
  state = {
    shownPlaylistId: null,
    shownPlaylistTitle: '',
    playlists: [],
    tagModalShown: false
  };

  static propTypes = {
    onAddTags: PropTypes.func.isRequired,
    onAddTagToContents: PropTypes.func,
    onLoadTags: PropTypes.func.isRequired,
    canEditPlaylists: PropTypes.bool,
    contentId: PropTypes.number.isRequired,
    style: PropTypes.object,
    tags: PropTypes.array.isRequired
  };

  async componentDidMount() {
    const { onLoadTags, contentId } = this.props;
    this.mounted = true;
    const playlists = await fetchPlaylistsContaining({ videoId: contentId });
    onLoadTags({ tags: playlists, contentId, type: 'video' });
  }

  async componentDidUpdate(prevProps) {
    const { onLoadTags, contentId } = this.props;
    if (contentId !== prevProps.contentId) {
      const playlists = await fetchPlaylistsContaining({ videoId: contentId });
      onLoadTags({ tags: playlists, contentId, type: 'video' });
    }
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  render() {
    const { shownPlaylistId, shownPlaylistTitle, tagModalShown } = this.state;
    const {
      onAddTagToContents,
      contentId,
      canEditPlaylists,
      style,
      tags
    } = this.props;
    return (

        {(tags.length > 0 || canEditPlaylists) && (

            {tags.map((tag, index) => (

                  this.setState({
                    shownPlaylistId: tag.id,
                    shownPlaylistTitle: tag.title
                  })
                }
              >
                {hashify(tag.title)}

            ))}
            {canEditPlaylists && (
               0 ? Color.orange() : Color.blue()
                }}
                onClick={() => this.setState({ tagModalShown: true })}
              >
                +Add
                {tags.length === 0 ? ' to Playlists' : ''}

            )}

        )}
        {tagModalShown && (
          <TagModal
            currentPlaylists={tags.map(tag => tag.id)}
            title="Add Video to Playlists"
            onHide={() => this.setState({ tagModalShown: false })}
            onAddPlaylist={({ videoIds, playlistId, playlistTitle }) =>
              onAddTagToContents?.({
                contentIds: videoIds,
                contentType: 'video',
                tagId: playlistId,
                tagTitle: playlistTitle
              })
            }
            onSubmit={this.onTagSubmit}
            videoId={contentId}
          />
        )}
        {shownPlaylistId && (
          <PlaylistModal
            onLinkClick={this.setState({ shownPlaylistId: null })}
            title={shownPlaylistTitle}
            playlistId={shownPlaylistId}
            onHide={() =>
              this.setState({ shownPlaylistId: null, shownPlaylistTitle: '' })
            }
          />
        )}

    );
  }

  onTagSubmit = selectedPlaylists => {
    const { onAddTags, contentId } = this.props;
    onAddTags({ tags: selectedPlaylists, type: 'video', contentId });
    this.setState(state => ({
      tagModalShown: false
    }));
  };
}

export default connect(state => ({
  canEditPlaylists: state.UserReducer.canEditPlaylists
}))(TagStatus);
<commit_msg>minor bug fix<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import PlaylistModal from 'components/Modals/PlaylistModal';
import TagModal from './TagModal';
import { hashify } from 'helpers/stringHelpers';
import { fetchPlaylistsContaining } from 'helpers/requestHelpers';
import { connect } from 'react-redux';
import { css } from 'emotion';
import { Color } from 'constants/css';

class TagStatus extends Component {
  mounted = false;
  state = {
    shownPlaylistId: null,
    shownPlaylistTitle: '',
    playlists: [],
    tagModalShown: false
  };

  static propTypes = {
    onAddTags: PropTypes.func.isRequired,
    onAddTagToContents: PropTypes.func,
    onLoadTags: PropTypes.func.isRequired,
    canEditPlaylists: PropTypes.bool,
    contentId: PropTypes.number.isRequired,
    style: PropTypes.object,
    tags: PropTypes.array.isRequired
  };

  async componentDidMount() {
    const { onLoadTags, contentId } = this.props;
    this.mounted = true;
    const playlists = await fetchPlaylistsContaining({ videoId: contentId });
    onLoadTags({ tags: playlists, contentId, type: 'video' });
  }

  async componentDidUpdate(prevProps) {
    const { onLoadTags, contentId } = this.props;
    if (contentId !== prevProps.contentId) {
      const playlists = await fetchPlaylistsContaining({ videoId: contentId });
      onLoadTags({ tags: playlists, contentId, type: 'video' });
    }
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  render() {
    const { shownPlaylistId, shownPlaylistTitle, tagModalShown } = this.state;
    const {
      onAddTagToContents,
      contentId,
      canEditPlaylists,
      style,
      tags
    } = this.props;
    return (

        {(tags.length > 0 || canEditPlaylists) && (

            {tags.map((tag, index) => (

                  this.setState({
                    shownPlaylistId: tag.id,
                    shownPlaylistTitle: tag.title
                  })
                }
              >
                {hashify(tag.title)}

            ))}
            {canEditPlaylists && (
               0 ? Color.orange() : Color.blue()
                }}
                onClick={() => this.setState({ tagModalShown: true })}
              >
                +Add
                {tags.length === 0 ? ' to Playlists' : ''}

            )}

        )}
        {tagModalShown && (
          <TagModal
            currentPlaylists={tags.map(tag => tag.id)}
            title="Add Video to Playlists"
            onHide={() => this.setState({ tagModalShown: false })}
            onAddPlaylist={({ videoIds, playlistId, playlistTitle }) =>
              onAddTagToContents?.({
                contentIds: videoIds,
                contentType: 'video',
                tagId: playlistId,
                tagTitle: playlistTitle
              })
            }
            onSubmit={this.onTagSubmit}
            videoId={contentId}
          />
        )}
        {shownPlaylistId && (
          <PlaylistModal
            onLinkClick={() => this.setState({ shownPlaylistId: null })}
            title={shownPlaylistTitle}
            playlistId={shownPlaylistId}
            onHide={() =>
              this.setState({ shownPlaylistId: null, shownPlaylistTitle: '' })
            }
          />
        )}

    );
  }

  onTagSubmit = selectedPlaylists => {
    const { onAddTags, contentId } = this.props;
    onAddTags({ tags: selectedPlaylists, type: 'video', contentId });
    this.setState(state => ({
      tagModalShown: false
    }));
  };
}

export default connect(state => ({
  canEditPlaylists: state.UserReducer.canEditPlaylists
}))(TagStatus);
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     495
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v496 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     496
// ==/UserScript==
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     348
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v349 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     349
// ==/UserScript==
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     274
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v275 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     275
// ==/UserScript==
<|endoftext|>


<commit_before>import { takeEvery } from 'redux-saga'
import { call, put, select} from 'redux-saga/effects'
import Promise from 'core-js/fn/promise'
import {inputData} from './data/inputData'
import {INITIAL_REQUEST, SIMULATION_UPDATE_SUCCESS} from './actions'
import {request} from './openfisca'

function* handleFormChange(action) {
	if (action.type === 'redux-form/CHANGE' && action.meta.field == 'codePostal')
		return // it is just an intermediary field used to input a codeINSEE

	try {
		let
			inputValues = Object.assign({},
				yield select(state => state.form.basicInput.values),
				yield select(state => state.form.advancedQuestions && state.form.advancedQuestions.values)
			),
			// Transform the raw input object to a new one tailored for the simulation API
			// 'inputData' provides this correspondance
			transformedValues = Object.keys(inputData).reduce((final, name) => {
				let transformer = inputData[name],
					userValue = inputValues[name]

				if (typeof transformer == 'string')
					return Object.assign(final, {[name]: transformer})

				if (userValue == null) return final

				if (typeof transformer === 'function')
					return Object.assign(final, transformer(userValue, inputValues))

				// else transformer is an Array and the function is the second element
				return Object.assign(final, transformer[1](userValue, inputValues))
			}, {}),

			results = yield call(request, transformedValues)
		// Used by the js API in openfisca.js
		window.Embauche.OpenFisca.input = transformedValues

		yield put({type: SIMULATION_UPDATE_SUCCESS, results})

	} catch (e) {
		console.log('ARGHH', e)
	}

}

function* watchFormChanges() {
	yield* takeEvery([ INITIAL_REQUEST, 'redux-form/CHANGE' ], handleFormChange)
}

export default function* rootSaga() {
	yield [ watchFormChanges() ]
}
<commit_msg>Debounce simulation requests by 500ms<commit_after>import { takeLatest } from 'redux-saga'
import { call, put, select} from 'redux-saga/effects'
import Promise from 'core-js/fn/promise'
import {inputData} from './data/inputData'
import {INITIAL_REQUEST, SIMULATION_UPDATE_SUCCESS} from './actions'
import {request} from './openfisca'

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))

function* handleFormChange(action) {
	if (action.type === 'redux-form/CHANGE' && action.meta.field == 'codePostal')
		return // it is just an intermediary field used to input a codeINSEE

	// debounce by 500ms
	yield call(delay, 500)

	try {
		let
			inputValues = Object.assign({},
				yield select(state => state.form.basicInput.values),
				yield select(state => state.form.advancedQuestions && state.form.advancedQuestions.values)
			),
			// Transform the raw input object to a new one tailored for the simulation API
			// 'inputData' provides this correspondance
			transformedValues = Object.keys(inputData).reduce((final, name) => {
				let transformer = inputData[name],
					userValue = inputValues[name]

				if (typeof transformer == 'string')
					return Object.assign(final, {[name]: transformer})

				if (userValue == null) return final

				if (typeof transformer === 'function')
					return Object.assign(final, transformer(userValue, inputValues))

				// else transformer is an Array and the function is the second element
				return Object.assign(final, transformer[1](userValue, inputValues))
			}, {}),

			results = yield call(request, transformedValues)
		// Used by the js API in openfisca.js
		window.Embauche.OpenFisca.input = transformedValues

		yield put({type: SIMULATION_UPDATE_SUCCESS, results})

	} catch (e) {
		console.log('ARGHH', e)
	}

}

function* watchFormChanges() {
	yield takeLatest([ INITIAL_REQUEST, 'redux-form/CHANGE' ], handleFormChange)
}

export default function* rootSaga() {
	yield [ watchFormChanges() ]
}
<|endoftext|>