<commit_before>/*
Mag.JS AddOns v0.21.9
(c) Michael Glazer 2016
https://github.com/magnumjs/mag.js
Requires: MagJS (core) Addons: Ajax , Router
*/

;
(function(mag, window, document, undefined) {

  'use strict';

  // helper
  mag.noop = function() {}

  //UTILITY
  // mag.utils.copy - now in core
  // mag.utils.merge - now in core

  mag.copy = mag.utils.copy
  mag.merge = mag.utils.merge

  /*
  controller:function(props)
    // if props has a property 'isVisible' when true will display entire module on before updates
    mag.show(this,'isVisible')
  }
  http://jsbin.com/fegizubime/edit?html,js,output
  */

  // show hide

  mag.show = function(context, conditionName) {
    var ctype = typeof context;
    if (ctype !== 'object') {
      var obj = {
        _config: function(node) {
          node.style.display = context ? 'block' : 'none'
        }
      }
      if (typeof conditionName == 'object') {
        mag.merge(conditionName, obj)
      }
      return obj
    } else if (ctype == 'object') {
      context.willload = function(event, node, inprops) {
        node.style.display = 'block'
      }
    } else {
      context.willupdate = function(event, node, inprops) {
        node.style.display = inprops[conditionName] ? 'block' : 'none'
      }
    }
  }

  // promises

  /*
  var d = mag.deferred()

  d.promise.then(function() {
    console.log('success', arguments)
  }, function() {
    console.log('failure', arguments)
  })
  d.reject({
    things: false
  })
  d.resolve({
    things: []
  })
  */

mag.deferred = function() {
  var Deferred = {}
  Deferred.promise = new Promise(function(resolve, reject) {
    Deferred.resolve = resolve
    Deferred.reject = reject
  })
  return Deferred
}

mag.when = function(arrayOfPromises, callback) {
  return Promise.all(arrayOfPromises).then(callback)
}

//TODO: add jsonp support

mag.request = function(options) {
  var deferred = mag.deferred();
  if (options.initialValue) {
    deferred.promise.initialValue = options.initialValue
  }
  //Uid:
  var key = JSON.stringify(options)
    //Cache:
  if (options.cache) {
    var cache = mag.cache(key)
    if (cache) {
      deferred.resolve(cache)
      return deferred.promise
    }
  }

  //In queue:
  if (mag.request.queue[key]) {
    return mag.request.queue[key]
  }
  //Add to queue
  mag.request.queue[key] = deferred.promise;

  var client = new XMLHttpRequest();
  var method = (options.method || 'GET').toUpperCase();
  var data = method === "GET" || !options.data ? "" : options.data

  client.onload = function(e) {
    var ct = client.getResponseHeader("content-type") || "";
    var data = e.target.responseText;
    if (ct.indexOf('json') > -1) {
      data = JSON.parse(data)
    }
    if (options.cache) {
      mag.cache(key, data, options.cacheTime, options.cacheType)
    }
    delete mag.request.queue[key];
    var headers = client.getAllResponseHeaders();
    deferred.resolve(data, headers);
  }

  client.onerror = function(e) {
    deferred.reject(e)
  };

  client.open(method, options.url);
    //Headers:
  if(options.headers){
    for(var i=0;i<options.headers.length;i++){
    	var header = options.headers[i]
      for(var k in header){
    	  client.setRequestHeader(k, header[k]);
      }
    }
  }
  client.send(data);

  return deferred.promise
}
mag.request.queue = {}

mag.cache = function(key, data, cacheTime, storageType) {
   var stype = window[storageType || 'sessionStorage']

  if (arguments.length == 1) {

    if (mag.cache.data[key]) {
      return mag.cache.data[key].data;
    } else {
      // check storage
      var item = stype.getItem(key)
      if (item) {
        mag.cache.data[key] = JSON.parse(item)
        return mag.cache.data[key].data
      }
      else return 0;
    }
  }

  if (mag.cache.data[key] && mag.cache.data[key].id) clearTimeout(mag.cache.data[key].id)

  var intervalID = setTimeout(function(key) {
    stype.removeItem(key);
    delete mag.cache.data[key]
  }.bind({}, key), cacheTime || 1000 * 60 * 10); //10 minutes

  mag.cache.data[key] = {
    id: intervalID,
    data: data
  }

  stype.setItem(key, JSON.stringify(mag.cache.data[key]))
}
mag.cache.data = {}

  // goes with mag.request for json ajax requests
  /*
  will show and hide all ''.loader' elements in transition
  */
  mag.requestWithFeedback = function(args) {
    var key = JSON.stringify(args)
    if (!mag.requestWithFeedback.cache[key]) {

      var loaders = document.querySelectorAll(".loader")

      //show icons
      for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "block"

      var expire = function(data) {
        delete mag.requestWithFeedback.cache[key]

        if (Object.keys(mag.requestWithFeedback.cache).length == 0) {
          //hide icons
          for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "none"
        }
        return data
      }
      mag.requestWithFeedback.cache[key] = mag.request(args)
      mag.requestWithFeedback.cache[key].then(expire, function(error) {
        expire(error)
        throw error
      })
    }
    return mag.requestWithFeedback.cache[key]
  }
  mag.requestWithFeedback.cache = {}

  // disable add to config
  // http://jsbin.com/zepacabile/edit?js,output
  // define condition func
  // this.show = mag.prop(false);

  //define context
  //   this.input = mag.disable(this.show)
  // OR mag.disable(this.show, this.input={})
  //TODO: change to mag.ui.disable
 mag.disable = function(condition, obj) {

  // add or merge to config if not exists
  var orig = obj && obj._config ? obj._config : function() {};

  var tmp = {
    _config: function(n) {
      orig.apply(this, arguments);
      if (condition()) n.setAttribute('disabled', 'disabled');
      else n.removeAttribute('disabled');
    }
  }
  if (obj) {
    mag.merge(obj, tmp);
  }
  return tmp;

}

  // TODO: change to mag.find
  // find a MagJS element Matcher
  mag.find = function(parentRootId, selector) {
    var parent = document.getElementById(parentRootId);
    return mag.fill.find(parent, selector)
  }

  // mag.addons.toMenu(['one', 'two', 'three'], state.selected())
  // http://jsbin.com/mapuwojumu/3/edit?js,output
  //TODO change to mag.ui.menu
  mag.toMenu = function(maps, selected) {
    return maps.map(function(v, k) {
      return {
        _text: v,
        _value: k,
        _selected: selected === k || selected === v ? true : null
      }
    })
  }

  /*
  module library creation with single global namespace / package names
  (function(namespace) {

    var mod = {
      view: function(state, props) {
      }
    }

    namespace.CommentBox = mod;

  })(mag.namespace('mglazer.mods.comments'));
  */
  mag.namespace = function(ns, obj) {
    var levels = ns.split('.'),
      first = levels.shift();
    obj = obj || this; //if no object argument supplied declare a global property
    obj[first] = obj[first] || {}; // initialize the "level"
    if (levels.length) { // recursion condition
      return mag.namespace(levels.join('.'), obj[first]);
    }
    return obj[first]; // return a reference to the top level object
  }

  //ROUTER:

  mag.route = (function(window) {

    'use strict';

    var routes = [];

    function addRoute(route, handler) {

      routes.push({
        parts: route.split('/'),
        handler: handler
      });
    }

    function load(route) {
      window.location.hash = route;
    }

    function start() {

      var path = window.location.hash.substr(1),
        parts = path.split('/'),
        partsLength = parts.length;

      for (var i = 0; i < routes.length; i++) {
        var route = routes[i];
        if (route.parts.length === partsLength) {
          var params = [];
          for (var j = 0; j < partsLength; j++) {
            if (route.parts[j].substr(0, 1) === ':') {
              params.push(parts[j]);
            } else if (route.parts[j] !== parts[j]) {
              break;
            }
          }
          if (j === partsLength) {
            route.handler.apply(undefined, params);
            return;
          }
        }
      }
    }

    window.onhashchange = start;

    return {
      addRoute: addRoute,
      load: load,
      start: start
    };

  }(window));

  //PLUGINS!
  // hookins

function forEach(obj, fn) {
  var keys = Object.keys(obj);
  for (var i = 0, l = keys.length; i < l; ++i) {
    var key = keys[i];
    fn(key, obj[key]);
  }
}

mag.hookin('attributes', 'key', function(data) {
  // remove system key from being added to attributes in html
  data.value = null
});

function camelDash (val) {
	return val.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}

mag.hookin('attributes', 'style', function(data) {

  //convert from object
  if (typeof data.value === 'object') {
    // get current styles not overwrite existing?

    var ostyle = data.node.getAttribute('style') || '';

    // make sure not already in styles
    var nstyle = ''
    forEach(data.value, function(key, value) {
     nstyle += camelDash(key) + ':' + (!isNaN(value) ? (value + "px") : value) + ';';
    });

    if(ostyle && ~ostyle.indexOf(nstyle)) data.value=ostyle;
    else data.value = ostyle+nstyle;

  }
});

 // _className plugin example
  mag.hookin('attributes', 'className', function(data) {
    data.key = 'class';

    var newClass = data.value
    var origClass = data.node.classList + ''

    if (typeof data.value == 'string') {
      newClass = data.value.split(' ')
    } else if (typeof data.value == 'object') {
      for (var k in newClass) {
        data.node.classList.toggle(k, newClass[k])
      }
      data.value = data.node.classList + ''
      return
    }

    data.value = data.node.classList + ''

    for (var k in newClass) {
      var cls = newClass[k]
      if (cls.trim() && data.node.classList.length > 0 && !data.node.classList.contains(cls.trim())) {
        data.value = data.node.classList + ' ' + cls

      } else if (cls.trim() && data.node.classList.length == 0) {
        data.value += cls + ' '
      }
    }

    if(data.value.trim() !== origClass) {
      data.change = 1;
    }
    data.value = data.value.trim()
  })

  //Values hookin:
mag.hookin('values', '*', function(data) {
  var dtype = typeof data.value

  if (data.value == null) {

  } else
  // allow function to return values
  if (dtype === 'function' && data.value.toJSON) {
    data.change = 1
    data.value = data.value()
  } else
  // Allow for promises to be resolved
  if (dtype == 'object' && typeof data.value.then == 'function') {
    var mid = mag.utils.items.getItem(mag.fill.id);

    data.value.then(function(newData) {
      mag.mod.getState(mid)[data.key] = newData;
      mag.redraw(mag.getNode(mag.fill.id), mid, 1);
    })

    if (data.value.initialValue) {
      data.change = 1
      data.value = data.value.initialValue
    }
  }
})

//GetId helper method
mag.getId =function(instanceId){
  return mag.utils.items.getItemVal(instanceId);
};

mag.prop = function(store) {
  var prop = function() {
    if (arguments.length) store = arguments[0];
    return store;
  };

  prop.toJSON = function() {
    return store;
  };

  return prop;
};

mag.withProp = function(prop, withAttrCallback) {
  return function(e) {
    e = e || event;
    var currentTarget = e.currentTarget || this;
    withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop))
  }
}

})(mag, window || global || this, document);
<commit_msg>Update mag.addons.0.2.js<commit_after>/*
Mag.JS AddOns v0.22.1
(c) Michael Glazer 2017
https://github.com/magnumjs/mag.js
Requires: MagJS (core) Addons: Ajax , Router
*/

;
(function(mag, window, document, undefined) {

	'use strict';

	// helper
	mag.noop = function() {}

	//UTILITY
	// mag.utils.copy - now in core
	// mag.utils.merge - now in core

	mag.copy = mag.utils.copy
	mag.merge = mag.utils.merge

	mag.isEmpty = function(x) {
			// undefined, '', null, 0, empty array
			if (!x || 0 == x.length) return true
			// empty {}
		  if ('Object]' == x.toString().substr(-7)) {
					for (var n in x)
							if (x.hasOwnProperty(n)) return false
					return true
			}
			// convert to string and match single non-space character
			return !(x + '').match(/S/)
	};

	/*
controller:function(props)
	// if props has a property 'isVisible' when true will display entire module on before updates
	mag.show(this,'isVisible')
}
http://jsbin.com/fegizubime/edit?html,js,output
*/

	// show hide

	mag.show = function(context, conditionName) {
			var ctype = typeof context;
			if (ctype !== 'object') {
					var obj = {
							_config: function(node) {
									node.style.display = context ? 'block' : 'none'
							}
					}
					if (typeof conditionName == 'object') {
							mag.merge(conditionName, obj)
					}
					return obj
			} else if (ctype == 'object') {
					context.willload = function(event, node, inprops) {
							node.style.display = 'block'
					}
			} else {
					context.willupdate = function(event, node, inprops) {
							node.style.display = inprops[conditionName] ? 'block' : 'none'
					}
			}
	}

	// promises

	/*
var d = mag.deferred()

d.promise.then(function() {
	console.log('success', arguments)
}, function() {
	console.log('failure', arguments)
})
d.reject({
	things: false
})
d.resolve({
	things: []
})
*/

	mag.deferred = function() {
			var Deferred = {}
			Deferred.promise = new Promise(function(resolve, reject) {
					Deferred.resolve = resolve
					Deferred.reject = reject
			})
			return Deferred
	}

	mag.when = function(arrayOfPromises, callback) {
			return Promise.all(arrayOfPromises).then(callback)
	}

	//TODO: add jsonp support

	mag.request = function(options) {
			var deferred = mag.deferred();
			if (options.initialValue) {
					deferred.promise.initialValue = options.initialValue
			}
			//Uid:
			var key = JSON.stringify(options)
			//Cache:
			if (options.cache) {
					var cache = mag.cache(key)
					if (cache) {
							deferred.resolve(cache)
							return deferred.promise
					}
			}

			//In queue:
			if (mag.request.queue[key]) {
					return mag.request.queue[key]
			}
			//Add to queue
			mag.request.queue[key] = deferred.promise;

			var client = new XMLHttpRequest();
			var method = (options.method || 'GET').toUpperCase();
			var data = method === "GET" || !options.data ? "" : options.data

			client.onload = function(e) {
					var ct = client.getResponseHeader("content-type") || "";
					var data = e.target.responseText;
					if (ct.indexOf('json') > -1) {
							data = JSON.parse(data)
					}
					if (options.cache) {
							mag.cache(key, data, options.cacheTime, options.cacheType)
					}
					delete mag.request.queue[key];
					var headers = client.getAllResponseHeaders();
					deferred.resolve(data, headers);
			}

			client.onerror = function(e) {
					deferred.reject(e)
			};

			client.open(method, options.url);
			//Headers:
			if (options.headers) {
					for (var i = 0; i < options.headers.length; i++) {
							var header = options.headers[i]
							for (var k in header) {
									client.setRequestHeader(k, header[k]);
							}
					}
			}
			client.send(data);

			return deferred.promise
	}
	mag.request.queue = {}

	mag.cache = function(key, data, cacheTime, storageType) {
			var stype = window[storageType || 'sessionStorage']

			if (arguments.length == 1) {

					if (mag.cache.data[key]) {
							return mag.cache.data[key].data;
					} else {
							// check storage
							var item = stype.getItem(key)
							if (item) {
									mag.cache.data[key] = JSON.parse(item)
									return mag.cache.data[key].data
							} else return 0;
					}
			}

			if (mag.cache.data[key] && mag.cache.data[key].id) clearTimeout(mag.cache.data[key].id)

			var intervalID = setTimeout(function(key) {
					stype.removeItem(key);
					delete mag.cache.data[key]
			}.bind({}, key), cacheTime || 1000 * 60 * 10); //10 minutes

			mag.cache.data[key] = {
					id: intervalID,
					data: data
			}

			stype.setItem(key, JSON.stringify(mag.cache.data[key]))
	}
	mag.cache.data = {}

	// goes with mag.request for json ajax requests
	/*
will show and hide all ''.loader' elements in transition
*/
	mag.requestWithFeedback = function(args) {
			var key = JSON.stringify(args)
			if (!mag.requestWithFeedback.cache[key]) {

					var loaders = document.querySelectorAll(".loader")

					//show icons
					for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "block"

					var expire = function(data) {
							delete mag.requestWithFeedback.cache[key]

							if (Object.keys(mag.requestWithFeedback.cache).length == 0) {
									//hide icons
									for (var i = 0, loader; loader = loaders[i]; i++) loader.style.display = "none"
							}
							return data
					}
					mag.requestWithFeedback.cache[key] = mag.request(args)
					mag.requestWithFeedback.cache[key].then(expire, function(error) {
							expire(error)
							throw error
					})
			}
			return mag.requestWithFeedback.cache[key]
	}
	mag.requestWithFeedback.cache = {}

	// disable add to config
	// http://jsbin.com/zepacabile/edit?js,output
	// define condition func
	// this.show = mag.prop(false);

	//define context
	//   this.input = mag.disable(this.show)
	// OR mag.disable(this.show, this.input={})
	//TODO: change to mag.ui.disable
	mag.disable = function(condition, obj) {

			// add or merge to config if not exists
			var orig = obj && obj._config ? obj._config : function() {};

			var tmp = {
					_config: function(n) {
							orig.apply(this, arguments);
							if (condition()) n.setAttribute('disabled', 'disabled');
							else n.removeAttribute('disabled');
					}
			}
			if (obj) {
					mag.merge(obj, tmp);
			}
			return tmp;

	}

	// TODO: change to mag.find
	// find a MagJS element Matcher
	mag.find = function(parentRootId, selector) {
			var parent = document.getElementById(parentRootId);
			return mag.fill.find(parent, selector)
	}

	// mag.addons.toMenu(['one', 'two', 'three'], state.selected())
	// http://jsbin.com/mapuwojumu/3/edit?js,output
	//TODO change to mag.ui.menu
	mag.toMenu = function(maps, selected) {
			return maps.map(function(v, k) {
					return {
							_text: v,
							_value: k,
							_selected: selected === k || selected === v ? true : null
					}
			})
	}

	/*
module library creation with single global namespace / package names
(function(namespace) {

	var mod = {
		view: function(state, props) {
		}
	}

	namespace.CommentBox = mod;

})(mag.namespace('mglazer.mods.comments'));
*/
	mag.namespace = function(ns, obj) {
			var levels = ns.split('.'),
					first = levels.shift();
			obj = obj || this; //if no object argument supplied declare a global property
			obj[first] = obj[first] || {}; // initialize the "level"
			if (levels.length) { // recursion condition
					return mag.namespace(levels.join('.'), obj[first]);
			}
			return obj[first]; // return a reference to the top level object
	}

	//ROUTER:

	mag.route = (function(window) {

			'use strict';

			var routes = [];

			function addRoute(route, handler) {

					routes.push({
							parts: route.split('/'),
							handler: handler
					});
			}

			function load(route) {
					window.location.hash = route;
			}

			function start() {

					var path = window.location.hash.substr(1),
							parts = path.split('/'),
							partsLength = parts.length;

					for (var i = 0; i < routes.length; i++) {
							var route = routes[i];
							if (route.parts.length === partsLength) {
									var params = [];
									for (var j = 0; j < partsLength; j++) {
											if (route.parts[j].substr(0, 1) === ':') {
													params.push(parts[j]);
											} else if (route.parts[j] !== parts[j]) {
													break;
											}
									}
									if (j === partsLength) {
											route.handler.apply(undefined, params);
											return;
									}
							}
					}
			}

			window.onhashchange = start;

			return {
					addRoute: addRoute,
					load: load,
					start: start
			};

	}(window));

	//PLUGINS!
	// hookins

	function forEach(obj, fn) {
			var keys = Object.keys(obj);
			for (var i = 0, l = keys.length; i < l; ++i) {
					var key = keys[i];
					fn(key, obj[key]);
			}
	}

	mag.hookin('attributes', 'key', function(data) {
			// remove system key from being added to attributes in html
			data.value = null
	});

	function camelDash(val) {
			return val.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
	}

	mag.hookin('attributes', 'style', function(data) {

			//convert from object
			if (typeof data.value === 'object') {
					// get current styles not overwrite existing?

					var ostyle = data.node.getAttribute('style') || '';

					// make sure not already in styles
					var nstyle = ''
					forEach(data.value, function(key, value) {
							nstyle += camelDash(key) + ':' + (!isNaN(value) ? (value + "px") : value) + ';';
					});

					if (ostyle && ~ostyle.indexOf(nstyle)) data.value = ostyle;
					else data.value = ostyle + nstyle;

			}
	});

	// _className plugin example
	mag.hookin('attributes', 'className', function(data) {
			data.key = 'class';

			var newClass = data.value
			var origClass = data.node.classList + ''

			if (typeof data.value == 'string') {
					newClass = data.value.split(' ')
			} else if (typeof data.value == 'object') {
					for (var k in newClass) {
							data.node.classList.toggle(k, newClass[k])
					}
					data.value = data.node.classList + ''
					return
			}

			data.value = data.node.classList + ''

			for (var k in newClass) {
					var cls = newClass[k]
					if (cls.trim() && data.node.classList.length > 0 && !data.node.classList.contains(cls.trim())) {
							data.value = data.node.classList + ' ' + cls

					} else if (cls.trim() && data.node.classList.length == 0) {
							data.value += cls + ' '
					}
			}

			if (data.value.trim() !== origClass) {
					data.change = 1;
			}
			data.value = data.value.trim()
	})

	//Values hookin:
	mag.hookin('values', '*', function(data) {
			var dtype = typeof data.value

			if (data.value == null) {

			} else
			// allow function to return values
			if (dtype === 'function' && data.value.toJSON) {
					data.change = 1
					data.value = data.value()
			} else
			// Allow for promises to be resolved
			if (dtype == 'object' && typeof data.value.then == 'function') {
					var mid = mag.utils.items.getItem(mag.fill.id);

					data.value.then(function(newData) {
							mag.mod.getState(mid)[data.key] = newData;
							mag.redraw(mag.getNode(mag.fill.id), mid, 1);
					})

					if (data.value.initialValue) {
							data.change = 1
							data.value = data.value.initialValue
					}
			}
	})

	//GetId helper method
	mag.getId = function(instanceId) {
			return mag.utils.items.getItemVal(instanceId);
	};

	mag.prop = function(store) {
			var prop = function() {
					if (arguments.length) store = arguments[0];
					return store;
			};

			prop.toJSON = function() {
					return store;
			};

			return prop;
	};

	mag.withProp = function(prop, withAttrCallback) {
			return function(e) {
					e = e || event;
					var currentTarget = e.currentTarget || this;
					withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop))
			}
	}

})(mag, window || global || this, document);
<|endoftext|>


<commit_before>function updateFromIri(iri) {
  fuseki( "metadata", iri );
  fuseki( "people", iri);
}

var _FUSEKI_URLS = ["http://seweb.abdn.ac.uk/fuseki/ds/query", "http://localhost:3030/ds/query"];
var fuseki = function(template_key, iri) { console.log("Error - not initialized"); };

function initFuseki() {
  if ("file:" === $(location).attr('protocol')) { //Local testing
    _FUSEKI_URLS.reverse(); //Prefer localhost to server
  }
  fuseki = function(template_key, iri) {
    fusekiCall(
        _FUSEKI_URLS[0],
        template_key,
        iri,
        function () {
          fusekiCall(
              _FUSEKI_URLS[1],
              template_key,
              iri,
              function () {
                console.log( "No response to " + template_key + " query for " + iri );
                $( "#myAjaxAlert" ).removeClass( "hide" );
              }
          );
        });
  };
}

function fusekiCall(fusekiUrl, template_key, iri, errorCallback) {
  var q = $( "body" ).data( "sparql-" + template_key).query.replace(/--IRI--/g, iri);
  $.ajax({
    url: fusekiUrl,
    data: {
      "query" : q},
    dataType: 'json',
    success: $( "body" ).data( "sparql-" + template_key).callback,
    timeout: 2500,
    error: errorCallback,
  });
}

function register(templateName, lines, callback) {
  $( "body" ).data( "sparql-" + templateName, {"query": _PREAMBLE.concat(lines).join("\n"), "callback": callback});
}

$( document ).ready( function() {
  initFuseki();
  register_all_sparql_queries();
  indexJsInit();
  updateFromIri( "https://catalogue.ukeof.org.uk/id/412d0ac7-5b16-4ba9-b72f-4b6da92310eb" );
});

function _valuesOfSparqlBinding( sparqlBinding ) {
  var result = {};
  for (key in sparqlBinding) {
    result[key] = sparqlBinding[key].value;
  }
  return result;
}

_PREAMBLE = [
             "BASE <http://dot.rural/sepake/>",
             "PREFIX rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>",
             "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>",
             "PREFIX prov: <http://www.w3.org/ns/prov#>",
             "PREFIX foaf: <http://xmlns.com/foaf/0.1/>",
             ];

function register_all_sparql_queries() {
  register(
      "metadata",
      [
       "SELECT * WHERE {",
       "    BIND (<--IRI--> AS ?focus) .",
       "    {?focus rdfs:label ?label} .",
       "    OPTIONAL {?focus rdfs:comment ?comment} .",
       "    OPTIONAL {?focus foaf:homepage ?homepage} .",
       "    OPTIONAL {?focus prov:startedAtTime ?startedAtTime} .",
       "    OPTIONAL {?focus prov:endedAtTime ?endedAtTime} .",
       "    OPTIONAL {?owner <owns> ?focus .",
       "               ?owner rdfs:label ?ownerLabel .",
       "               ?owner foaf:homepage ?ownerHomepage} .",
       "}",
       "LIMIT 1",
      ],
      function (response) {
        try{
          var values = _valuesOfSparqlBinding(response.results.bindings[0]);
          $( "#labelOfFocus" ).text(values.label);
          $( "#commentOfFocus" ).html(values.comment.split("\n").join("

") || "(No summary)");
          $( "#homepageOfFocus" ).text(values.homepage || "");
          $( "#homepageOfFocus" ).attr("href", values.homepage || "");
          $( "#startedAtTime" ).text(values.startedAtTime || "(unknown)");
          $( "#endedAtTime" ).text(values.endedAtTime || "(unknown)");
          $( "#labelOfOwner" ).text(values.ownerLabel || "");
          $( "#labelOfOwner" ).attr('href', values.ownerHomepage || "");
        } catch (err) {
          console.log( err );
        }
      });
  register(
      "people",
      [
       "SELECT * WHERE {",
       "    BIND (<--IRI--> AS ?focus) .",
       "    {?person prov:memberOf ?focus} .",
       "    {?person rdf:type prov:Person} .",
       "    {?person rdf:type foaf:Person} .",
       "    {?person foaf:givenName ?given} .",
       "    {?person foaf:familyName ?family} .",
       "    {?person foaf:mbox ?mbox} .",
       "    BIND (CONCAT(?given, ' ', ?family) AS ?label)",
       "}",
       "ORDER BY ASC(?family)",
       "LIMIT 11",
      ],
      function (response) {
        var bindings = response.results.bindings;
        $( "#personList .personWrapper" ).each( function(index, dom_elem) {//TODO: #personList -> .personList to allow several
          if (bindings.length > 0) {
            $( dom_elem ).data( bindings.shift() );
            $( dom_elem ).find( ".personLink" ).text( $( dom_elem ).data().label.value );
            $( dom_elem ).find( ".personLink" ).attr('title', $( dom_elem ).data().mbox.value );
            $( dom_elem ).removeClass( "noData" );
          } else {
            $( dom_elem ).addClass( "noData" );
          }
        });
        if (bindings.length > 0) { //More results to display
          $( "#personList" ).removeClass( "noMore" );
        } else {
          $( "#personList" ).addClass( "noMore" );
        }
      });
  register(
      "searchables",
      [
       "SELECT * WHERE {",//TODO: Get type, but only one record per id, maybe using #3
       "    {?id rdfs:label ?label} .",
       "    {?id rdfs:comment ?comment} .",
       "}",
      ],
      function (response) {
        $( "#search" ).trigger( "preloaded", {items: $.map( response.results.bindings, function(binding, index) {
          return _valuesOfSparqlBinding(binding);
        })});
      });
}
<commit_msg>Do not fail over to remote host when testing locally For #14-ukeof<commit_after>function updateFromIri(iri) {
  fuseki( "metadata", iri );
  fuseki( "people", iri);
}

var _FUSEKI_URLS = {
    "http:" : "http://seweb.abdn.ac.uk/fuseki/ds/query",
    "file:" : "http://localhost:3030/ds/query"};

var fuseki = function(template_key, iri) { console.log("Error - not initialized"); };

function initFuseki() {
  fuseki = function(template_key, iri) {
    fusekiCall(
        _FUSEKI_URLS[$(location).attr('protocol')],
        template_key,
        iri,
        function () {
          console.log( "No response to " + template_key + " query for " + iri );
          $( "#myAjaxAlert" ).removeClass( "hide" );
        });
  };
}

function fusekiCall(fusekiUrl, template_key, iri, errorCallback) {
  var q = $( "body" ).data( "sparql-" + template_key).query.replace(/--IRI--/g, iri);
  $.ajax({
    url: fusekiUrl,
    data: {
      "query" : q},
    dataType: 'json',
    success: $( "body" ).data( "sparql-" + template_key).callback,
    timeout: 2500,
    error: errorCallback,
  });
}

function register(templateName, lines, callback) {
  $( "body" ).data( "sparql-" + templateName, {"query": _PREAMBLE.concat(lines).join("\n"), "callback": callback});
}

$( document ).ready( function() {
  initFuseki();
  register_all_sparql_queries();
  indexJsInit();
  updateFromIri( "https://catalogue.ukeof.org.uk/id/412d0ac7-5b16-4ba9-b72f-4b6da92310eb" );
});

function _valuesOfSparqlBinding( sparqlBinding ) {
  var result = {};
  for (key in sparqlBinding) {
    result[key] = sparqlBinding[key].value;
  }
  return result;
}

_PREAMBLE = [
             "BASE <http://dot.rural/sepake/>",
             "PREFIX rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>",
             "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>",
             "PREFIX prov: <http://www.w3.org/ns/prov#>",
             "PREFIX foaf: <http://xmlns.com/foaf/0.1/>",
             ];

function register_all_sparql_queries() {
  register(
      "metadata",
      [
       "SELECT * WHERE {",
       "    BIND (<--IRI--> AS ?focus) .",
       "    {?focus rdfs:label ?label} .",
       "    OPTIONAL {?focus rdfs:comment ?comment} .",
       "    OPTIONAL {?focus foaf:homepage ?homepage} .",
       "    OPTIONAL {?focus prov:startedAtTime ?startedAtTime} .",
       "    OPTIONAL {?focus prov:endedAtTime ?endedAtTime} .",
       "    OPTIONAL {?owner <owns> ?focus .",
       "               ?owner rdfs:label ?ownerLabel .",
       "               ?owner foaf:homepage ?ownerHomepage} .",
       "}",
       "LIMIT 1",
      ],
      function (response) {
        try{
          var values = _valuesOfSparqlBinding(response.results.bindings[0]);
          $( "#labelOfFocus" ).text(values.label);
          $( "#commentOfFocus" ).html(values.comment.split("\n").join("

") || "(No summary)");
          $( "#homepageOfFocus" ).text(values.homepage || "");
          $( "#homepageOfFocus" ).attr("href", values.homepage || "");
          $( "#startedAtTime" ).text(values.startedAtTime || "(unknown)");
          $( "#endedAtTime" ).text(values.endedAtTime || "(unknown)");
          $( "#labelOfOwner" ).text(values.ownerLabel || "");
          $( "#labelOfOwner" ).attr('href', values.ownerHomepage || "");
        } catch (err) {
          console.log( err );
        }
      });
  register(
      "people",
      [
       "SELECT * WHERE {",
       "    BIND (<--IRI--> AS ?focus) .",
       "    {?person prov:memberOf ?focus} .",
       "    {?person rdf:type prov:Person} .",
       "    {?person rdf:type foaf:Person} .",
       "    {?person foaf:givenName ?given} .",
       "    {?person foaf:familyName ?family} .",
       "    {?person foaf:mbox ?mbox} .",
       "    BIND (CONCAT(?given, ' ', ?family) AS ?label)",
       "}",
       "ORDER BY ASC(?family)",
       "LIMIT 11",
      ],
      function (response) {
        var bindings = response.results.bindings;
        $( "#personList .personWrapper" ).each( function(index, dom_elem) {//TODO: #personList -> .personList to allow several
          if (bindings.length > 0) {
            $( dom_elem ).data( bindings.shift() );
            $( dom_elem ).find( ".personLink" ).text( $( dom_elem ).data().label.value );
            $( dom_elem ).find( ".personLink" ).attr('title', $( dom_elem ).data().mbox.value );
            $( dom_elem ).removeClass( "noData" );
          } else {
            $( dom_elem ).addClass( "noData" );
          }
        });
        if (bindings.length > 0) { //More results to display
          $( "#personList" ).removeClass( "noMore" );
        } else {
          $( "#personList" ).addClass( "noMore" );
        }
      });
  register(
      "searchables",
      [
       "SELECT * WHERE {",//TODO: Get type, but only one record per id, maybe using #3
       "    {?id rdfs:label ?label} .",
       "    {?id rdfs:comment ?comment} .",
       "}",
      ],
      function (response) {
        $( "#search" ).trigger( "preloaded", {items: $.map( response.results.bindings, function(binding, index) {
          return _valuesOfSparqlBinding(binding);
        })});
      });
}
<|endoftext|>


<commit_before>// @flow

type Privs = { buildPromise?: Promise<Object[]> }

import _ from 'lodash';
import Bluebird from 'bluebird';
import Yaml from 'js-yaml';
import Glob from 'glob';
import Logquacious from 'logquacious';

import FS from 'fs-extra';
import Path from 'path';
import Util from 'util';

import Definition from './definition';
import Handlebars from './handlebars';
import HandlebarsContext from './handlebarsContext';

const logger = new Logquacious('symvasi');

Bluebird.promisifyAll(FS);
const GlobAsync = Bluebird.promisify(Glob);

// Declare the private dataset.
const privData = new WeakMap();
function priv(ctx): Privs { return privData.get(ctx); }

type ParsedBuild = {
    targets: Object[],
    definitions: Definition[],
    output: string
}

async function parseBuildsAsync(builds: Object[], targetsPath: string, templatesPath: string): Promise<ParsedBuild[]> {
    let targets = _(builds)
        .map(b => b.targets)
        .flatten()
        .value();

    let compiledTargets = await Bluebird.map(targets, async (target) => {
        let targetPath = Path.resolve(targetsPath, target);

        let handlebars = HandlebarsContext();

        let targetParamsPath = Path.resolve(targetPath, 'params.yml');
        let targetParams = await FS.readFileAsync(targetParamsPath, 'utf8').then(content => Yaml.safeLoad(content));

        let partialsPath = Path.resolve(targetPath, 'partials');
        let partialsGlobPath = Path.resolve(partialsPath, '*.tpl');

        let partialsFilesPaths = await GlobAsync(partialsGlobPath);
        await Bluebird.map(partialsFilesPaths, async (partialFilePath) => {
            let partialPath = Path.relative(partialsPath, partialFilePath);
            partialPath = Path.basename(partialPath, '.tpl');

            await FS.readFileAsync(partialFilePath, 'utf8').then(content => handlebars.registerPartial(partialPath, content));
        });

        let outputsPath = Path.resolve(targetPath, 'outputs');
        let outputsGlobPath = Path.resolve(outputsPath, '**/*.tpl');

        let outputFilesPaths = await GlobAsync(outputsGlobPath);

        let compiledOutputs = await Bluebird.map(outputFilesPaths, async (outputFilePath) => {
            let compiledOutput = await FS.readFileAsync(outputFilePath, 'utf8').then(content => handlebars.compile(content));

            let outputPath = Path.relative(outputsPath, outputFilePath);
            outputPath = outputPath.slice(0, 0 - Path.basename(outputFilePath).length);

            let outputName = Path.relative(outputsPath, outputFilePath);
            outputName = outputName.slice(0, -4);

            // let outputName = Path.basename(outputFilePath, '.tpl');
            // if (outputName === 'main') { outputName = ''; }

            return [ outputName, {
                path: outputPath,
                compiledTemplate: compiledOutput
            }];
        }).then(_.fromPairs);

        return [ target, {
            params: targetParams,
            outputTemplates: compiledOutputs
        }];
    }).then(_.fromPairs);

    let parsedBuilds = await Bluebird.map(builds, async (build) => {
        let { targets, options, templates, output } = build;

        let templatePaths = await Bluebird.map(templates, async (template) => {
            let globPath = Path.resolve(templatesPath, template);

            return await GlobAsync(globPath);
        }).then(_.flatten);

        let parsedDefinitions: Definition[] = await Bluebird.map(templatePaths, async (templatePath) => {
            let templateParams = await FS.readFileAsync(templatePath, 'utf8').then(content => Yaml.safeLoad(content));

            let templateName = Path.basename(templatePath, '.yml');

            return new Definition(templateName, templateParams, options);
        });

        let parsedTargets = _.map(targets, t => compiledTargets[t]);

        let parsedOutput = Path.resolve(process.cwd(), output);

        return {
            targets: parsedTargets,
            definitions: parsedDefinitions,
            output: parsedOutput
        };
    });

    return parsedBuilds;
}

export default class Generator {
    constructor(manifest: Object, targetsPath: string, templatesPath: string) {
        privData.set(this, {});

        let { builds } = manifest;

        logger.debug('Parsing builds...');
        priv(this).buildPromise = parseBuildsAsync.call(this, builds, targetsPath, templatesPath);
    }

    async generateAsync(): Promise<any> {
        let builds: ?ParsedBuild[] = await priv(this).buildPromise;

        let handlebars = HandlebarsContext();

        await Bluebird.map(builds, async (build: ParsedBuild) => {
            let { targets, definitions, output } = build;

            await Bluebird.map(targets, async (target) => {
                let { outputs: targetOutputs } = target.params;

                await Bluebird.map(targetOutputs, async (targetOutput) => {
                    let outputTemplate = target.outputTemplates[targetOutput.templateName];
                    if (!outputTemplate) {
                        throw new Error(`Output template '${targetOutput.templateName}' not defined`);
                    }

                    await Bluebird.map(definitions, async (definition: Definition) => {
                        let context = definition;
                        if (targetOutput.context) {
                            context = _.get(context, targetOutput.context);
                            if (!context) {
                                throw new Error(`Context "${targetOutput.context}" failed to resolve`);
                            }
                        }

                        if (_.isArray(context)) {
                            await Bluebird.map(context, async (itemContext) => {
                                let normalizedContext = definition.resolveContext(itemContext);

                                let outputPathTemplate = handlebars.compile(targetOutput.path);
                                let outputPath = Path.resolve(output, outputPathTemplate(normalizedContext));

                                logger.info(`Generating output for ${outputPath}...`);

                                let generatedFile = outputTemplate.compiledTemplate(normalizedContext);
                                await FS.outputFileAsync(outputPath, generatedFile);
                            });
                        }
                        else {
                            let normalizedContext = definition.resolveContext(context);

                            let outputPathTemplate = handlebars.compile(targetOutput.path);
                            let outputPath = Path.resolve(output, outputPathTemplate(normalizedContext));

                            logger.info(`Generating output for ${outputPath}...`);

                            let generatedFile = outputTemplate.compiledTemplate(normalizedContext);
                            await FS.outputFileAsync(outputPath, generatedFile);
                        }
                    });
                });
            });
        });
    }
}<commit_msg>Added support for definition augmentations<commit_after>// @flow

type Privs = { buildPromise?: Promise<Object[]> }

import _ from 'lodash';
import Bluebird from 'bluebird';
import Yaml from 'js-yaml';
import Glob from 'glob';
import Logquacious from 'logquacious';

import FS from 'fs-extra';
import Path from 'path';
import Util from 'util';

import Definition from './definition';
import Handlebars from './handlebars';
import HandlebarsContext from './handlebarsContext';

const logger = new Logquacious('symvasi');

Bluebird.promisifyAll(FS);
const GlobAsync = Bluebird.promisify(Glob);

// Declare the private dataset.
const privData = new WeakMap();
function priv(ctx): Privs { return privData.get(ctx); }

type ParsedBuild = {
    targets: Object[],
    definitions: Definition[],
    augmentations: Definition[],
    output: string
}

async function parseBuildsAsync(builds: Object[], targetsPath: string, templatesPath: string): Promise<ParsedBuild[]> {
    let targets = _(builds)
        .map(b => b.targets)
        .flatten()
        .value();

    let compiledTargets = await Bluebird.map(targets, async (target) => {
        let targetPath = Path.resolve(targetsPath, target);

        let handlebars = HandlebarsContext();

        let targetParamsPath = Path.resolve(targetPath, 'params.yml');
        let targetParams = await FS.readFileAsync(targetParamsPath, 'utf8').then(content => Yaml.safeLoad(content));

        let partialsPath = Path.resolve(targetPath, 'partials');
        let partialsGlobPath = Path.resolve(partialsPath, '*.tpl');

        let partialsFilesPaths = await GlobAsync(partialsGlobPath);
        await Bluebird.map(partialsFilesPaths, async (partialFilePath) => {
            let partialPath = Path.relative(partialsPath, partialFilePath);
            partialPath = Path.basename(partialPath, '.tpl');

            await FS.readFileAsync(partialFilePath, 'utf8').then(content => handlebars.registerPartial(partialPath, content));
        });

        let outputsPath = Path.resolve(targetPath, 'outputs');
        let outputsGlobPath = Path.resolve(outputsPath, '**/*.tpl');

        let outputFilesPaths = await GlobAsync(outputsGlobPath);

        let compiledOutputs = await Bluebird.map(outputFilesPaths, async (outputFilePath) => {
            let compiledOutput = await FS.readFileAsync(outputFilePath, 'utf8').then(content => handlebars.compile(content));

            let outputPath = Path.relative(outputsPath, outputFilePath);
            outputPath = outputPath.slice(0, 0 - Path.basename(outputFilePath).length);

            let outputName = Path.relative(outputsPath, outputFilePath);
            outputName = outputName.slice(0, -4);

            // let outputName = Path.basename(outputFilePath, '.tpl');
            // if (outputName === 'main') { outputName = ''; }

            return [ outputName, {
                path: outputPath,
                compiledTemplate: compiledOutput
            }];
        }).then(_.fromPairs);

        return [ target, {
            params: targetParams,
            outputTemplates: compiledOutputs
        }];
    }).then(_.fromPairs);

    let parsedBuilds = await Bluebird.map(builds, async (build) => {
        let { targets, options, templates, augmentations, output } = build;

        let parsedAugmentations = {};
        let augmentationPaths = await Bluebird.map(augmentations, async (augmentation) => {
            let globPath = Path.resolve(templatesPath, augmentation);

            return await GlobAsync(globPath);
        }).then(_.flatten);
        await Bluebird.map(augmentationPaths, async (augmentationPath) => {
            let augmentationParams = await FS.readFileAsync(augmentationPath, 'utf8').then(content => Yaml.safeLoad(content));
            let augmentationName = Path.basename(augmentationPath, '.yml');
            let fqn = augmentationParams.fqn || augmentationName;

            parsedAugmentations[fqn] = augmentationParams;
        });

        let templatePaths = await Bluebird.map(templates, async (template) => {
            let globPath = Path.resolve(templatesPath, template);

            return await GlobAsync(globPath);
        }).then(_.flatten);
        let parsedDefinitions: Definition[] = await Bluebird.map(templatePaths, async (templatePath) => {
            let templateParams = await FS.readFileAsync(templatePath, 'utf8').then(content => Yaml.safeLoad(content));
            let templateName = Path.basename(templatePath, '.yml');
            let fqn = templateParams.fqn || templateName;

            if (parsedAugmentations[fqn]) {
                let augmentation = parsedAugmentations[fqn];

                _.each(augmentation.models, (augmentedModel) => {
                    let model = _.find(templateParams.models, model => model.name === augmentedModel.name);

                    model.methods = model.methods || [];
                    for (let augmentedMethod of augmentedModel.methods)
                        model.methods.push(augmentedMethod);
                });
            }

            return new Definition(templateName, templateParams, options);
        });

        let parsedTargets = _.map(targets, t => compiledTargets[t]);

        let parsedOutput = Path.resolve(process.cwd(), output);

        return {
            targets: parsedTargets,
            definitions: parsedDefinitions,
            augmentations: null,
            output: parsedOutput
        };
    });

    return parsedBuilds;
}

export default class Generator {
    constructor(manifest: Object, targetsPath: string, templatesPath: string) {
        privData.set(this, {});

        let { builds } = manifest;

        logger.debug('Parsing builds...');
        priv(this).buildPromise = parseBuildsAsync.call(this, builds, targetsPath, templatesPath);
    }

    async generateAsync(): Promise<any> {
        let builds: ?ParsedBuild[] = await priv(this).buildPromise;

        let handlebars = HandlebarsContext();

        await Bluebird.map(builds, async (build: ParsedBuild) => {
            let { targets, definitions, output } = build;

            await Bluebird.map(targets, async (target) => {
                let { outputs: targetOutputs } = target.params;

                await Bluebird.map(targetOutputs, async (targetOutput) => {
                    let outputTemplate = target.outputTemplates[targetOutput.templateName];
                    if (!outputTemplate) {
                        throw new Error(`Output template '${targetOutput.templateName}' not defined`);
                    }

                    await Bluebird.map(definitions, async (definition: Definition) => {
                        let context = definition;
                        if (targetOutput.context) {
                            context = _.get(context, targetOutput.context);
                            if (!context) {
                                throw new Error(`Context "${targetOutput.context}" failed to resolve`);
                            }
                        }

                        if (_.isArray(context)) {
                            await Bluebird.map(context, async (itemContext) => {
                                let normalizedContext = definition.resolveContext(itemContext);

                                let outputPathTemplate = handlebars.compile(targetOutput.path);
                                let outputPath = Path.resolve(output, outputPathTemplate(normalizedContext));

                                logger.info(`Generating output for ${outputPath}...`);

                                let generatedFile = outputTemplate.compiledTemplate(normalizedContext);
                                await FS.outputFileAsync(outputPath, generatedFile);
                            });
                        }
                        else {
                            let normalizedContext = definition.resolveContext(context);

                            let outputPathTemplate = handlebars.compile(targetOutput.path);
                            let outputPath = Path.resolve(output, outputPathTemplate(normalizedContext));

                            logger.info(`Generating output for ${outputPath}...`);

                            let generatedFile = outputTemplate.compiledTemplate(normalizedContext);
                            await FS.outputFileAsync(outputPath, generatedFile);
                        }
                    });
                });
            });
        });
    }
}<|endoftext|>


<commit_before>import VPatch from './vpatch'
import { isVNode, isVText, isWidget, isStateLess, isHook } from './vnode/types'
import { isFunction, isObject, getPrototype } from '~'

function diff (a, b) {
  let patches = {old: a}
  walk(a, b, patches, 0)
  return patches
}

function walk (a, b, patches, index) {
  if (a === b) {
    return
  }
  let apply = patches[index]
  let applyClear = false
  if (!b) {
    if (!isWidget(a)) {
      clearState(a, patches, index)
      apply = patches[index]
    }
    apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, null))
  } else if (isVText(b)) {
    if (!isVText(a)) {
      applyClear = true
      apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
    } else if (a.text !== b.text) {
      apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
    }
  } else if (isVNode(b)) {
    if (!isVNode(a)) {
      apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
      applyClear = true
    } else if (a.tagName === b.tagName && a.key === b.key) {
      const propsPatch = diffProps(a.props, b.props)
      if (propsPatch) {
        apply = appendPatch(apply, new VPatch(VPatch.PROPS, a, propsPatch))
      }
      apply = diffChildren(a, b, apply, patches, index)
    } else {
      apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
      applyClear = true
    }
  } else if (isWidget(b)) {
    if (!isWidget(a)) {
      applyClear = true
    }
    apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))
  } else if (Array.isArray(b)) {
    applyClear = true
    b.forEach(item => {
      walk(null, item, patches, index)
      index++
    })
  } else if (isStateLess(b)) {
    applyClear = true
    apply = appendPatch(apply, new VPatch(VPatch.STATELESS, a, b))
  }
  if (apply) {
    patches[index] = apply
  }
  if (applyClear) {
    clearState(a, patches, index)
  }
}

function diffProps (propsA, propsB) {
  let diff = null
  for (let key in propsA) {
    if (!propsB.hasOwnProperty(key)) {
      diff = diff || {}
      diff[key] = undefined
    }
    let aValue = propsA[key]
    let bValue = propsB[key]
    if (aValue === bValue) {
      continue
    } else if (isObject(aValue) && isObject(bValue)) {
      if (getPrototype(aValue) !== getPrototype(bValue)) {
        diff = diff || {}
        diff[key] = bValue
      } else if (isHook(bValue)) {
        diff = diff || {}
        diff[key] = bValue
      } else {
        let objDiff = diffProps(aValue, bValue)
        if (objDiff) {
          diff = diff || {}
          diff[key] = objDiff
        }
      }
    } else {
      diff = diff || {}
      diff[key] = bValue
    }
  }
  for (let key in propsB) {
    if (!propsA.hasOwnProperty(key)) {
      diff = diff || {}
      diff[key] = propsB[key]
    }
  }
  return diff
}

function diffChildren (a, b, apply, patches, index) {
  const aChildren = a.children
  const diffSet = diffList(aChildren, b.children, 'key')
  let bChildren = diffSet.list
  let len = Math.max(aChildren.length, bChildren.length)
  for (let i = 0; i < len; i++) {
    let leftNode = aChildren[i]
    let rightNode = bChildren[i]
    index += 1
    if (!leftNode) {
      if (rightNode) {
        apply = appendPatch(apply, new VPatch(VPatch.INSERT, null, rightNode))
      }
    } else {
      walk(leftNode, rightNode, patches, index)
    }
    if (isVNode(leftNode) && leftNode.count) {
      index += leftNode.count
    }
  }
  if (diffSet.moves) {
    apply = appendPatch(apply, new VPatch(VPatch.ORDER, a, diffSet.moves))
  }
  return apply
}

function diffList (oldList, newList, key) {
  const newListKeyIndex = mapListKeyIndex(newList, key)
  const newListkeyMap = newListKeyIndex.keyMap
  const newListFree = newListKeyIndex.free
  if (newListFree.length === newList.length) {
    return {
      list: newList,
      moves: null
    }
  }
  const oldListKeyIndex = mapListKeyIndex(oldList, key)
  const oldListkeyMap = oldListKeyIndex.keyMap
  const oldListFree = oldListKeyIndex.free
  if (oldListFree.length === oldList.length) {
    return {
      list: newList,
      moves: null
    }
  }
  let listChange = []
  let freeIndex = 0
  let deletedItems = 0
  listChange = oldList.map((item) => {
    const itemKey = item[key]
    if (itemKey) {
      if (newListkeyMap.hasOwnProperty(itemKey)) {
        return newList[newListkeyMap[itemKey]]
      }
      deletedItems++
      return null
    }
    let itemIndex = newListFree[freeIndex++]
    let freeItem = newList[itemIndex]
    if (!freeItem) {
      deletedItems++
      return null
    }
    return freeItem
  })
  let lastFreeIndex = freeIndex >= newListFree.length ? newList.length : newListFree[freeIndex]
  newList.forEach((newItem, index) => {
    const itemKey = newItem[key]
    if (itemKey) {
      if (!oldListkeyMap.hasOwnProperty(itemKey)) {
        listChange.push(newItem)
      }
    } else if (index >= lastFreeIndex) {
      listChange.push(newItem)
    }
  })

  let simulate = listChange.slice(0)
  let simulateIndex = 0
  let removes = []
  let inserts = []
  let simulateItem
  for (let k = 0; k < newList.length;) {
    simulateItem = simulate[simulateIndex]
    while (simulateItem === null && simulate.length) {
      removes.push(remove(simulate, simulateIndex, null))
      simulateItem = simulate[simulateIndex]
    }
    let newItem = newList[k]
    let newItemKey = newItem[key]
    let simulateItemKey = simulateItem[key]
    if (!simulateItem || simulateItemKey !== newItemKey) {
      if (newItem[key]) {
        if (simulateItem && simulateItemKey) {
          if (newListkeyMap[simulateItemKey] !== k + 1) {
            removes.push(remove(simulate, simulateIndex, simulateItemKey))
            simulateItem = simulate[simulateIndex]
            if (!simulateItem || simulateItemKey !== newItemKey) {
              inserts.push({key: newItemKey, to: k})
            } else {
              simulateIndex++
            }
          } else {
            inserts.push({key: newItemKey, to: k})
          }
        } else {
          inserts.push({key: newItemKey, to: k})
        }
        k++
      } else if (simulateItem && simulateItemKey) {
        removes.push(remove(simulate, simulateIndex, simulateItemKey))
      }
    } else {
      simulateIndex++
      k++
    }
  }
  while (simulateIndex < simulate.length) {
    simulateItem = simulate[simulateIndex]
    removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))
  }
  if (removes.length === deletedItems && !inserts.length) {
    return {
      list: listChange,
      moves: null
    }
  }

  return {
    list: listChange,
    moves: {
      removes,
      inserts
    }
  }
}

function remove (arr, index, key) {
  arr.splice(index, 1)
  return {
    from: index,
    key
  }
}

function clearState (vnode, patch, index) {
  unhook(vnode, patch, index)
  destroyWidgets(vnode, patch, index)
}

function unhook (vnode, patch, index) {
  if (isVNode(vnode)) {
    if (vnode.hooks) {
      patch[index] = appendPatch(
        patch[index],
        new VPatch(
          VPatch.PROPS,
          vnode,
          undefinedKeys(vnode.hooks)
        )
      )
    }

    if (vnode.descendantHooks) {
      let children = vnode.children
      let len = children.length
      for (let i = 0; i < len; i++) {
        let child = children[i]
        index += 1
        unhook(child, patch, index)
        if (isVNode(child) && child.count) {
          index += child.count
        }
      }
    }
  } else if (isStateLess(vnode)) {
    index += 1
    unhook(vnode._renderd, patch, index)
  }
}

function destroyWidgets (vnode, patch, index) {
  if (isWidget(vnode)) {
    if (isFunction(vnode.destroy)) {
      patch[index] = appendPatch(patch[index], new VPatch(VPatch.REMOVE, vnode, null))
    }
  } else if (isVNode(vnode) && vnode.hasWidgets) {
    vnode.children.forEach(child => {
      index += 1
      destroyWidgets(child, patch, index)
      if (isVNode(child) && child.count) {
        index += child.count
      }
    })
  } else if (isStateLess(vnode)) {
    index += 1
    destroyWidgets(vnode._renderd, patch, index)
  }
}

function mapListKeyIndex (list, key) {
  let keyMap = {}
  let free = []
  list.forEach((item, i) => {
    if (item[key]) {
      keyMap[item[key]] = i
    } else {
      free.push(i)
    }
  })
  return {
    keyMap,
    free
  }
}

function undefinedKeys (obj) {
  let result = {}

  for (let key in obj) {
    result[key] = undefined
  }

  return result
}

function appendPatch (apply, patch) {
  if (apply) {
    if (Array.isArray(apply)) {
      apply.push(patch)
    } else {
      apply = [apply, patch]
    }
    return apply
  }
  return [patch]
}

export default diff
<commit_msg>fix:do not diff children in props<commit_after>import VPatch from './vpatch'
import { isVNode, isVText, isWidget, isStateLess, isHook } from './vnode/types'
import { isFunction, isObject, getPrototype } from '~'

function diff (a, b) {
  let patches = {old: a}
  walk(a, b, patches, 0)
  return patches
}

function walk (a, b, patches, index) {
  if (a === b) {
    return
  }
  let apply = patches[index]
  let applyClear = false
  if (!b) {
    if (!isWidget(a)) {
      clearState(a, patches, index)
      apply = patches[index]
    }
    apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, null))
  } else if (isVText(b)) {
    if (!isVText(a)) {
      applyClear = true
      apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
    } else if (a.text !== b.text) {
      apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
    }
  } else if (isVNode(b)) {
    if (!isVNode(a)) {
      apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
      applyClear = true
    } else if (a.tagName === b.tagName && a.key === b.key) {
      const propsPatch = diffProps(a.props, b.props)
      if (propsPatch) {
        apply = appendPatch(apply, new VPatch(VPatch.PROPS, a, propsPatch))
      }
      apply = diffChildren(a, b, apply, patches, index)
    } else {
      apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
      applyClear = true
    }
  } else if (isWidget(b)) {
    if (!isWidget(a)) {
      applyClear = true
    }
    apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))
  } else if (Array.isArray(b)) {
    applyClear = true
    b.forEach(item => {
      walk(null, item, patches, index)
      index++
    })
  } else if (isStateLess(b)) {
    applyClear = true
    apply = appendPatch(apply, new VPatch(VPatch.STATELESS, a, b))
  }
  if (apply) {
    patches[index] = apply
  }
  if (applyClear) {
    clearState(a, patches, index)
  }
}

function diffProps (propsA, propsB) {
  let diff = null
  for (let key in propsA) {
    if (key === 'children') {
      continue
    }
    if (!propsB.hasOwnProperty(key)) {
      diff = diff || {}
      diff[key] = undefined
    }
    let aValue = propsA[key]
    let bValue = propsB[key]
    if (aValue === bValue) {
      continue
    } else if (isObject(aValue) && isObject(bValue)) {
      if (getPrototype(aValue) !== getPrototype(bValue)) {
        diff = diff || {}
        diff[key] = bValue
      } else if (isHook(bValue)) {
        diff = diff || {}
        diff[key] = bValue
      } else {
        let objDiff = diffProps(aValue, bValue)
        if (objDiff) {
          diff = diff || {}
          diff[key] = objDiff
        }
      }
    } else {
      diff = diff || {}
      diff[key] = bValue
    }
  }
  for (let key in propsB) {
    if (!propsA.hasOwnProperty(key)) {
      diff = diff || {}
      diff[key] = propsB[key]
    }
  }
  return diff
}

function diffChildren (a, b, apply, patches, index) {
  const aChildren = a.children
  const diffSet = diffList(aChildren, b.children, 'key')
  let bChildren = diffSet.list
  let len = Math.max(aChildren.length, bChildren.length)
  for (let i = 0; i < len; i++) {
    let leftNode = aChildren[i]
    let rightNode = bChildren[i]
    index += 1
    if (!leftNode) {
      if (rightNode) {
        apply = appendPatch(apply, new VPatch(VPatch.INSERT, null, rightNode))
      }
    } else {
      walk(leftNode, rightNode, patches, index)
    }
    if (isVNode(leftNode) && leftNode.count) {
      index += leftNode.count
    }
  }
  if (diffSet.moves) {
    apply = appendPatch(apply, new VPatch(VPatch.ORDER, a, diffSet.moves))
  }
  return apply
}

function diffList (oldList, newList, key) {
  const newListKeyIndex = mapListKeyIndex(newList, key)
  const newListkeyMap = newListKeyIndex.keyMap
  const newListFree = newListKeyIndex.free
  if (newListFree.length === newList.length) {
    return {
      list: newList,
      moves: null
    }
  }
  const oldListKeyIndex = mapListKeyIndex(oldList, key)
  const oldListkeyMap = oldListKeyIndex.keyMap
  const oldListFree = oldListKeyIndex.free
  if (oldListFree.length === oldList.length) {
    return {
      list: newList,
      moves: null
    }
  }
  let listChange = []
  let freeIndex = 0
  let deletedItems = 0
  listChange = oldList.map((item) => {
    const itemKey = item[key]
    if (itemKey) {
      if (newListkeyMap.hasOwnProperty(itemKey)) {
        return newList[newListkeyMap[itemKey]]
      }
      deletedItems++
      return null
    }
    let itemIndex = newListFree[freeIndex++]
    let freeItem = newList[itemIndex]
    if (!freeItem) {
      deletedItems++
      return null
    }
    return freeItem
  })
  let lastFreeIndex = freeIndex >= newListFree.length ? newList.length : newListFree[freeIndex]
  newList.forEach((newItem, index) => {
    const itemKey = newItem[key]
    if (itemKey) {
      if (!oldListkeyMap.hasOwnProperty(itemKey)) {
        listChange.push(newItem)
      }
    } else if (index >= lastFreeIndex) {
      listChange.push(newItem)
    }
  })

  let simulate = listChange.slice(0)
  let simulateIndex = 0
  let removes = []
  let inserts = []
  let simulateItem
  for (let k = 0; k < newList.length;) {
    simulateItem = simulate[simulateIndex]
    while (simulateItem === null && simulate.length) {
      removes.push(remove(simulate, simulateIndex, null))
      simulateItem = simulate[simulateIndex]
    }
    let newItem = newList[k]
    let newItemKey = newItem[key]
    let simulateItemKey = simulateItem[key]
    if (!simulateItem || simulateItemKey !== newItemKey) {
      if (newItem[key]) {
        if (simulateItem && simulateItemKey) {
          if (newListkeyMap[simulateItemKey] !== k + 1) {
            removes.push(remove(simulate, simulateIndex, simulateItemKey))
            simulateItem = simulate[simulateIndex]
            if (!simulateItem || simulateItemKey !== newItemKey) {
              inserts.push({key: newItemKey, to: k})
            } else {
              simulateIndex++
            }
          } else {
            inserts.push({key: newItemKey, to: k})
          }
        } else {
          inserts.push({key: newItemKey, to: k})
        }
        k++
      } else if (simulateItem && simulateItemKey) {
        removes.push(remove(simulate, simulateIndex, simulateItemKey))
      }
    } else {
      simulateIndex++
      k++
    }
  }
  while (simulateIndex < simulate.length) {
    simulateItem = simulate[simulateIndex]
    removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))
  }
  if (removes.length === deletedItems && !inserts.length) {
    return {
      list: listChange,
      moves: null
    }
  }

  return {
    list: listChange,
    moves: {
      removes,
      inserts
    }
  }
}

function remove (arr, index, key) {
  arr.splice(index, 1)
  return {
    from: index,
    key
  }
}

function clearState (vnode, patch, index) {
  unhook(vnode, patch, index)
  destroyWidgets(vnode, patch, index)
}

function unhook (vnode, patch, index) {
  if (isVNode(vnode)) {
    if (vnode.hooks) {
      patch[index] = appendPatch(
        patch[index],
        new VPatch(
          VPatch.PROPS,
          vnode,
          undefinedKeys(vnode.hooks)
        )
      )
    }

    if (vnode.descendantHooks) {
      let children = vnode.children
      let len = children.length
      for (let i = 0; i < len; i++) {
        let child = children[i]
        index += 1
        unhook(child, patch, index)
        if (isVNode(child) && child.count) {
          index += child.count
        }
      }
    }
  } else if (isStateLess(vnode)) {
    index += 1
    unhook(vnode._renderd, patch, index)
  }
}

function destroyWidgets (vnode, patch, index) {
  if (isWidget(vnode)) {
    if (isFunction(vnode.destroy)) {
      patch[index] = appendPatch(patch[index], new VPatch(VPatch.REMOVE, vnode, null))
    }
  } else if (isVNode(vnode) && vnode.hasWidgets) {
    vnode.children.forEach(child => {
      index += 1
      destroyWidgets(child, patch, index)
      if (isVNode(child) && child.count) {
        index += child.count
      }
    })
  } else if (isStateLess(vnode)) {
    index += 1
    destroyWidgets(vnode._renderd, patch, index)
  }
}

function mapListKeyIndex (list, key) {
  let keyMap = {}
  let free = []
  list.forEach((item, i) => {
    if (item[key]) {
      keyMap[item[key]] = i
    } else {
      free.push(i)
    }
  })
  return {
    keyMap,
    free
  }
}

function undefinedKeys (obj) {
  let result = {}

  for (let key in obj) {
    result[key] = undefined
  }

  return result
}

function appendPatch (apply, patch) {
  if (apply) {
    if (Array.isArray(apply)) {
      apply.push(patch)
    } else {
      apply = [apply, patch]
    }
    return apply
  }
  return [patch]
}

export default diff
<|endoftext|>


<commit_before><commit_msg>Fix animations.<commit_after><|endoftext|>


<commit_before>import React from 'react';
import FontAwesome from 'react-fontawesome';
import { Icon } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { NavLink, withRouter } from 'react-router-dom';
import classnames from 'classnames';
import _ from 'lodash';
import Sound from 'react-hifi';
import { withTranslation } from 'react-i18next';
import { PluginConfig } from '@nuclear/core';

import * as SearchActions from './actions/search';
import * as PlayerActions from './actions/player';
import * as PlaylistsActions from './actions/playlists';
import * as PluginsActions from './actions/plugins';
import * as QueueActions from './actions/queue';
import * as SettingsActions from './actions/settings';
import * as ScrobblingActions from './actions/scrobbling';
import * as ImportFavActions from './actions/importfavs';
import * as ConnectivityActions from './actions/connectivity';
import * as GithubContribActions from './actions/githubContrib';
import * as WindowActions from './actions/window';

import './app.global.scss';
import styles from './styles.scss';
import compact from './compact.scss';

import logoImg from '../resources/media/logo_full_light.png';
import logoIcon from '../resources/media/512x512.png';

import settingsConst from './constants/settings';

import PlaylistsSubMenu from './components/PlaylistsSubMenu';
import Navbar from './components/Navbar';
import VerticalPanel from './components/VerticalPanel';
import Spacer from './components/Spacer';

import HelpModalContainer from './containers/HelpModalContainer';
import MainContentContainer from './containers/MainContentContainer';
import PlayQueueContainer from './containers/PlayQueueContainer';
import SearchBoxContainer from './containers/SearchBoxContainer';
import PlayerBarContainer from './containers/PlayerBarContainer';
import MiniPlayerContainer from './containers/MiniPlayerContainer';

import IpcContainer from './containers/IpcContainer';
import SoundContainer from './containers/SoundContainer';
import ToastContainer from './containers/ToastContainer';
import ShortcutsContainer from './containers/ShortcutsContainer';
import ErrorBoundary from './containers/ErrorBoundary';

import NavButtons from './components/NavButtons';
import SidebarMenu from './components/SidebarMenu';
import SidebarMenuItem from './components/SidebarMenu/SidebarMenuItem';
import SidebarMenuCategoryHeader from './components/SidebarMenu/SidebarMenuCategoryHeader';
import WindowControls from './components/WindowControls';

@withTranslation('app')
class App extends React.PureComponent {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    this.props.actions.readSettings();
    this.props.actions.lastFmReadSettings();
    this.props.actions.FavImportInit();
    this.props.actions.createPlugins(PluginConfig.plugins);
    this.props.actions.loadPlaylists();
    this.props.actions.deserializePlugins();
    this.props.actions.githubContribInfo();

    this.updateConnectivityStatus(navigator.onLine);
    window.addEventListener('online',  () => this.updateConnectivityStatus(true));
    window.addEventListener('offline',  () => this.updateConnectivityStatus(false));
  }

  updateConnectivityStatus = (isConnected) => {
    this.props.actions.changeConnectivity(isConnected);
  }

  togglePlayback () {
    if (this.props.player.playbackStatus === Sound.status.PAUSED) {
      this.scrobbleLastFmIfAble();
    }
    this.props.actions.togglePlayback(this.props.player.playbackStatus);
  }

  nextSong () {
    this.props.actions.nextSong();
    this.scrobbleLastFmIfAble();
  }

  scrobbleLastFmIfAble () {
    if (this.canScrobbleLastFm()) {
      this.scrobbleLastFm();
    }
  }

  scrobbleLastFm () {
    const currentSong = this.props.queue.queueItems[
      this.props.queue.currentSong
    ];
    this.props.actions.updateNowPlayingAction(
      currentSong.artist,
      currentSong.name,
      this.props.scrobbling.lastFmSessionKey
    );
  }

  canScrobbleLastFm () {
    return this.props.scrobbling.lastFmScrobblingEnabled &&
      this.props.scrobbling.lastFmSessionKey;
  }

  renderNavBar () {
    return (
      <Navbar>
        <NavButtons/>
        <SearchBoxContainer />
        <Spacer className={styles.navbar_spacer}/>
        <HelpModalContainer />
        {this.props.settings.framelessWindow && (
          <WindowControls
            onCloseClick={this.props.actions.closeWindow}
            onMaxClick={this.props.actions.maximizeWindow}
            onMinClick={this.props.actions.minimizeWindow}
          />
        )}
      </Navbar>
    );
  }

  renderRightPanel () {
    return (
      <VerticalPanel
        className={classnames(styles.right_panel, {
          [`${compact.compact_panel}`]: this.props.settings.compactQueueBar
        })}
      >
        <PlayQueueContainer compact={this.props.settings.compactQueueBar} />
      </VerticalPanel>
    );
  }

  renderSidebarMenu () {

    return (
      <VerticalPanel
        className={classnames(styles.left_panel, {
          [`${compact.compact_panel}`]: this.props.settings.compactMenuBar
        })}
      >
        <SidebarMenu>

              {this.props.settings.compactMenuBar ? '0.6.11' : 'Version 0.6.11'}

            {
              this.renderMenuCategory('main', [
                { name: 'dashboard', path: 'dashboard', icon: 'dashboard' },
                { name: 'downloads', path: 'downloads', icon: 'download' },
                { name: 'lyrics', path: 'lyrics', icon: 'microphone' },
                { name: 'plugins', path: 'plugins', icon: 'flask' },
                { name: 'search', path: 'search', icon: 'search' },
                { name: 'settings', path: 'settings', icon: 'cogs' },
                { name: 'equalizer', path: 'equalizer', icon: 'sliders' },
                { name: 'visualizer', path: 'visualizer', icon: 'tint' }
              ])
            }

            {
              this.renderMenuCategory('collection', [
                { name: 'favorite-albums', path: 'favorites/albums', icon: 'dot circle'},
                { name: 'favorite-tracks', path: 'favorites/tracks', icon: 'music'},
                { name: 'library', path: 'library', icon: 'file audio outline' }
              ])
            }

            {
              !_.isEmpty(this.props.playlists) &&
              <SidebarMenuCategoryHeader compact={this.props.settings.compactMenuBar} headerText={'playlists'}/>
            }
            <PlaylistsSubMenu
              playlists={this.props.playlists}
              compact={this.props.settings.compactMenuBar}
            />

          {this.renderSidebarFooter()}
        </SidebarMenu>
      </VerticalPanel>
    );
  }

  renderMenuCategory (headerText, links) {
    return <React.Fragment>
      <SidebarMenuCategoryHeader
        compact={this.props.settings.compactMenuBar}
        headerText={this.props.t(headerText)}
      />
      {
        links.map(
          link => this.renderNavLink(link.name, link.path, link.icon)
        )
      }
    </React.Fragment>;
  }

  renderNavLink (name, path, icon) {
    return (
      <NavLink key={path} to={'/' + path} activeClassName={styles.active_nav_link}>
        <SidebarMenuItem>
          <Icon name={icon} />{!this.props.settings.compactMenuBar && this.props.t(name)}
        </SidebarMenuItem>
      </NavLink>
    );
  }

  renderSidebarFooter () {
    return (

         {
            this.props.actions.toggleOption(
              _.find(settingsConst, ['name', 'compactMenuBar']),
              this.props.settings
            );
          }}
          href='#'
        >
          <FontAwesome
            name={this.props.settings.compactMenuBar ? 'angle-right' : 'angle-left'}
          />

    );
  }

  getCurrentSongParameter (parameter) {
    return this.props.queue.queueItems[this.props.queue.currentSong]
      ? this.props.queue.queueItems[this.props.queue.currentSong][parameter]
      : null;
  }

  render () {
    return (
      <>
        <ErrorBoundary>

            <MiniPlayerContainer />
            {this.renderNavBar()}

              {this.renderSidebarMenu()}
              <VerticalPanel className={styles.center_panel}>
                <MainContentContainer />
              </VerticalPanel>
              {this.renderRightPanel()}

            <PlayerBarContainer />
            <SoundContainer />
            <IpcContainer />

        </ErrorBoundary>
        <ShortcutsContainer />
        <ToastContainer />
      </>
    );
  }
}

function mapStateToProps (state) {
  return {
    queue: state.queue,
    player: state.player,
    playlists: state.playlists.playlists,
    scrobbling: state.scrobbling,
    settings: state.settings,
    isConnected: state.connectivity
  };
}

function mapDispatchToProps (dispatch) {
  return {
    actions: bindActionCreators(
      Object.assign(
        {},
        ScrobblingActions,
        ImportFavActions,
        SettingsActions,
        QueueActions,
        PlayerActions,
        PlaylistsActions,
        PluginsActions,
        ConnectivityActions,
        SearchActions,
        GithubContribActions,
        WindowActions
      ),
      dispatch
    )
  };
}

export default withRouter(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )(App)
);
<commit_msg>feat: playlists button in menu bar<commit_after>import React from 'react';
import FontAwesome from 'react-fontawesome';
import { Icon } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { NavLink, withRouter } from 'react-router-dom';
import classnames from 'classnames';
import _ from 'lodash';
import Sound from 'react-hifi';
import { withTranslation } from 'react-i18next';
import { PluginConfig } from '@nuclear/core';

import * as SearchActions from './actions/search';
import * as PlayerActions from './actions/player';
import * as PlaylistsActions from './actions/playlists';
import * as PluginsActions from './actions/plugins';
import * as QueueActions from './actions/queue';
import * as SettingsActions from './actions/settings';
import * as ScrobblingActions from './actions/scrobbling';
import * as ImportFavActions from './actions/importfavs';
import * as ConnectivityActions from './actions/connectivity';
import * as GithubContribActions from './actions/githubContrib';
import * as WindowActions from './actions/window';

import './app.global.scss';
import styles from './styles.scss';
import compact from './compact.scss';

import logoImg from '../resources/media/logo_full_light.png';
import logoIcon from '../resources/media/512x512.png';

import settingsConst from './constants/settings';

import Navbar from './components/Navbar';
import VerticalPanel from './components/VerticalPanel';
import Spacer from './components/Spacer';

import HelpModalContainer from './containers/HelpModalContainer';
import MainContentContainer from './containers/MainContentContainer';
import PlayQueueContainer from './containers/PlayQueueContainer';
import SearchBoxContainer from './containers/SearchBoxContainer';
import PlayerBarContainer from './containers/PlayerBarContainer';
import MiniPlayerContainer from './containers/MiniPlayerContainer';

import IpcContainer from './containers/IpcContainer';
import SoundContainer from './containers/SoundContainer';
import ToastContainer from './containers/ToastContainer';
import ShortcutsContainer from './containers/ShortcutsContainer';
import ErrorBoundary from './containers/ErrorBoundary';

import NavButtons from './components/NavButtons';
import SidebarMenu from './components/SidebarMenu';
import SidebarMenuItem from './components/SidebarMenu/SidebarMenuItem';
import SidebarMenuCategoryHeader from './components/SidebarMenu/SidebarMenuCategoryHeader';
import WindowControls from './components/WindowControls';

@withTranslation('app')
class App extends React.PureComponent {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    this.props.actions.readSettings();
    this.props.actions.lastFmReadSettings();
    this.props.actions.FavImportInit();
    this.props.actions.createPlugins(PluginConfig.plugins);
    this.props.actions.loadPlaylists();
    this.props.actions.deserializePlugins();
    this.props.actions.githubContribInfo();

    this.updateConnectivityStatus(navigator.onLine);
    window.addEventListener('online',  () => this.updateConnectivityStatus(true));
    window.addEventListener('offline',  () => this.updateConnectivityStatus(false));
  }

  updateConnectivityStatus = (isConnected) => {
    this.props.actions.changeConnectivity(isConnected);
  }

  togglePlayback () {
    if (this.props.player.playbackStatus === Sound.status.PAUSED) {
      this.scrobbleLastFmIfAble();
    }
    this.props.actions.togglePlayback(this.props.player.playbackStatus);
  }

  nextSong () {
    this.props.actions.nextSong();
    this.scrobbleLastFmIfAble();
  }

  scrobbleLastFmIfAble () {
    if (this.canScrobbleLastFm()) {
      this.scrobbleLastFm();
    }
  }

  scrobbleLastFm () {
    const currentSong = this.props.queue.queueItems[
      this.props.queue.currentSong
    ];
    this.props.actions.updateNowPlayingAction(
      currentSong.artist,
      currentSong.name,
      this.props.scrobbling.lastFmSessionKey
    );
  }

  canScrobbleLastFm () {
    return this.props.scrobbling.lastFmScrobblingEnabled &&
      this.props.scrobbling.lastFmSessionKey;
  }

  renderNavBar () {
    return (
      <Navbar>
        <NavButtons/>
        <SearchBoxContainer />
        <Spacer className={styles.navbar_spacer}/>
        <HelpModalContainer />
        {this.props.settings.framelessWindow && (
          <WindowControls
            onCloseClick={this.props.actions.closeWindow}
            onMaxClick={this.props.actions.maximizeWindow}
            onMinClick={this.props.actions.minimizeWindow}
          />
        )}
      </Navbar>
    );
  }

  renderRightPanel () {
    return (
      <VerticalPanel
        className={classnames(styles.right_panel, {
          [`${compact.compact_panel}`]: this.props.settings.compactQueueBar
        })}
      >
        <PlayQueueContainer compact={this.props.settings.compactQueueBar} />
      </VerticalPanel>
    );
  }

  renderSidebarMenu () {

    return (
      <VerticalPanel
        className={classnames(styles.left_panel, {
          [`${compact.compact_panel}`]: this.props.settings.compactMenuBar
        })}
      >
        <SidebarMenu>

              {this.props.settings.compactMenuBar ? '0.6.11' : 'Version 0.6.11'}

            {
              this.renderMenuCategory('main', [
                { name: 'dashboard', path: 'dashboard', icon: 'dashboard' },
                { name: 'downloads', path: 'downloads', icon: 'download' },
                { name: 'lyrics', path: 'lyrics', icon: 'microphone' },
                { name: 'plugins', path: 'plugins', icon: 'flask' },
                { name: 'search', path: 'search', icon: 'search' },
                { name: 'settings', path: 'settings', icon: 'cogs' },
                { name: 'equalizer', path: 'equalizer', icon: 'sliders' },
                { name: 'visualizer', path: 'visualizer', icon: 'tint' }
              ])
            }

            {
              this.renderMenuCategory('collection', [
                { name: 'favorite-albums', path: 'favorites/albums', icon: 'dot circle'},
                { name: 'favorite-tracks', path: 'favorites/tracks', icon: 'music'},
                { name: 'library', path: 'library', icon: 'file audio outline' },
                { name: 'playlists', path: 'playlists', icon: 'list alternate outline'}
              ])
            }

          {this.renderSidebarFooter()}
        </SidebarMenu>
      </VerticalPanel>
    );
  }

  renderMenuCategory (headerText, links) {
    return <React.Fragment>
      <SidebarMenuCategoryHeader
        compact={this.props.settings.compactMenuBar}
        headerText={this.props.t(headerText)}
      />
      {
        links.map(
          link => this.renderNavLink(link.name, link.path, link.icon)
        )
      }
    </React.Fragment>;
  }

  renderNavLink (name, path, icon) {
    return (
      <NavLink key={path} to={'/' + path} activeClassName={styles.active_nav_link}>
        <SidebarMenuItem>
          <Icon name={icon} />{!this.props.settings.compactMenuBar && this.props.t(name)}
        </SidebarMenuItem>
      </NavLink>
    );
  }

  renderSidebarFooter () {
    return (

         {
            this.props.actions.toggleOption(
              _.find(settingsConst, ['name', 'compactMenuBar']),
              this.props.settings
            );
          }}
          href='#'
        >
          <FontAwesome
            name={this.props.settings.compactMenuBar ? 'angle-right' : 'angle-left'}
          />

    );
  }

  getCurrentSongParameter (parameter) {
    return this.props.queue.queueItems[this.props.queue.currentSong]
      ? this.props.queue.queueItems[this.props.queue.currentSong][parameter]
      : null;
  }

  render () {
    return (
      <>
        <ErrorBoundary>

            <MiniPlayerContainer />
            {this.renderNavBar()}

              {this.renderSidebarMenu()}
              <VerticalPanel className={styles.center_panel}>
                <MainContentContainer />
              </VerticalPanel>
              {this.renderRightPanel()}

            <PlayerBarContainer />
            <SoundContainer />
            <IpcContainer />

        </ErrorBoundary>
        <ShortcutsContainer />
        <ToastContainer />
      </>
    );
  }
}

function mapStateToProps (state) {
  return {
    queue: state.queue,
    player: state.player,
    playlists: state.playlists.playlists,
    scrobbling: state.scrobbling,
    settings: state.settings,
    isConnected: state.connectivity
  };
}

function mapDispatchToProps (dispatch) {
  return {
    actions: bindActionCreators(
      Object.assign(
        {},
        ScrobblingActions,
        ImportFavActions,
        SettingsActions,
        QueueActions,
        PlayerActions,
        PlaylistsActions,
        PluginsActions,
        ConnectivityActions,
        SearchActions,
        GithubContribActions,
        WindowActions
      ),
      dispatch
    )
  };
}

export default withRouter(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )(App)
);
<|endoftext|>